我使用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的正确方法,还是有更快的方法可以做到这一点? 任何帮助表示赞赏。感谢。
答案 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针对以下方面进行了优化:
由于最后一个要点,我建议使用protobuf-net而不是谷歌自己的C#protobuf库,这意味着:你可以将它与你已有的数据一起使用。
为了说明原因,我将使用https://aloiskraus.wordpress.com/2017/04/23/the-definitive-serialization-performance-guide/中的此图片:
特别注意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>
。
如果您的数据是基于文本的,则还可以考虑通过GZipStream
或DeflateStream
运行序列化。如果你的数据是由文本控制的,它会很好地压缩。