有時(shí)候 Redis 實(shí)例需要在短時(shí)間內(nèi)加載大量的已存在數(shù)據(jù),或者用戶(hù)產(chǎn)生的數(shù)據(jù),這樣,上百萬(wàn)的鍵將在很短的時(shí)間內(nèi)被創(chuàng)建。
這被稱(chēng)為集中插入(mass insertion),這篇文檔的目的,就是提供如何最快地向 Redis 中插入數(shù)據(jù)的一些相關(guān)信息。
使用標(biāo)準(zhǔn)的 Redis 客戶(hù)端來(lái)完成集中插入并不是一個(gè)好主意,理由是:一條一條的發(fā)送命令很慢,因?yàn)槟阈枰獮槊總€(gè)命令付出往返時(shí)間的花費(fèi)。可以使用管道(pipelining),但對(duì)于許多記錄的集中插入而言,你在讀取響應(yīng)的同時(shí)還需要寫(xiě)新命令,以確保插入盡可能快。
只有少部分的客戶(hù)端支持非阻塞 I/O,也并不是所有的客戶(hù)端都能高效地解析響應(yīng)以最大化吞吐量?;谏鲜鲞@些原因,首選的集中導(dǎo)入數(shù)據(jù)到 Redis 中的方式,是生成按照 Redis 協(xié)議的原始(raw)格式的文本文件,以調(diào)用需要的命令來(lái)插入需要的數(shù)據(jù)。
例如,如果我需要生成一個(gè)巨大的數(shù)據(jù)集,擁有數(shù)十億形式為”keyN->ValueN” 的鍵,我將創(chuàng)建一個(gè)按照 Redis 協(xié)議格式,包含如下命令的文件:
SET Key0 Value0
SET Key1 Value1
...
SET KeyN ValueN
當(dāng)這個(gè)文件被創(chuàng)建后,剩下的工作就是將其盡可能快的導(dǎo)入到 Redis 中。過(guò)去的辦法是使用 netcat 來(lái)完成,命令如下:
(cat data.txt; sleep 10) | nc localhost 6379 > /dev/null
然而,這種集中導(dǎo)入的方式并不是十分可靠,因?yàn)?netcat 并不知道所有的數(shù)據(jù)什么時(shí)候被傳輸完,并且不能檢查錯(cuò)誤。在 github 上一個(gè)不穩(wěn)定的 Redis 分支上,redis-cli 工具支持一種稱(chēng)為管道模式(pipe mode)的模式,設(shè)計(jì)用來(lái)執(zhí)行集中插入。
使用管道模式運(yùn)行命令如下:
cat data.txt | redis-cli --pipe
輸出類(lèi)似如下的內(nèi)容:
All data transferred. Waiting for the last reply...
Last reply received from server.
errors: 0, replies: 1000000
redis-cli 工具也能夠確保僅僅將來(lái)自 Redis 實(shí)例的錯(cuò)誤重定向到標(biāo)準(zhǔn)輸出。
Redis 協(xié)議非常容易生成和解析,可以參考其文檔(請(qǐng)關(guān)注后續(xù)翻譯文檔,譯者注)。但是,為了集中插入的目標(biāo)而生成協(xié)議,你不必了解協(xié)議的每一個(gè)細(xì)節(jié),僅僅需要知道每個(gè)命令通過(guò)如下方式來(lái)表示:
*<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 通過(guò)以下協(xié)議來(lái)表示:
*3<cr><lf>
$3<cr><lf>
SET<cr><lf>
$3<cr><lf>
key<cr><lf>
$5<cr><lf>
value<cr><lf>
或者表示為一個(gè)字符串:
"*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n"
為集中插入而生成的文件,就是由一條一條按照上面的方式表示的命令組成的。
下面的 Ruby 函數(shù)生成合法的協(xié)議。
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
使用上面的函數(shù),可以很容易地生成上面例子中的鍵值對(duì)。程序如下:
(0...1000).each{|n|
STDOUT.write(gen_redis_proto("SET","Key#{n}","Value#{n}"))
}
我們現(xiàn)在可以直接以 redis-cli 的管道模式來(lái)運(yùn)行這個(gè)程序,來(lái)執(zhí)行我們的第一次集中導(dǎo)入會(huì)話(huà)。
$ 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 一樣的快,并且能理解服務(wù)器同時(shí)返回的最后一條響應(yīng)。
按照以下方式獲得:
使用這個(gè)技巧,我們不需要為了知道發(fā)送了多少命令而解析發(fā)送給服務(wù)端的協(xié)議,僅僅只需要知道響應(yīng)就可以。
但是,在解析響應(yīng)的時(shí)候,我們對(duì)所有已解析響應(yīng)進(jìn)行了計(jì)數(shù),于是最后我們可以告訴用戶(hù),通過(guò)集中插入會(huì)話(huà)傳輸給服務(wù)器的命令的數(shù)。