Stackexchange.Redis 中的流水线与批处理

Pipelining vs Batching in Stackexchange.Redis

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

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);

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);

我没有注意到任何显着的时间差异(实际上我希望批处理方法更快):对于大约 250K 的插入,流水线处理大约需要 7 秒,批处理大约需要 8 秒。

阅读有关流水线的文档,

"Using pipelining allows us to get both requests onto the network immediately, eliminating most of the latency. Additionally, it also helps reduce packet fragmentation: 20 requests sent individually (waiting for each response) will require at least 20 packets, but 20 requests sent in a pipeline could fit into much fewer packets (perhaps even just one)."

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

在幕后,SE.Redis 做了很多工作来尽量避免数据包碎片,因此与您的情况非常相似也就不足为奇了。批处理和扁平流水线之间的主要区别是:

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

在大多数情况下,避免批处理会做得更好,因为 SE.Redis 仅在添加工作时 自动 完成大部分工作。

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

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

这会发送所有内容,既不等待响应也不分配不完整的 Tasks 来表示未来值。您可能想在 末尾执行类似 Ping 的操作,而无需 即发即弃,以检查服务器是否仍在与您通话。请注意,使用即发即弃意味着您不会注意到报告的任何服务器错误。