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

集中插入

有時候 Redis 實例需要在短時間內(nèi)加載大量的已存在數(shù)據(jù),或者用戶產(chǎn)生的數(shù)據(jù),這樣,上百萬的鍵將在很短的時間內(nèi)被創(chuàng)建。

這被稱為集中插入(mass insertion),這篇文檔的目的,就是提供如何最快地向 Redis 中插入數(shù)據(jù)的一些相關(guān)信息。

使用協(xié)議,伙計

使用標(biāo)準(zhǔn)的 Redis 客戶端來完成集中插入并不是一個好主意,理由是:一條一條的發(fā)送命令很慢,因為你需要為每個命令付出往返時間的花費。可以使用管道(pipelining),但對于許多記錄的集中插入而言,你在讀取響應(yīng)的同時還需要寫新命令,以確保插入盡可能快。

只有少部分的客戶端支持非阻塞 I/O,也并不是所有的客戶端都能高效地解析響應(yīng)以最大化吞吐量?;谏鲜鲞@些原因,首選的集中導(dǎo)入數(shù)據(jù)到 Redis 中的方式,是生成按照 Redis 協(xié)議的原始(raw)格式的文本文件,以調(diào)用需要的命令來插入需要的數(shù)據(jù)。

例如,如果我需要生成一個巨大的數(shù)據(jù)集,擁有數(shù)十億形式為”keyN->ValueN” 的鍵,我將創(chuàng)建一個按照 Redis 協(xié)議格式,包含如下命令的文件:

SET Key0 Value0  
SET Key1 Value1  
...  
SET KeyN ValueN  

當(dāng)這個文件被創(chuàng)建后,剩下的工作就是將其盡可能快的導(dǎo)入到 Redis 中。過去的辦法是使用 netcat 來完成,命令如下:

(cat data.txt; sleep 10) | nc localhost 6379 > /dev/null  

然而,這種集中導(dǎo)入的方式并不是十分可靠,因為 netcat 并不知道所有的數(shù)據(jù)什么時候被傳輸完,并且不能檢查錯誤。在 github 上一個不穩(wěn)定的 Redis 分支上,redis-cli 工具支持一種稱為管道模式(pipe mode)的模式,設(shè)計用來執(zhí)行集中插入。

使用管道模式運行命令如下:

cat data.txt | redis-cli --pipe  

輸出類似如下的內(nèi)容:

All data transferred. Waiting for the last reply...  
Last reply received from server.  
errors: 0, replies: 1000000  

redis-cli 工具也能夠確保僅僅將來自 Redis 實例的錯誤重定向到標(biāo)準(zhǔn)輸出。

生成 Redis 協(xié)議(Generating Redis Protocol)

Redis 協(xié)議非常容易生成和解析,可以參考其文檔(請關(guān)注后續(xù)翻譯文檔,譯者注)。但是,為了集中插入的目標(biāo)而生成協(xié)議,你不必了解協(xié)議的每一個細(xì)節(jié),僅僅需要知道每個命令通過如下方式來表示:

*<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 通過以下協(xié)議來表示:

*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 函數(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ù),可以很容易地生成上面例子中的鍵值對。程序如下:

(0...1000).each{|n|  
STDOUT.write(gen_redis_proto("SET","Key#{n}","Value#{n}"))  
}  

我們現(xiàn)在可以直接以 redis-cli 的管道模式來運行這個程序,來執(zhí)行我們的第一次集中導(dǎo)入會話。

$ 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ù)器同時返回的最后一條響應(yīng)。

按照以下方式獲得:

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

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

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

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