鍍金池/ 教程/ 大數(shù)據(jù)/ 集中插入
使用 Redis 實(shí)現(xiàn) Twitter(上)
集群(下)
使用 Redis 實(shí)現(xiàn) Twitter(下)
使用 Redis 作為 LRU 緩存
高可用(上)
高可用客戶(hù)端指引
集群(中)
高可用(下)
持久化
Redis 介紹
集中插入
集群(上)
從入門(mén)到精通(上)
從入門(mén)到精通(下)
從入門(mén)到精通(中)
分片
數(shù)據(jù)類(lèi)型初探
復(fù)制

集中插入

有時(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)信息。

使用協(xié)議,伙計(jì)

使用標(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é)議(Generating Redis Protocol)

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  

管道模式如何工作(How works)

redis-cli 管道模式的魔力,就是和 netcat 一樣的快,并且能理解服務(wù)器同時(shí)返回的最后一條響應(yīng)。

按照以下方式獲得:

  • redis-cli –pipe 嘗試盡可能快的發(fā)送數(shù)據(jù)到服務(wù)器。
  • 與此同時(shí)讀取可用數(shù)據(jù),并嘗試解析。
  • 當(dāng)標(biāo)準(zhǔn)輸入沒(méi)有數(shù)據(jù)可讀時(shí),發(fā)送一個(gè)帶有 20 字節(jié)隨機(jī)字符的特殊 ECHO 命令:我們確保這是最后發(fā)送的命令,我們也確保可以匹配響應(yīng)的檢查,如果我們收到了相同的 20 字節(jié)的批量回復(fù)(bulk reply)。
  • 一旦這個(gè)特殊的最后命令被發(fā)送,收到響應(yīng)的代碼開(kāi)始使用這 20 個(gè)字節(jié)來(lái)匹配響應(yīng)。當(dāng)匹配響應(yīng)到達(dá)后成功退出。

使用這個(gè)技巧,我們不需要為了知道發(fā)送了多少命令而解析發(fā)送給服務(wù)端的協(xié)議,僅僅只需要知道響應(yīng)就可以。

但是,在解析響應(yīng)的時(shí)候,我們對(duì)所有已解析響應(yīng)進(jìn)行了計(jì)數(shù),于是最后我們可以告訴用戶(hù),通過(guò)集中插入會(huì)話(huà)傳輸給服務(wù)器的命令的數(shù)。

上一篇:持久化下一篇:集群(上)