将字典中的数据批量设置到 Redis
Batch set data from Dictionary into 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());
}
}
我的问题:设置 350 个字典项需要大约 7 秒。
我的问题:这是将批量项目设置到 Redis 中的正确方法还是有更快的方法?
任何帮助表示赞赏。谢谢
"just" 是一个非常相对的术语,如果没有更多的上下文就没有真正意义,特别是:这些有效载荷有多大?
不过,澄清几点以帮助您调查:
- 没有必要锁定
IDatabase
除非那纯粹是为了您自己的目的; SE.Redis 在内部处理线程安全,旨在供竞争线程使用
- 目前,您的计时将包括所有序列化代码(
JsonConvert.SerializeObject
);这将加起来,尤其是 如果你的对象很大;为了获得合适的衡量标准,我强烈建议您分别对序列化和 Redis 时间进行计时
batch.Execute()
方法使用管道 API 并且不等待调用之间的响应,因此:您看到的时间 而不是 潜伏期的累积效应;只剩下本地 CPU(用于序列化)、网络带宽和服务器 CPU;客户端库工具不会影响任何这些东西
- 有一个接受
KeyValuePair<RedisKey, RedisValue>[]
的 StringSet
重载;你 可以 选择使用它而不是批处理,但这里唯一的区别是它是 varadic MSET
而不是 muliple 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());
}
我要做的第二件事是将序列化与 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));
}
}
第二个答案有点离题,但根据讨论,主要成本似乎是序列化:
The object in this context is big with huge infos in string props and many nested classes.
你可以在这里做的一件事是不存储JSON。 JSON 相对较大,并且基于文本的序列化和反序列化处理成本相对较高。除非你使用 rejson
,否则 redis 只是将你的数据视为一个不透明的 blob,所以它不关心实际值是什么。因此,您可以使用更高效的格式。
我有很大的偏见,但我们在我们的 redis 存储中使用了 protobuf-net。 protobuf-net 针对:
进行了优化
- 小输出(没有冗余信息的密集二进制)
- 快速二进制处理(通过上下文 IL emit 等进行了荒谬的优化)
- 良好的跨平台支持(它实现了 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 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
序列化;如果你的数据以文本为主,它会很好地压缩。
我正在使用 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());
}
}
我的问题:设置 350 个字典项需要大约 7 秒。
我的问题:这是将批量项目设置到 Redis 中的正确方法还是有更快的方法? 任何帮助表示赞赏。谢谢
"just" 是一个非常相对的术语,如果没有更多的上下文就没有真正意义,特别是:这些有效载荷有多大?
不过,澄清几点以帮助您调查:
- 没有必要锁定
IDatabase
除非那纯粹是为了您自己的目的; SE.Redis 在内部处理线程安全,旨在供竞争线程使用 - 目前,您的计时将包括所有序列化代码(
JsonConvert.SerializeObject
);这将加起来,尤其是 如果你的对象很大;为了获得合适的衡量标准,我强烈建议您分别对序列化和 Redis 时间进行计时 batch.Execute()
方法使用管道 API 并且不等待调用之间的响应,因此:您看到的时间 而不是 潜伏期的累积效应;只剩下本地 CPU(用于序列化)、网络带宽和服务器 CPU;客户端库工具不会影响任何这些东西- 有一个接受
KeyValuePair<RedisKey, RedisValue>[]
的StringSet
重载;你 可以 选择使用它而不是批处理,但这里唯一的区别是它是 varadicMSET
而不是 mulipleSET
;无论哪种方式,您都将在持续时间内阻止其他呼叫者的连接(因为批处理的目的是使命令连续) - 您 实际上 不需要在这里使用
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());
}
我要做的第二件事是将序列化与 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));
}
}
第二个答案有点离题,但根据讨论,主要成本似乎是序列化:
The object in this context is big with huge infos in string props and many nested classes.
你可以在这里做的一件事是不存储JSON。 JSON 相对较大,并且基于文本的序列化和反序列化处理成本相对较高。除非你使用 rejson
,否则 redis 只是将你的数据视为一个不透明的 blob,所以它不关心实际值是什么。因此,您可以使用更高效的格式。
我有很大的偏见,但我们在我们的 redis 存储中使用了 protobuf-net。 protobuf-net 针对:
进行了优化- 小输出(没有冗余信息的密集二进制)
- 快速二进制处理(通过上下文 IL emit 等进行了荒谬的优化)
- 良好的跨平台支持(它实现了 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 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
序列化;如果你的数据以文本为主,它会很好地压缩。