鍍金池/ 教程/ 大數(shù)據(jù)/ 基礎(chǔ)知識
數(shù)據(jù)結(jié)構(gòu)
超越數(shù)據(jù)結(jié)構(gòu)
基礎(chǔ)知識
入門
管理
使用數(shù)據(jù)結(jié)構(gòu)

基礎(chǔ)知識

是什么使Redis顯得這么特別?Redis具體能解決什么類型的問題?要實際應(yīng)用Redis,開發(fā)者必須儲備什么知識?在我們能回答這么一些問題之前,我們需要明白Redis到底是什么。

Redis通常被人們認為是一種持久化的存儲器關(guān)鍵字-值型存儲(in-memory persistent key-value store)。我認為這種對Redis的描述并不太準確。Redis的確是將所有的數(shù)據(jù)存放于存儲器(更多是是按位存儲),而且也確實通過將數(shù)據(jù)寫入磁盤來實現(xiàn)持久化,但是Redis的實際意義比單純的關(guān)鍵字-值型存儲要來得深遠。糾正腦海里的這種誤解觀點非常關(guān)鍵,否則你對于Redis之道以及其應(yīng)用的洞察力就會變得越發(fā)狹義。

事實是,Redis引入了5種不同的數(shù)據(jù)結(jié)構(gòu),只有一個是典型的關(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)建模型。首先,讓我們來弄明白這些數(shù)據(jù)結(jié)構(gòu)的實際意義。

應(yīng)用上面提及的數(shù)據(jù)結(jié)構(gòu)概念到我們熟悉的關(guān)系型數(shù)據(jù)庫里,我們可以認為其引入了一個單獨的數(shù)據(jù)結(jié)構(gòu)——表格。表格既復(fù)雜又靈活,基于表格的存儲和管理,沒有多少東西是你不能進行建模的。然而,這種通用性并不是沒有缺點。具體來說就是,事情并不是總能達到假設(shè)中的簡單或者快速。相對于這種普遍適用(one-size-fits-all)的結(jié)構(gòu)體系,我們可以使用更為專門化的結(jié)構(gòu)體系。當(dāng)然,因此可能有些事情我們會完成不了(至少,達不到很好的程度)。但話說回來,這樣做就能確定我們可以獲得想象中的簡單性和速度嗎?

針對特定類型的問題使用特定的數(shù)據(jù)結(jié)構(gòu)?我們不就是這樣進行編程的嗎?你不會使用一個散列表去存儲每份數(shù)據(jù),也不會使用一個標量變量去存儲。對我來說,這正是Redis的做法。如果你需要處理標量、列表、散列或者集合,為什么不直接就用標量、列表、散列和集合去存儲他們?為什么不是直接調(diào)用exists(key)去檢測一個已存在的值,而是要調(diào)用其他比O(1)(常量時間查找,不會因為待處理元素的增長而變慢)慢的操作?

數(shù)據(jù)庫(Databases)

與你熟悉的關(guān)系型數(shù)據(jù)庫一致,Redis有著相同的數(shù)據(jù)庫基本概念,即一個數(shù)據(jù)庫包含一組數(shù)據(jù)。典型的數(shù)據(jù)庫應(yīng)用案例是,將一個程序的所有數(shù)據(jù)組織起來,使之與另一個程序的數(shù)據(jù)保持獨立。

在Redis里,數(shù)據(jù)庫簡單的使用一個數(shù)字編號來進行辨認,默認數(shù)據(jù)庫的數(shù)字編號是0。如果你想切換到一個不同的數(shù)據(jù)庫,你可以使用select命令來實現(xiàn)。在命令行界面里鍵入select 1,Redis應(yīng)該會回復(fù)一條OK的信息,然后命令行界面里的提示符會變成類似redis 127.0.0.1:6379[1]>這樣。如果你想切換回默認數(shù)據(jù)庫,只要在命令行界面鍵入select 0即可。

命令、關(guān)鍵字和值(Commands, Keys and Values)

Redis不僅僅是一種簡單的關(guān)鍵字-值型存儲,從其核心概念來看,Redis的5種數(shù)據(jù)結(jié)構(gòu)中的每一個都至少有一個關(guān)鍵字和一個值。在轉(zhuǎn)入其它關(guān)于Redis的有用信息之前,我們必須理解關(guān)鍵字和值的概念。

