是什么使Redis顯得這么特別?Redis具體能解決什么類型的問題?要實(shí)際應(yīng)用Redis,開發(fā)者必須儲(chǔ)備什么知識(shí)?在我們能回答這么一些問題之前,我們需要明白R(shí)edis到底是什么。
Redis通常被人們認(rèn)為是一種持久化的存儲(chǔ)器關(guān)鍵字-值型存儲(chǔ)(in-memory persistent key-value store)。我認(rèn)為這種對(duì)Redis的描述并不太準(zhǔn)確。Redis的確是將所有的數(shù)據(jù)存放于存儲(chǔ)器(更多是是按位存儲(chǔ)),而且也確實(shí)通過將數(shù)據(jù)寫入磁盤來(lái)實(shí)現(xiàn)持久化,但是Redis的實(shí)際意義比單純的關(guān)鍵字-值型存儲(chǔ)要來(lái)得深遠(yuǎn)。糾正腦海里的這種誤解觀點(diǎn)非常關(guān)鍵,否則你對(duì)于Redis之道以及其應(yīng)用的洞察力就會(huì)變得越發(fā)狹義。
事實(shí)是,Redis引入了5種不同的數(shù)據(jù)結(jié)構(gòu),只有一個(gè)是典型的關(guān)鍵字-值型結(jié)構(gòu)。理解Redis的關(guān)鍵就在于搞清楚這5種數(shù)據(jù)結(jié)構(gòu),其工作的原理都是如何,有什么關(guān)聯(lián)方法以及你能怎樣應(yīng)用這些數(shù)據(jù)結(jié)構(gòu)去構(gòu)建模型。首先,讓我們來(lái)弄明白這些數(shù)據(jù)結(jié)構(gòu)的實(shí)際意義。
應(yīng)用上面提及的數(shù)據(jù)結(jié)構(gòu)概念到我們熟悉的關(guān)系型數(shù)據(jù)庫(kù)里,我們可以認(rèn)為其引入了一個(gè)單獨(dú)的數(shù)據(jù)結(jié)構(gòu)——表格。表格既復(fù)雜又靈活,基于表格的存儲(chǔ)和管理,沒有多少東西是你不能進(jìn)行建模的。然而,這種通用性并不是沒有缺點(diǎn)。具體來(lái)說(shuō)就是,事情并不是總能達(dá)到假設(shè)中的簡(jiǎn)單或者快速。相對(duì)于這種普遍適用(one-size-fits-all)的結(jié)構(gòu)體系,我們可以使用更為專門化的結(jié)構(gòu)體系。當(dāng)然,因此可能有些事情我們會(huì)完成不了(至少,達(dá)不到很好的程度)。但話說(shuō)回來(lái),這樣做就能確定我們可以獲得想象中的簡(jiǎn)單性和速度嗎?
針對(duì)特定類型的問題使用特定的數(shù)據(jù)結(jié)構(gòu)?我們不就是這樣進(jìn)行編程的嗎?你不會(huì)使用一個(gè)散列表去存儲(chǔ)每份數(shù)據(jù),也不會(huì)使用一個(gè)標(biāo)量變量去存儲(chǔ)。對(duì)我來(lái)說(shuō),這正是Redis的做法。如果你需要處理標(biāo)量、列表、散列或者集合,為什么不直接就用標(biāo)量、列表、散列和集合去存儲(chǔ)他們?為什么不是直接調(diào)用exists(key)
去檢測(cè)一個(gè)已存在的值,而是要調(diào)用其他比O(1)(常量時(shí)間查找,不會(huì)因?yàn)榇幚碓氐脑鲩L(zhǎng)而變慢)慢的操作?
與你熟悉的關(guān)系型數(shù)據(jù)庫(kù)一致,Redis有著相同的數(shù)據(jù)庫(kù)基本概念,即一個(gè)數(shù)據(jù)庫(kù)包含一組數(shù)據(jù)。典型的數(shù)據(jù)庫(kù)應(yīng)用案例是,將一個(gè)程序的所有數(shù)據(jù)組織起來(lái),使之與另一個(gè)程序的數(shù)據(jù)保持獨(dú)立。
在Redis里,數(shù)據(jù)庫(kù)簡(jiǎn)單的使用一個(gè)數(shù)字編號(hào)來(lái)進(jìn)行辨認(rèn),默認(rèn)數(shù)據(jù)庫(kù)的數(shù)字編號(hào)是0
。如果你想切換到一個(gè)不同的數(shù)據(jù)庫(kù),你可以使用select
命令來(lái)實(shí)現(xiàn)。在命令行界面里鍵入select 1
,Redis應(yīng)該會(huì)回復(fù)一條OK
的信息,然后命令行界面里的提示符會(huì)變成類似redis 127.0.0.1:6379[1]>
這樣。如果你想切換回默認(rèn)數(shù)據(jù)庫(kù),只要在命令行界面鍵入select 0
即可。
Redis不僅僅是一種簡(jiǎn)單的關(guān)鍵字-值型存儲(chǔ),從其核心概念來(lái)看,Redis的5種數(shù)據(jù)結(jié)構(gòu)中的每一個(gè)都至少有一個(gè)關(guān)鍵字和一個(gè)值。在轉(zhuǎn)入其它關(guān)于Redis的有用信息之前,我們必須理解關(guān)鍵字和值的概念。
關(guān)鍵字(Keys)是用來(lái)標(biāo)識(shí)數(shù)據(jù)塊。我們將會(huì)很常跟關(guān)鍵字打交道,不過在現(xiàn)在,明白關(guān)鍵字就是類似于users:leto
這樣的表述就足夠了。一般都能很好地理解到,這樣關(guān)鍵字包含的信息是一個(gè)名為leto
的用戶。這個(gè)關(guān)鍵字里的冒號(hào)沒有任何特殊含義,對(duì)于Redis而言,使用分隔符來(lái)組織關(guān)鍵字是很常見的方法。
值(Values)是關(guān)聯(lián)于關(guān)鍵字的實(shí)際值,可以是任何東西。有時(shí)候你會(huì)存儲(chǔ)字符串,有時(shí)候是整數(shù),還有時(shí)候你會(huì)存儲(chǔ)序列化對(duì)象(使用JSON、XML或其他格式)。在大多數(shù)情況下,Redis會(huì)把值看做是一個(gè)字節(jié)序列,而不會(huì)關(guān)注它們實(shí)質(zhì)上是什么。要注意,不同的Redis載體處理序列化會(huì)有所不同(一些會(huì)讓你自己決定)。因此,在這本書里,我們將僅討論字符串、整數(shù)和JSON。
現(xiàn)在讓我們活動(dòng)一下手指吧。在命令行界面鍵入下面的命令:
set users:leto "{name: leto, planet: dune, likes: [spice]}"
這就是Redis命令的基本構(gòu)成。首先我們要有一個(gè)確定的命令,在上面的語(yǔ)句里就是set
。然后就是相應(yīng)的參數(shù),set
命令接受兩個(gè)參數(shù),包括要設(shè)置的關(guān)鍵字,以及相應(yīng)要設(shè)置的值。很多的情況是,命令接受一個(gè)關(guān)鍵字(當(dāng)這種情況出現(xiàn),其經(jīng)常是第一個(gè)參數(shù))。你能想到如何去獲取這個(gè)值嗎?我想你會(huì)說(shuō)(當(dāng)然一時(shí)拿不準(zhǔn)也沒什么):
get users:leto
關(guān)鍵字和值的是Redis的基本概念,而get
和set
命令是對(duì)此最簡(jiǎn)單的使用。你可以創(chuàng)建更多的用戶,去嘗試不同類型的關(guān)鍵字以及不同的值,看看一些不同的組合。
隨著學(xué)習(xí)的持續(xù)深入,兩件事情將變得清晰起來(lái)。對(duì)于Redis而言,關(guān)鍵字就是一切,而值是沒有任何意義。更通俗來(lái)看就是,Redis不允許你通過值來(lái)進(jìn)行查詢?;氐缴厦娴睦?,我們就不能查詢生活在dune
行星上的用戶。
對(duì)許多人來(lái)說(shuō),這會(huì)引起一些擔(dān)憂。在我們生活的世界里,數(shù)據(jù)查詢是如此的靈活和強(qiáng)大,而Redis的方式看起來(lái)是這么的原始和不高效。不要讓這些擾亂你太久。要記住,Redis不是一種普遍使用(one-size-fits-all)的解決方案,確實(shí)存在這么一些事情是不應(yīng)該由Redis來(lái)解決的(因?yàn)槠洳樵兊南拗疲?。事?shí)上,在考慮了這些情況后,你會(huì)找到新的方法去構(gòu)建你的數(shù)據(jù)。
很快,我們就能看到更多實(shí)際的用例。很重要的一點(diǎn)是,我們要明白關(guān)于Redis的這些基本事實(shí)。這能幫助我們弄清楚為什么值可以是任何東西,因?yàn)镽edis從來(lái)不需要去讀取或理解它們。而且,這也可以幫助我們理清思路,然后去思考如何在這個(gè)新世界里建立模型。
我們之前提及過,Redis是一種持久化的存儲(chǔ)器內(nèi)存儲(chǔ)(in-memory persistent store)。對(duì)于持久化,默認(rèn)情況下,Redis會(huì)根據(jù)已變更的關(guān)鍵字?jǐn)?shù)量來(lái)進(jìn)行判斷,然后在磁盤里創(chuàng)建數(shù)據(jù)庫(kù)的快照(snapshot)。你可以對(duì)此進(jìn)行設(shè)置,如果X個(gè)關(guān)鍵字已變更,那么每隔Y秒存儲(chǔ)數(shù)據(jù)庫(kù)一次。默認(rèn)情況下,如果1000個(gè)或更多的關(guān)鍵字已變更,Redis會(huì)每隔60秒存儲(chǔ)數(shù)據(jù)庫(kù);而如果9個(gè)或更少的關(guān)鍵字已變更,Redis會(huì)每隔15分鐘存儲(chǔ)數(shù)據(jù)庫(kù)。
除了創(chuàng)建磁盤快照外,Redis可以在附加模式下運(yùn)行。任何時(shí)候,如果有一個(gè)關(guān)鍵字變更,一個(gè)單一附加(append-only)的文件會(huì)在磁盤里進(jìn)行更新。在一些情況里,雖然硬件或軟件可能發(fā)生錯(cuò)誤,但用那60秒有效數(shù)據(jù)存儲(chǔ)去換取更好性能是可以接受的。而在另一些情況里,這種損失就難以讓人接受,Redis為你提供了選擇。在第5章里,我們將會(huì)看到第三種選擇,其將持久化任務(wù)減荷到一個(gè)從屬數(shù)據(jù)庫(kù)里。
至于存儲(chǔ)器,Redis會(huì)將所有數(shù)據(jù)都保留在存儲(chǔ)器中。顯而易見,運(yùn)行Redis具有不低的成本:因?yàn)镽AM仍然是最昂貴的服務(wù)器硬件部件。
我很清楚有一些開發(fā)者對(duì)即使是一點(diǎn)點(diǎn)的數(shù)據(jù)空間都是那么的敏感。一本《威廉·莎士比亞全集》需要近5.5MB的存儲(chǔ)空間。對(duì)于縮放的需求,其它的解決方案趨向于IO-bound或者CPU-bound。這些限制(RAM或者IO)將會(huì)需要你去理解更多機(jī)器實(shí)際依賴的數(shù)據(jù)類型,以及應(yīng)該如何去進(jìn)行存儲(chǔ)和查詢。除非你是存儲(chǔ)大容量的多媒體文件到Redis中,否則存儲(chǔ)器內(nèi)存儲(chǔ)應(yīng)該不會(huì)是一個(gè)問題。如果這對(duì)于一個(gè)程序是個(gè)問題,你就很可能不會(huì)用IO-bound的解決方案。
Redis有虛擬存儲(chǔ)器的支持。然而,這個(gè)功能已經(jīng)被認(rèn)為是失敗的了(通過Redis的開發(fā)者),而且它的使用已經(jīng)被廢棄了。
(從另一個(gè)角度來(lái)看,一本5.5MB的《威廉·莎士比亞全集》可以通過壓縮減小到近2MB。當(dāng)然,Redis不會(huì)自動(dòng)對(duì)值進(jìn)行壓縮,但是因?yàn)槠鋵⑺兄刀伎醋魇亲止?jié),沒有什么限制讓你不能對(duì)數(shù)據(jù)進(jìn)行壓縮/解壓,通過犧牲處理時(shí)間來(lái)?yè)Q取存儲(chǔ)空間。)
我們已經(jīng)接觸了好幾個(gè)高層次的主題。在繼續(xù)深入Redis之前,我想做的最后一件事情是將這些主題整合起來(lái)。這些主題包括,查詢的限制,數(shù)據(jù)結(jié)構(gòu)以及Redis在存儲(chǔ)器內(nèi)存儲(chǔ)數(shù)據(jù)的方法。
當(dāng)你將這3個(gè)主題整合起來(lái),你最終會(huì)得出一個(gè)絕妙的結(jié)論:速度。一些人可能會(huì)想,當(dāng)然Redis會(huì)很快速,要知道所以的東西都在存儲(chǔ)器里。但這僅僅是其中的一部分,讓Redis閃耀的真正原因是其不同于其它解決方案的特殊數(shù)據(jù)結(jié)構(gòu)。
能有多快速?這依賴于很多東西,包括你正在使用著哪個(gè)命令,數(shù)據(jù)的類型等等。但Redis的性能測(cè)試是趨向于數(shù)萬(wàn)或數(shù)十萬(wàn)次操作每秒。你可以通過運(yùn)行redis-benchmark
(就在redis-server
和redis-cli
的同一個(gè)文件夾里)來(lái)進(jìn)行測(cè)試。
我曾經(jīng)試過將一組使用傳統(tǒng)模型的代碼轉(zhuǎn)向使用Redis。在傳統(tǒng)模型里,運(yùn)行一個(gè)我寫的載入測(cè)試,需要超過5分鐘的時(shí)間來(lái)完成。而在Redis里,只需要150毫秒就完成了。你不會(huì)總能得到這么好的收獲,但希望這能讓你對(duì)我們所談的東西有更清晰的理解。
理解Redis的這個(gè)特性很重要,因?yàn)檫@將影響到你如何去與Redis進(jìn)行交互。擁有SQL背景的程序員通常會(huì)致力于讓數(shù)據(jù)庫(kù)的數(shù)據(jù)往返次數(shù)減至最小。這對(duì)于任何系統(tǒng)都是個(gè)好建議,包括Redis。然而,考慮到我們是在處理比較簡(jiǎn)單的數(shù)據(jù)結(jié)構(gòu),有時(shí)候我們還是需要與Redis服務(wù)器頻繁交互,以達(dá)到我們的目的。剛開始的時(shí)候,可能會(huì)對(duì)這種數(shù)據(jù)訪問模式感到不太自然。實(shí)際上,相對(duì)于我們通過Redis獲得的高性能而言,這僅僅是微不足道的損失。
雖然我們只接觸和擺弄了Redis的冰山一角,但我們討論的主題已然覆蓋了很大范圍內(nèi)的東西。如果覺得有些事情還是不太清楚(例如查詢),不用為此而擔(dān)心,在下一章我們將會(huì)繼續(xù)深入探討,希望你的問題都能得到解答。
這一章的要點(diǎn)包括:
關(guān)鍵字(Keys)是用于標(biāo)識(shí)一段數(shù)據(jù)的一個(gè)字符串
值(Values)是一段任意的字節(jié)序列,Redis不會(huì)關(guān)注它們實(shí)質(zhì)上是什么
Redis展示了(也實(shí)現(xiàn)了)5種專門的數(shù)據(jù)結(jié)構(gòu)
更多建議: