https://redis.io/topics/mass-insert

有时,Redis 实例需要在短时间内加载大量预先存在或用户生成的数据,以便尽快创建数百万个键。
这称为批量插入,本文档的目标是提供有关如何尽快向 Redis 提供数据的信息。

使用协议,Luke

使用普通的 Redis 客户端执行批量插入并不是一个好主意,原因有以下几个:一个接一个发送命令的天真方法很慢,因为您必须为每个命令的往返时间付费。可以使用流水线,但是对于大量插入许多记录,您需要在读回复的同时编写新命令,以确保尽可能快地插入。
只有一小部分客户端支持非阻塞 I/O,并且并非所有客户端都能够以有效的方式解析回复以最大化吞吐量。由于所有这些原因,将数据批量导入 Redis 的首选方法是以原始格式生成包含 Redis 协议的文本文件,以便调用插入所需数据所需的命令。( 使用协议文本+pipeline)
例如,如果我需要生成一个包含数十亿个键的大型数据集:`keyN -> ValueN’ 我将创建一个文件,其中包含以下 Redis 协议格式的命令

  1. SET Key0 Value0
  2. SET Key1 Value1
  3. ...
  4. SET KeyN ValueN

创建此文件后,剩下的操作就是尽快将其提供给 Redis。过去,这样做的方法是将netcat与以下命令一起使用 :

  1. (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>

其中表示“\r”(或 ASCII 字符 13),表示“\n”(或 ASCII 字符 10)。
例如命令SET 键值由以下协议表示:

*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 一样快,同时仍然能够理解服务器发送的最后一个回复是什么时候。
这是通过以下方式获得的:

  • redis-cli —pipe 尝试尽可能快地将数据发送到服务器。
  • 同时它在可用时读取数据,尝试解析它。
  • 一旦没有更多数据要从 stdin 读取,它会发送一个 带有随机 20 字节字符串的特殊ECHO命令:我们确定这是发送的最新命令,并且我们确定如果我们收到相同的 20字节作为批量回复。
  • 一旦发送了这个特殊的最终命令,接收回复的代码就开始匹配这 20 个字节的回复。当达到匹配的回复时,它可以成功退出。

使用这个技巧,我们不需要解析我们发送到服务器的协议来了解我们发送了多少命令,而只是回复。
然而,在解析回复时,我们对解析的所有回复进行计数,以便最后我们能够告诉用户通过批量插入会话传输到服务器的命令数量。