在Stackexchange.Redis中管道传输与批处理

Cyb*_*ude 21 optimization redis stackexchange.redis

我试图在尽可能短的时间内插入大量( - )数量的元素,我尝试了这两种选择:

1)流水线:

List<Task> addTasks = new List<Task>();
for (int i = 0; i < table.Rows.Count; i++)
{
    DataRow row = table.Rows[i];
    Task<bool> addAsync = redisDB.SetAddAsync(string.Format(keyFormat, row.Field<int>("Id")), row.Field<int>("Value"));
    addTasks.Add(addAsync);
}
Task[] tasks = addTasks.ToArray();
Task.WaitAll(tasks);
Run Code Online (Sandbox Code Playgroud)

2)配料:

List<Task> addTasks = new List<Task>();
IBatch batch = redisDB.CreateBatch();
for (int i = 0; i < table.Rows.Count; i++)
{
    DataRow row = table.Rows[i];
    Task<bool> addAsync = batch.SetAddAsync(string.Format(keyFormat, row.Field<int>("Id")), row.Field<int>("Value"));
    addTasks.Add(addAsync);
}
batch.Execute();
Task[] tasks = addTasks.ToArray();
Task.WaitAll(tasks);
Run Code Online (Sandbox Code Playgroud)

我没有注意到任何显着的时间差异(实际上我预计批处理方法会更快):对于大约250K的插入,我得到流水线约7秒,批量约8秒.

阅读有关流水线的文档,

"使用流水线操作允许我们立即将两个请求都发送到网络上,消除了大部分延迟.此外,它还有助于减少数据包碎片:单独发送的20个请求(等待每个响应)将需要至少20个数据包,但发送了20个请求在管道中可以容纳更少的数据包(甚至可能只有一个)."

对我来说,这听起来很像一个批处理行为.我想知道幕后是否有两者之间有很大的区别,因为在一个简单的检查中,procmon我看到TCP Send两个版本上几乎相同数量的s.

Mar*_*ell 27

在幕后,SE.Redis尝试避免数据包碎片做了相当多的工作,所以在你的情况下它非常相似并不奇怪.批处理和扁平流水线之间的主要区别是:

  • 批处理永远不会与同一多路复用器上的竞争操作交错(尽管它可能在服务器上交错;为了避免你需要使用multi/ exectransaction或Lua脚本)
  • 批处理将始终避免小数据包的可能性,因为它提前知道所有数据
  • 但同时,必须在发送任何内容之前完成整个批处理,因此这需要更多的内存缓冲并可能人为地引入延迟

在大多数情况下,您可以通过避免批处理来做得更好,因为SE.Redis 只需添加工作即可实现自动执行的大部分操作.

作为最后的说明; 如果你想避免本地开销,最后一种方法可能是:

redisDB.SetAdd(string.Format(keyFormat, row.Field<int>("Id")),
    row.Field<int>("Value"), flags: CommandFlags.FireAndForget);
Run Code Online (Sandbox Code Playgroud)

这会将所有内容发送到网络中,既不等待响应也不分配不完整的Tasks来表示未来的值.你可能想要做一些像Ping最后一样的事情而不是一劳永逸,检查服务器是否还在和你说话.请注意,使用"即发即弃"确实意味着您不会注意到报告的任何服务器错误.