使用 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 无法知道数据何时传输完毕,也不能够检查传输是否出错。
在 Redis 2.6 及之后的版本中,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}"))
}
我们可以直接运行这个程序,然后把它的输出通过管道喂给 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
管道模式的工作原理
redis-cli
的管道模式真神奇!它的神奇之处就在于,既能达到和 netcat
一样快速,又能同时理解 server 的上一个回复。
而这是通过下面的方式实现的:
redis-cli --pipe
试图尽可能快地发送数据到 server。- 同时,它也在尽可能地读取数据,并试图解析。
- 一旦标准输入中没有更多数据的可读取,它就会发送一个特殊的
ECHO
命令,附带一个 20 字节大小的随机字符串。这样一来,我们就可以确定这是最新发送的命令,也可以确定我们能够在收到同样的 20 字节时就开始匹配回复响应。当收到了匹配的回复,它就可以成功退出了。
有了这个技巧,我们不需要解析发送到 server 的协议来知道将发送多少条命令,只需要简单解析响应就好了。
然而,当解析响应时,我们使用了一个计数器来记录解析完成数量。这样,我们才能最终告诉用户此次批量加载的会话中发送的命令数量。
后语
读了本文,你是否学会了 Redis 批量加载数据的最佳技巧呢?在下一篇文章中,我将介绍 Redis 的另一个用例 —— 分布式锁。请保持关注喔!
参考
本文使用 CC BY-SA 4.0 国际协议 进行许可,欢迎 遵照协议规定 转载。
作者:六开箱
链接:https://lkxed.github.io/posts/redis-bulk-loading/