批量加载
使用 Redis 协议批量写入数据
批量加载是将大量预先存在的数据加载到 Redis 的过程。理想情况下,您希望快速有效地执行此操作。本文档介绍了一些在 Redis 中批量加载数据的策略。
使用 Redis 协议进行批量加载
使用普通 Redis 客户端执行批量加载对于以下几个原因来说不是一个好主意:将一个命令一个命令发送的简单方法很慢,因为您必须为每个命令的往返时间付费。可以使用流水线,但是对于大量记录的批量加载,您需要在读取回复的同时写入新命令,以确保插入速度尽可能快。
只有很小一部分客户端支持非阻塞 I/O,并非所有客户端都能够以有效的方式解析回复,以最大程度地提高吞吐量。出于所有这些原因,将数据批量导入 Redis 的首选方法是生成一个包含 Redis 协议(以原始格式)的文本文件,以便调用插入所需数据的命令。
例如,如果我需要生成一个大型数据集,其中包含数十亿个键,形式为:`keyN -> ValueN' 我将创建一个包含以下命令的 Redis 协议格式文件
SET Key0 Value0
SET Key1 Value1
...
SET KeyN ValueN
创建此文件后,剩下的操作就是尽可能快地将其提供给 Redis。过去,这样做的方法是使用 `netcat` 以及以下命令
(cat data.txt; sleep 10) | nc localhost 6379 > /dev/null
但是,这并不是一种非常可靠的大规模导入方法,因为 netcat 实际上并不知道所有数据何时传输完毕,并且无法检查错误。在 2.6 或更高版本的 Redis 中,redis-cli
实用程序支持一种名为 **管道模式** 的新模式,该模式旨在执行批量加载。
使用管道模式,要运行的命令如下所示:
cat data.txt | redis-cli --pipe
这将产生类似于以下内容的输出:
All data transferred. Waiting for the last reply...
Last reply received from server.
errors: 0, replies: 1000000
redis-cli
实用程序还将确保仅将从 Redis 实例接收到的错误重定向到标准输出。
生成 Redis 协议
Redis 协议非常容易生成和解析,并且已在此处记录。但是,为了生成批量加载的目标协议,您不需要了解协议的每一个细节,只需了解每个命令都以以下方式表示即可:
*<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}"))
}
我们可以直接将程序运行到管道中以执行第一个大规模导入会话。
$ ruby proto.rb | redis-cli --pipe
All data transferred. Waiting for the last reply...
Last reply received from server.
errors: 0, replies: 1000
管道模式在幕后是如何工作的
redis-cli --pipe
中所需的魔法是尽可能快地像 netcat 一样,同时仍然能够理解服务器何时发送了最后一个回复。
这通过以下方式实现:
redis-cli --pipe
尝试尽快将数据发送到服务器。- 同时,它在可用时读取数据,尝试对其进行解析。
- 一旦没有更多数据可以从标准输入读取,它会发送一个带有随机 20 字节字符串的特殊 **ECHO** 命令:我们确定这是发送的最后一个命令,并且我们确定可以匹配回复,检查我们是否收到与 20 字节相同的批量回复。
- 发送这个特殊的最终命令后,接收回复的代码开始将回复与这 20 字节进行匹配。当找到匹配的回复时,它可以成功退出。
使用这种技巧,我们不需要解析发送到服务器的协议来了解我们发送了多少命令,而只需要解析回复。
但是,在解析回复时,我们记录了所有已解析回复的计数器,以便最后能够告诉用户在大规模插入会话中传输到服务器的命令数量。