關(guān)鍵字(Keys)是用來標識數(shù)據(jù)塊。我們將會很常跟關(guān)鍵字打交道,不過在現(xiàn)在,明白關(guān)鍵字就是類似于users:leto這樣的表述就足夠了。一般都能很好地理解到,這樣關(guān)鍵字包含的信息是一個名為leto的用戶。這個關(guān)鍵字里的冒號沒有任何特殊含義,對于Redis而言,使用分隔符來組織關(guān)鍵字是很常見的方法。

值(Values)是關(guān)聯(lián)于關(guān)鍵字的實際值,可以是任何東西。有時候你會存儲字符串,有時候是整數(shù),還有時候你會存儲序列化對象(使用JSON、XML或其他格式)。在大多數(shù)情況下,Redis會把值看做是一個字節(jié)序列,而不會關(guān)注它們實質(zhì)上是什么。要注意,不同的Redis載體處理序列化會有所不同(一些會讓你自己決定)。因此,在這本書里,我們將僅討論字符串、整數(shù)和JSON。

現(xiàn)在讓我們活動一下手指吧。在命令行界面鍵入下面的命令:

set users:leto "{name: leto, planet: dune, likes: [spice]}"

這就是Redis命令的基本構(gòu)成。首先我們要有一個確定的命令,在上面的語句里就是set。然后就是相應(yīng)的參數(shù),set命令接受兩個參數(shù),包括要設(shè)置的關(guān)鍵字,以及相應(yīng)要設(shè)置的值。很多的情況是,命令接受一個關(guān)鍵字(當(dāng)這種情況出現(xiàn),其經(jīng)常是第一個參數(shù))。你能想到如何去獲取這個值嗎?我想你會說(當(dāng)然一時拿不準也沒什么):

get users:leto

關(guān)鍵字和值的是Redis的基本概念,而getset命令是對此最簡單的使用。你可以創(chuàng)建更多的用戶,去嘗試不同類型的關(guān)鍵字以及不同的值,看看一些不同的組合。

查詢(Querying)

隨著學(xué)習(xí)的持續(xù)深入,兩件事情將變得清晰起來。對于Redis而言,關(guān)鍵字就是一切,而值是沒有任何意義。更通俗來看就是,Redis不允許你通過值來進行查詢?;氐缴厦娴睦樱覀兙筒荒懿樵兩钤?code>dune行星上的用戶。

對許多人來說,這會引起一些擔(dān)憂。在我們生活的世界里,數(shù)據(jù)查詢是如此的靈活和強大,而Redis的方式看起來是這么的原始和不高效。不要讓這些擾亂你太久。要記住,Redis不是一種普遍使用(one-size-fits-all)的解決方案,確實存在這么一些事情是不應(yīng)該由Redis來解決的(因為其查詢的限制)。事實上,在考慮了這些情況后,你會找到新的方法去構(gòu)建你的數(shù)據(jù)。

很快,我們就能看到更多實際的用例。很重要的一點是,我們要明白關(guān)于Redis的這些基本事實。這能幫助我們弄清楚為什么值可以是任何東西,因為Redis從來不需要去讀取或理解它們。而且,這也可以幫助我們理清思路,然后去思考如何在這個新世界里建立模型。

存儲器和持久化(Memory and Persistence)

我們之前提及過,Redis是一種持久化的存儲器內(nèi)存儲(in-memory persistent store)。對于持久化,默認情況下,Redis會根據(jù)已變更的關(guān)鍵字數(shù)量來進行判斷,然后在磁盤里創(chuàng)建數(shù)據(jù)庫的快照(snapshot)。你可以對此進行設(shè)置,如果X個關(guān)鍵字已變更,那么每隔Y秒存儲數(shù)據(jù)庫一次。默認情況下,如果1000個或更多的關(guān)鍵字已變更,Redis會每隔60秒存儲數(shù)據(jù)庫;而如果9個或更少的關(guān)鍵字已變更,Redis會每隔15分鐘存儲數(shù)據(jù)庫。

除了創(chuàng)建磁盤快照外,Redis可以在附加模式下運行。任何時候,如果有一個關(guān)鍵字變更,一個單一附加(append-only)的文件會在磁盤里進行更新。在一些情況里,雖然硬件或軟件可能發(fā)生錯誤,但用那60秒有效數(shù)據(jù)存儲去換取更好性能是可以接受的。而在另一些情況里,這種損失就難以讓人接受,Redis為你提供了選擇。在第5章里,我們將會看到第三種選擇,其將持久化任務(wù)減荷到一個從屬數(shù)據(jù)庫里。

