批量加载是将大量现有数据载入 Valkey 的过程。理想情况下,您希望快速高效地执行此操作。本文档介绍了一些在 Valkey 中批量加载数据的策略。
使用 RESP 协议进行批量加载
使用普通的 Valkey 客户端进行批量加载并非一个好主意,原因如下:朴素地一个接一个发送命令的方法很慢,因为您必须为每个命令支付往返时间。虽然可以使用管道(pipelining),但对于大量记录的批量加载,您需要同时写入新命令并读取回复,以确保尽可能快地插入数据。
只有一小部分客户端支持非阻塞 I/O,并且并非所有客户端都能以高效的方式解析回复以最大限度地提高吞吐量。鉴于所有这些原因,将数据批量导入 Valkey 的首选方法是生成一个包含 Valkey 协议的文本文件,该文件采用原始格式,以便调用插入所需数据的命令。
例如,如果我需要生成一个包含数十亿个形如 `keyN -> ValueN` 的键的大型数据集,我将创建一个文件,其中包含以下 Valkey 协议格式的命令
SET Key0 Value0
SET Key1 Value1
...
SET KeyN ValueN
一旦此文件创建完成,接下来的操作就是尽快将其输入到 Valkey 中。过去,实现此目的的方法是使用 `netcat` 和以下命令
(cat data.txt; sleep 10) | nc localhost 6379 > /dev/null
然而,这并非一种非常可靠的批量导入方式,因为 netcat 无法真正知道所有数据何时传输完毕,也无法检查错误。`valkey-cli` 工具支持一种专为批量加载设计的管道模式。
使用管道模式,运行命令如下所示
cat data.txt | valkey-cli --pipe
这将产生类似以下的输出
All data transferred. Waiting for the last reply...
Last reply received from server.
errors: 0, replies: 1000000
valkey-cli 工具还会确保只将从 Valkey 实例接收到的错误重定向到标准输出。
生成 RESP 协议
RESP 协议非常易于生成和解析,并在此处有文档说明。然而,为了批量加载的目的生成协议,您无需了解协议的每个细节,只需知道每个命令都以下列方式表示即可
*<args><cr><lf>
$<len><cr><lf>
<arg0><cr><lf>
<arg1><cr><lf>
...
<argN><cr><lf>
其中 <cr>
表示 "\r"(或 ASCII 字符 13),<lf>
表示 "\n"(或 ASCII 字符 10)。
例如,命令 SET key value 由以下协议表示
*3<cr><lf>
$3<cr><lf>
SET<cr><lf>
$3<cr><lf>
key<cr><lf>
$5<cr><lf>
value<cr><lf>
或表示为带引号的字符串
"*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n"
您需要为批量加载生成的文件仅由以上述方式表示的命令组成,命令一个接一个排列。
以下 Ruby 函数生成有效的协议
def gen_redis_proto(*cmd)
proto = ""
proto << "*"+cmd.length.to_s+"\r\n"
cmd.each{|arg|
proto << "$"+arg.to_s.bytesize.to_s+"\r\n"
proto << arg.to_s+"\r\n"
}
proto
end
puts gen_redis_proto("SET","mykey","Hello World!").inspect
使用上述函数,可以通过此程序轻松生成上述示例中的键值对
(0...1000).each{|n|
STDOUT.write(gen_redis_proto("SET","Key#{n}","Value#{n}"))
}
我们可以直接通过管道将程序运行到 valkey-cli 中,以执行我们的第一次批量导入会话。
$ ruby proto.rb | valkey-cli --pipe
All data transferred. Waiting for the last reply...
Last reply received from server.
errors: 0, replies: 1000
管道模式的工作原理
valkey-cli 管道模式的奥秘在于它能像 netcat 一样快速,同时仍能理解服务器何时发送了最后一条回复。
这通过以下方式实现
- valkey-cli --pipe 尝试以最快速度将数据发送到服务器。
- 同时,它会在数据可用时读取并尝试解析它。
- 一旦标准输入中没有更多数据可读,它会发送一个特殊的 ECHO 命令,带有一个随机的 20 字节字符串:我们确信这是发送的最后一个命令,并且我们确信可以通过检查是否收到相同的 20 字节作为批量回复来匹配回复。
- 一旦发送此特殊的最终命令,接收回复的代码就会开始将回复与这 20 字节进行匹配。当匹配的回复到达时,它就可以成功退出。
使用这个技巧,我们无需解析发送到服务器的协议来了解我们正在发送多少命令,只需解析回复即可。
然而,在解析回复时,我们会对所有已解析的回复进行计数,以便最终能够告知用户通过批量插入会话传输到服务器的命令数量。