将数据从Dictionary批量设置为Redis

Use*_*250 2 c# redis stackexchange.redis

我正在使用StackExchange Redis DB插入键值对的字典,Batch如下所示:

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());
    }
}
Run Code Online (Sandbox Code Playgroud)

我的问题:只设置350项字典需要7秒钟左右。

我的问题:这是将批量商品放入Redis的正确方法,还是有一种更快的方法?任何帮助表示赞赏。谢谢。

Mar*_*ell 6

第二个答案有点离题,但根据讨论,听起来主要成本是序列化:

此上下文中的对象很大,字符串属性和许多嵌套类中包含大量信息。

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

我有很大的偏见,但我们在 Redis 存储中使用了 protobuf-net。protobuf-net 针对以下方面进行了优化:

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

我建议使用 protobuf-net 而不是 Google 自己的 C# protobuf 库,因为最后一个要点意味着:您可以将它与已有的数据一起使用。

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

串行器性能

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

您需要向模型添加一些属性以帮助 protobuf-net 输出(按照How to conversion 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));
    }
}
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,对 redis 代码的代码更改是最少的。Deserialize<T>显然,您在读回数据时需要使用。


如果您的数据是基于文本的,您GZipStream可以考虑通过或运行序列化DeflateStream;如果你的数据以文本为主,它会压缩得很好。


Mar*_*ell 5

“正义”是一个非常相对的术语,没有更多的上下文就没有任何意义,尤其是:这些有效载荷有多大?

但是,请澄清一些要点,以帮助您进行调查:

  • IDatabase除非纯粹出于您自己的目的,否则无需锁定an ;SE.Redis内部处理线程安全,旨在供竞争线程使用
  • 目前,您的时间将包括所有序列化代码(JsonConvert.SerializeObject);这将加起来,尤其是当您的对象很大时;得到一个体面的措施,我强烈建议你的时间序列化和Redis的时间分别
  • batch.Execute()方法使用管道API,并且不等待调用之间的响应,因此:您所看到的时间不是延迟的累积影响;只剩下本地CPU(用于序列化),网络带宽和服务器CPU;客户端库工具不会影响任何这些事情
  • 有一个StringSet接受的重载KeyValuePair<RedisKey, RedisValue>[]; 您可以选择使用它而不是批处理,但是这里唯一的区别是,它是方尖碑MSET而不是多尖峰SET;无论哪种方式,您都将在此时间内阻止其他调用者的连接(因为批处理的目的是使命令连续)
  • 实际上不需要在CreateBatch这里使用,尤其是因为您正在锁定数据库(但我仍然建议您不需要这样做);的目的CreateBatch是使一系列命令顺序化,但是我看不到您在这里需要这个;您可以只_database.StringSetAsync依次使用每个命令,这具有与发送前一条命令并行运行序列化的优势-它可以让您重叠序列化(绑定CPU)和redis ops(绑定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());
}
Run Code Online (Sandbox Code Playgroud)

我要做的第二件事是将序列化与redis的工作分开计时。

我要做的第三件事是看我是否可以序列化为MemoryStream一个我可以重复使用的,理想情况下可以避免重复使用-避免string分配和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));
    }
}
Run Code Online (Sandbox Code Playgroud)