至于存儲器,Redis會將所有數(shù)據(jù)都保留在存儲器中。顯而易見,運行Redis具有不低的成本:因為RAM仍然是最昂貴的服務(wù)器硬件部件。

我很清楚有一些開發(fā)者對即使是一點點的數(shù)據(jù)空間都是那么的敏感。一本《威廉·莎士比亞全集》需要近5.5MB的存儲空間。對于縮放的需求,其它的解決方案趨向于IO-bound或者CPU-bound。這些限制(RAM或者IO)將會需要你去理解更多機器實際依賴的數(shù)據(jù)類型,以及應(yīng)該如何去進行存儲和查詢。除非你是存儲大容量的多媒體文件到Redis中,否則存儲器內(nèi)存儲應(yīng)該不會是一個問題。如果這對于一個程序是個問題,你就很可能不會用IO-bound的解決方案。

Redis有虛擬存儲器的支持。然而,這個功能已經(jīng)被認為是失敗的了(通過Redis的開發(fā)者),而且它的使用已經(jīng)被廢棄了。

(從另一個角度來看,一本5.5MB的《威廉·莎士比亞全集》可以通過壓縮減小到近2MB。當(dāng)然,Redis不會自動對值進行壓縮,但是因為其將所有值都看作是字節(jié),沒有什么限制讓你不能對數(shù)據(jù)進行壓縮/解壓,通過犧牲處理時間來換取存儲空間。)

整體來看(Putting It Together)

我們已經(jīng)接觸了好幾個高層次的主題。在繼續(xù)深入Redis之前,我想做的最后一件事情是將這些主題整合起來。這些主題包括,查詢的限制,數(shù)據(jù)結(jié)構(gòu)以及Redis在存儲器內(nèi)存儲數(shù)據(jù)的方法。

當(dāng)你將這3個主題整合起來,你最終會得出一個絕妙的結(jié)論:速度。一些人可能會想,當(dāng)然Redis會很快速,要知道所以的東西都在存儲器里。但這僅僅是其中的一部分,讓Redis閃耀的真正原因是其不同于其它解決方案的特殊數(shù)據(jù)結(jié)構(gòu)。

能有多快速?這依賴于很多東西,包括你正在使用著哪個命令,數(shù)據(jù)的類型等等。但Redis的性能測試是趨向于數(shù)萬或數(shù)十萬次操作每秒。你可以通過運行redis-benchmark(就在redis-serverredis-cli的同一個文件夾里)來進行測試。

我曾經(jīng)試過將一組使用傳統(tǒng)模型的代碼轉(zhuǎn)向使用Redis。在傳統(tǒng)模型里,運行一個我寫的載入測試,需要超過5分鐘的時間來完成。而在Redis里,只需要150毫秒就完成了。你不會總能得到這么好的收獲,但希望這能讓你對我們所談的東西有更清晰的理解。

理解Redis的這個特性很重要,因為這將影響到你如何去與Redis進行交互。擁有SQL背景的程序員通常會致力于讓數(shù)據(jù)庫的數(shù)據(jù)往返次數(shù)減至最小。這對于任何系統(tǒng)都是個好建議,包括Redis。然而,考慮到我們是在處理比較簡單的數(shù)據(jù)結(jié)構(gòu),有時候我們還是需要與Redis服務(wù)器頻繁交互,以達到我們的目的。剛開始的時候,可能會對這種數(shù)據(jù)訪問模式感到不太自然。實際上,相對于我們通過Redis獲得的高性能而言,這僅僅是微不足道的損失。

小結(jié)

雖然我們只接觸和擺弄了Redis的冰山一角,但我們討論的主題已然覆蓋了很大范圍內(nèi)的東西。如果覺得有些事情還是不太清楚(例如查詢),不用為此而擔(dān)心,在下一章我們將會繼續(xù)深入探討,希望你的問題都能得到解答。

這一章的要點包括:

  • 關(guān)鍵字(Keys)是用于標識一段數(shù)據(jù)的一個字符串

  • 值(Values)是一段任意的字節(jié)序列,Redis不會關(guān)注它們實質(zhì)上是什么

  • Redis展示了(也實現(xiàn)了)5種專門的數(shù)據(jù)結(jié)構(gòu)

  • 上面的幾點使得Redis快速而且容易使用,但要知道Redis并不適用于所有的應(yīng)用場景
上一篇:管理