如何使用多线程C#应用程序在Redis中插入数百万个键/值

时间:2019-04-04 11:53:33

标签: c# multithreading redis

我需要创建一个C#应用程序(Windows服务),该应用程序每5秒运行一次(间隔),并生成大约2000万个值。

我需要在5秒内将这2000万个值插入Redis(1个键/值),并确保在下一个间隔开始之前完成插入。

注意:我只需要在Redis中保留7个周期 => 2000万* 7 => 1.4亿个Redis键

我正在使用C#的Threading.Tasks调用一个函数(2000万次),以便并行(异步)处理它们。

我什至为我的进程创建了一个Redis客户端池,以便能够并行执行Redis查询。

这里是C#调用函数2000万次的部分:

List<Task> tasksList = new List<Task>();

foreach (object k in ListOf20MillionData)
{
    tasksList.Add(

        Task.Factory.StartNew(() =>
        {
            GenerateValue(k);
            //Inside 'GenerateValue' data is generated and pushed to redis
        })
    );
}

这是'GenerateValue'中的一段代码,它从客户端池中获取redis客户端对象,执行插入操作并将redis客户端释放回该池中。

RedisClient redisClientObj = RedisPool.GetNextAvailableClient();

redisClientObj.Add("SomeKey", "SomeValue");

RedisPool.ReleaseRedisClient(redisClientObj );

我的关注和挑战:

  1. 我的Redis Pools概念还可以吗?
  2. Redis可以处理多少个客户端连接?
  3. 使用C#和Redis甚至可以实现我的请求吗?
  4. 任何建议或建议都将受到高度赞赏。

1 个答案:

答案 0 :(得分:2)

  1. 我的Redis Pools概念还可以吗?

不是。池不会为您提供更多的吞吐量。它们通过顺序命令分解不同的逻辑连接范围,并允许简单的并发...但是redis核心是单线程的,您应该希望饱和网络,而不是线程。

  1. Redis可以处理多少个客户端连接?

吨数,但是添加更多的吨数对您没有帮助-实际上,建立大量的连接会增加开销

  1. 使用C#和Redis甚至可以实现我的请求吗?

仅在具有庞大网络的非常盒子上出现;您可能使用“集群”增加吞吐量,但这也会增加数据包碎片

  1. 任何建议或建议都将受到高度赞赏。

批次。疯狂地批处理,以最大程度地减少往返次数。快速响应的胖批处理可以非常有效地利用网络,并且不需要复杂的代码。 redis mset命令已针对以下方面进行了优化:胖批处理响应很小。

在本地,同一台计算机在单个线程将数据发明为Redis服务器,但是对我来说,它仍然需要34秒,

    static void Main()
    {
        using (var conn = ConnectionMultiplexer.Connect("127.0.0.1:6379"))
        {
            var db = conn.GetDatabase();
            var watch = Stopwatch.StartNew();
            foreach(var batch in InventData(20000000).Batchify(5000))
            {
                db.StringSet(batch);
            }
            watch.Stop();
            Console.WriteLine(watch.ElapsedMilliseconds);
        }
    }

或者如果我使用Parallel,即

            var watch = Stopwatch.StartNew();
            Parallel.ForEach(InventData(20000000).Batchify(5000),
                batch => db.StringSet(batch));
            watch.Stop();

需要16秒。

和(请参阅注释),如果我将Parallel与async结合使用:

            var watch = Stopwatch.StartNew();
            Parallel.ForEach(InventData(20000000).Batchify(5000),
                batch => db.StringSetAsync(batch));
            watch.Stop();

然后只需不到14秒的时间。

    static IEnumerable<KeyValuePair<RedisKey, RedisValue>> InventData(int count)
    {
        if (count < 0) throw new ArgumentOutOfRangeException(nameof(count));
        string dictionary = "abcdefghijklmnopqrstuvwxyz _@:0123456789";
        int dLen = dictionary.Length;
        var rand = new Random(12345);
        const int KEY_LEN = 10, MAX_VAL_LEN = 50;
        char[] keyData = new char[KEY_LEN];
        char[] valueData = new char[MAX_VAL_LEN];
        while (count-- != 0)
        {
            for (int i = 0; i < keyData.Length; i++)
                keyData[i] = dictionary[rand.Next(dLen)];
            var len = rand.Next(10, MAX_VAL_LEN);
            for(int i = 0; i < len; i++)
                valueData[i] = dictionary[rand.Next(dLen)];

            yield return new KeyValuePair<RedisKey, SomeType>(
                new string(keyData), new string(valueData, 0, len));
        }
    }

    static IEnumerable<T[]> Batchify<T>(this IEnumerable<T> source, int batchSize)
    {
        var batch = new List<T>(batchSize);
        foreach(var item in source)
        {
            batch.Add(item);
            if (batch.Count == batchSize)
            {
                var arr = batch.ToArray();
                batch.Clear();
                yield return arr;
            }
        }
        if (batch.Count != 0) yield return batch.ToArray(); // trailers
    }