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);
这会发送所有内容,既不等待响应也不分配不完整的 Task
s 来表示未来值。您可能想在 末尾执行类似 Ping
的操作,而无需 即发即弃,以检查服务器是否仍在与您通话。请注意,使用即发即弃意味着您不会注意到报告的任何服务器错误。
我试图在尽可能短的时间内插入大量(-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);
这会发送所有内容,既不等待响应也不分配不完整的 Task
s 来表示未来值。您可能想在 末尾执行类似 Ping
的操作,而无需 即发即弃,以检查服务器是否仍在与您通话。请注意,使用即发即弃意味着您不会注意到报告的任何服务器错误。