批量设置数据从Dictionary到Redis

时间:2017-06-16 09:01:44

标签: c# redis stackexchange.redis

我使用StackExchange Redis DB使用Batch插入Key值对字典,如下所示:

private static StackExchange.Redis.IDatabase _database;
public void SetAll<T>(Dictionary<string, T> data, int cacheTime)
{
    lock (_database)
    {
        TimeSpan expiration = new TimeSpan(0, cacheTime, 0);
        var list = new List<Task<bool>>();
        var batch = _database.CreateBatch();               
        foreach (var item in data)
        {
            string serializedObject = JsonConvert.SerializeObject(item.Value, Formatting.Indented,
        new JsonSerializerSettings { ContractResolver = new SerializeAllContractResolver(), ReferenceLoopHandling = ReferenceLoopHandling.Ignore });

            var task = batch.StringSetAsync(item.Key, serializedObject, expiration);
            list.Add(task);
            serializedObject = null;
        }
        batch.Execute();

        Task.WhenAll(list.ToArray());
    }
}

我的问题 7秒需要设置350项词典。

我的问题:这是将批量商品设置为Redis的正确方法,还是有更快的方法可以做到这一点? 任何帮助表示赞赏。感谢。

2 个答案:

答案 0 :(得分:4)

“just”是一个非常相对的术语,没有更多的上下文就没有意义,特别是:这些有效载荷有多大?

然而,澄清几点可以帮助你调查:

  • 除非纯粹出于您自己的目的,否则无需锁定IDatabase; SE.Redis内部处理线程安全,旨在供竞争线程使用
  • 目前,您的时间安排将包括所有序列化代码(JsonConvert.SerializeObject);如果您的对象很大,这将加起来,尤其是;为了获得一个体面的衡量标准,我强烈建议你将序列化和redis时间单独分开
  • batch.Execute()方法使用管道API并且不等待呼叫之间的响应,因此:您看到的时间延迟的累积影响;只留下本地CPU(用于序列化),网络带宽和服务器CPU;客户端库工具不会影响任何这些东西
  • StringSet重载,接受KeyValuePair<RedisKey, RedisValue>[];你可以选择使用它而不是批处理,但唯一的区别是它是varadic MSET而不是muliple SET;无论哪种方式,您将在一段时间内阻止其他呼叫者的连接(因为批处理的目的是使命令连续)
  • 实际上需要在这里使用CreateBatch尤其是,因为你正在锁定数据库(但我仍然建议你不需要去做这个); CreateBatch的目的是制作一系列命令顺序,但我不认为你需要这个;您可以依次为每个命令使用_database.StringSetAsync,这样的优点是您将并行运行序列化 正在发送的上一个命令 - 它将允许您重叠序列化(CPU绑定)和redis操作(IO绑定),除了删除CreateBatch调用之外没有任何工作;这也意味着您不会独占其他来​​电者的连接

因此; 第一个我会做的事情是删除一些代码:

private static StackExchange.Redis.IDatabase _database;
static JsonSerializerSettings _redisJsonSettings = new JsonSerializerSettings {
    ContractResolver = new SerializeAllContractResolver(),
    ReferenceLoopHandling = ReferenceLoopHandling.Ignore };

public void SetAll<T>(Dictionary<string, T> data, int cacheTime)
{
    TimeSpan expiration = new TimeSpan(0, cacheTime, 0);
    var list = new List<Task<bool>>();
    foreach (var item in data)
    {
        string serializedObject = JsonConvert.SerializeObject(
            item.Value, Formatting.Indented, _redisJsonSettings);

        list.Add(_database.StringSetAsync(item.Key, serializedObject, expiration));
    }
    Task.WhenAll(list.ToArray());
}

我要做的第二件事就是将序列化分别计算到redis工作。

我要做的第三件事就是看看我是否可以序列化为MemoryStream,理想情况下我可以重复使用 - 以避免string alocation和UTF-8编码:

using(var ms = new MemoryStream())
{
    foreach (var item in data)
    {
        ms.Position = 0;
        ms.SetLength(0); // erase existing data
        JsonConvert.SerializeObject(ms,
            item.Value, Formatting.Indented, _redisJsonSettings);

        list.Add(_database.StringSetAsync(item.Key, ms.ToArray(), expiration));
    }
}

答案 1 :(得分:3)

第二个答案有点切,但基于讨论,听起来似乎主要成本是序列化:

  

此上下文中的对象很大,在字符串道具和许多嵌套类中有大量信息。

你可以做的一件事是不存储JSON 。 JSON相对较大,基于文本的处理序列化和反序列化都相对昂贵。除非您使用rejson,否则redis会将您的数据视为不透明的blob,因此它并不关心实际值。因此,您可以使用更有效的格式。

我非常偏颇,但我们在redis存储中使用了protobuf-net。 protobuf-net针对以下方面进行了优化:

  • 小输出(没有冗余信息的密集二进制)
  • 快速二进制处理(使用上下文IL发射进行荒谬优化等)
  • 良好的跨平台支持(它实现了Google&#34; protobuf&#34;有线格式,可在几乎所有可用平台上使用)
  • 旨在与现有的C#代码配合使用,而不仅仅是从.proto架构生成的全新类型

由于最后一个要点,我建议使用protobuf-net而不是谷歌自己的C#protobuf库,这意味着:你可以将它与你已有的数据一起使用。

为了说明原因,我将使用https://aloiskraus.wordpress.com/2017/04/23/the-definitive-serialization-performance-guide/中的此图片:

serializer performance

特别注意protobuf-net的输出大小是Json.NET的一半(降低带宽成本),序列化时间不到五分之一(降低本地CPU成本)。

您需要在模型中添加一些属性以帮助protobuf-net out(按照How to convert existing POCO classes in C# to google Protobuf standard POCO),但这只是:

using(var ms = new MemoryStream())
{
    foreach (var item in data)
    {
        ms.Position = 0;
        ms.SetLength(0); // erase existing data
        ProtoBuf.Serializer.Serialize(ms, item.Value);

        list.Add(_database.StringSetAsync(item.Key, ms.ToArray(), expiration));
    }
}

如您所见,对redis代码的代码更改很少。显然,在阅读数据时你需要使用Deserialize<T>

如果您的数据是基于文本的,则可以考虑通过GZipStreamDeflateStream运行序列化。如果你的数据是由文本控制的,它会很好地压缩。