Redis批量数据插入
有时候Redis实例需要在短时间内加载大量之前已存在或用户产生的数据,因此数以百万计的键需要尽快被创建。
这就是所谓的批量插入,本文的目标就是提供有关如何尽可能快的满足Redis的数据要求的相关信息。
使用Luke协议
使用一个常规的Redis客户端来处理批量数据插入不是一个好主意,原因如下:原生的一次发送一条命令的方式很慢,因为你不得不等待每个命令的往返时间。也可以使用流水线,但是在批量数据插入条件下,为了尽可能快的插入数据,你需要在读取命令响应的同时发送新的命令。
只有一小部分的客户端支持不阻塞I/O,并且不是所有的客户端都能够高效的解析响应,以最大限度的提高吞吐量。正是由于这些原因,为了调用插入数据所需的命令,以原生方式生成一个包含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-cli支持一种新的模式,即pipe模式,它被设计来用于处理批量数据插入。
使用pipe模式,命令是这样执行的:
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实例里接收到的错误重定向到标准输出里。
The redis-cli utility will also make sure to only redirect errors received from the Redis instance to the standard output.
生成Redis协议
生成和解析Redis协议是极其简单的,Redis协议文档请参考https://redis.io/topics/protocol。然而为了生成处理批量数据插入的协议,你并不需要理解协议的每一个细节,只需要知道每个命令会用如下方式表示就可以了:
*<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}"))
}
我们可以在pipe中直接使用redis-cli来运行这个程序来测试我们的第一个批量数据插入会话。
$ ruby proto.rb | redis-cli --pipe
All data transferred. Waiting for the last reply...
Last reply received from server.
errors: 0, replies: 1000
pipe模式是如何工作的
在redis-cli的pipe模式里,它的处理速度和netcat一样快,与此同时,它还能知道服务器发出最后一个响应的时间。
整个过程包含了以下步骤:
- redis-cli --pipe尽可能快地向服务器发送数据。
- 与此同时,如果有响应回来,它会读取响应数据并解析。
- 一旦在标准输入里没有数据可读的时候,它会发送一个特殊的ECHO命令,后面跟着20字节的随机字符串:我们确定这是最新发送的一个命令,
并且可以通过检查当我们是否接收到了20个同样字节的响应来匹配回复。 - 一旦最后这个特殊的命令被发送出去,代码接收到的响应就开始匹配这20个字节。一旦匹配成功,程序就执行成功并退出。
使用这种策略后,为了了解我们发送了多少个命令,我们就不需要解析向服务器发送的协议了,而只需要解析响应就可以了。
在解析响应时,我们会对所以已经被解析的响应进行计数,所以我们最终可以告诉用户在这个批量插入过程中向服务器传输的命令总数。
2017-09-17