現在開始將探究Redis的5種資料結構,我們會解釋每種資料結構都是什麼,包含了什麼有效方法(Method),以及你能用這些資料結構處理哪些類型的特性和資料。
目前為止,我們所知道的Redis構成僅包括命令、關鍵字和值,還沒有接觸到關於資料結構的具體概念。當我們使用set命令時,Redis是怎麼知道我們是在使用哪個資料結構。其解決方案是,每個命令都相對應於一種特定的資料結構。例如,當你使用set命令,你就是將值儲存到一個字串資料結構裡。而當你使用hset命令,你就是將值儲存到一個散列資料結構裡。考慮到Redis的關鍵字集很小,這樣的機制具有相當的可管理性。
Redis的網站裡有著非常優秀的參考文檔,沒有任何理由去重造輪子。但為了搞清楚這些資料結構的作用,我們將會覆蓋那些必須知道的重要命令。
沒有什麼事情比高興的玩和實驗有趣的東西來得更重要的了。在任何時候,你都能通過鍵入flushdb命令將你資料庫裡的所有值清除掉,因此,不要再那麼害羞了,去嘗試做些瘋狂的事情吧。 字串(Strings)
在Redis裡,字串是最基本的資料結構。當你在思索著關鍵字-值對時,你就是在思索著字串資料結構。不要被名字給搞混了,如之前說過的,你的值可以是任何東西。我更喜歡將他們稱作“標量”(Scalars),但也許只有我才這樣想。
我們已經看到了一個常見的字串使用案例,即通過關鍵字儲存物件的執行個體。有時候,你會頻繁地用到這類操作:
set users:leto "{name: leto, planet: dune, likes: [spice]}"
除了這些外,Redis還有一些常用的操作。例如,strlen能用來擷取一個關鍵字對應值的長度;getrange將返回指定範圍內的關鍵字對應值;append會將value附加到已存在的關鍵字對應值中(如果該關鍵字並不存在,則會建立一個新的關鍵字-值對)。不要猶豫,去試試看這些命令吧。下面是我得到的:
> strlen users:leto(integer) 42> getrange users:leto 27 40"likes: [spice]"> append users:leto " OVER 9000!!"(integer) 54
現在你可能會想,這很好,但似乎沒有什麼意義。你不能有效地提取出一段範圍內的JSON檔案,或者為其附加一些值。你是對的,這裡的經驗是,一些命令,尤其是關於字串資料結構的,只有在給定了明確的資料類型後,才會有實際意義。
之前我們知道了,Redis不會去關注你的值是什麼東西。通常情況下,這沒有錯。然而,一些字串命令是專門為一些類型或值的結構而設計的。作為一個有些含糊的用例,我們可以看到,對於一些自訂的空間效率很高的(space-efficient)序列化對象,append和getrange命令將會很有用。對於一個更為具體的用例,我們可以再看一下incr、incrby、decr和decrby命令。這些命令會增長或者縮減一個字串資料結構的值:
> incr stats:page:about(integer) 1> incr stats:page:about(integer) 2> incrby ratings:video:12333 5(integer) 5> incrby ratings:video:12333 3(integer) 8
由此你可以想象到,Redis的字串資料結構能很好地用於分析用途。你還可以去嘗試增長users:leto(一個不是整數的值),然後看看會發生什麼(應該會得到一個錯誤)。
更為進階的用例是setbit和getbit命令。“今天我們有多少個獨立使用者訪問”是個在Web應用裡常見的問題,有一篇精彩的博文,在裡面可以看到Spool是如何使用這兩個命令有效地解決此問題。對於1.28億個使用者,一部膝上型電腦在不到50毫秒的時間裡就給出了回覆,而且只用了16MB的儲存空間。
最重要的事情不是在於你是否明白位元影像(Bitmaps)的工作原理,或者Spool是如何去使用這些命令,而是應該要清楚Redis的字串資料結構比你當初所想的要有用許多。然而,最常見的應用案例還是上面我們給出的:儲存物件(簡單或複雜)和計數。同時,由於通過關鍵字來擷取一個值是如此之快,字串資料結構很常被用來快取資料。 散列(Hashes)
我們已經知道把Redis稱為一種關鍵字-值型儲存是不太準確的,散列資料結構是一個很好的例證。你會看到,在很多方面裡,散列資料結構很像字串資料結構。兩者顯著的區別在於,散列資料結構提供了一個額外的間接層:一個域(Field)。因此,散列資料結構中的set和get是:
hset users:goku powerlevel 9000hget users:goku powerlevel
相關的操作還包括在同一時間設定多個域、同一時間擷取多個域、擷取所有的域和值、列出所有的域或者刪除指定的一個域:
hmset users:goku race saiyan age 737hmget users:goku race powerlevelhgetall users:gokuhkeys users:gokuhdel users:goku age
如你所見,散列資料結構比普通的字串資料結構具有更多的可操作性。我們可以使用一個散列資料結構去獲得更精確的描述,是儲存一個使用者,而不是一個序列化對象。從而得到的好處是能夠提取、更新和刪除具體的資料片段,而不必去擷取或寫入整個值。
對於散列資料結構,可以從一個經過明確定義的對象的角度來考慮,例如一個使用者,關鍵之處在於要理解他們是如何工作的。從效能上的原因來看,這是正確的,更具粒度化的控制可能會相當有用。在下一章我們將會看到,如何用散列資料結構去組織你的資料,使查詢變得更為實效。在我看來,這是散列真正耀眼的地方。 列表(Lists)
對於一個給定的關鍵字,列表資料結構讓你可以儲存和處理一組值。你可以添加一個值到列表裡、擷取列表的第一個值或最後一個值以及用給定的索引來處理值。列表資料結構維護了值的順序,提供了基於索引的高效操作。為了跟蹤在網站裡註冊的最新使用者,我們可以維護一個newusers的列表:
lpush newusers gokultrim newusers 0 50
(譯註:ltrim命令的具體構成是LTRIM Key start stop。要理解ltrim命令,首先要明白Key所儲存的值是一個列表,理論上列表可以存放任意個值。對於指定的列表,根據所提供的兩個範圍參數start和stop,ltrim命令會將指定範圍外的值都刪除掉,只留下範圍內的值。)
首先,我們將一個新使用者推入到列表的前端,然後對列表進行調整,使得該列表只包含50個最近被推入的使用者。這是一種常見的模式。ltrim是一個具有O(N)時間複雜度的操作,N是被刪除的值的數量。從上面的例子來看,我們總是在插入了一個使用者後再進行列表調整,實際上,其將具有O(1)的時間複雜度(因為N將永遠等於1)的常數效能。
這是我們第一次看到一個關鍵字的對應值索引另一個值。如果我們想要擷取最近的10個使用者的詳細資料,我們可以運行下面的組合操作:
keys = redis.lrange('newusers', 0, 10)redis.mget(*keys.map {|u| "users:#{u}"})
我們之前談論過關於多次往返資料的模式,上面的兩行Ruby代碼為我們進行了很好的示範。
當然,對於儲存和索引關鍵字的功能,並不是只有列表資料結構這種方式。值可以是任意的東西,你可以使用列表資料結構去儲存日誌,也可以用來跟蹤使用者瀏覽網站時的路徑。如果你過往曾構建過遊戲,你可能會使用列表資料結構去跟蹤使用者的排隊活動。 集合(Sets)
集合資料結構常常被用來儲存只能唯一存在的值,並提供了許多的基於集合的操作,例如並集。集合資料結構沒有對值進行排序,但是其提供了高效的基於值的操作。使用集合資料結構的典型用例是朋友名單的實現:
sadd friends:leto ghanima paul chani jessicasadd friends:duncan paul jessica alia
不管一個使用者有多少個朋友,我們都能高效地(O(1)時間複雜度)識別出使用者X是不是使用者Y的朋友:
sismember friends:leto jessicasismember friends:leto vladimir
而且,我們可以查看兩個或更多的人是不是有共同的朋友:
sinter friends:leto friends:duncan
甚至可以在一個新的關鍵字裡儲存結果:
sinterstore friends:leto_duncan friends:leto friends:duncan
有時候需要對值的屬性進行標記和跟蹤處理,但不能通過簡單的複製操作完成,集合資料結構是解決此類問題的最好方法之一。當然,對於那些需要運用集合操作的地方(例如交集和並集),集合資料結構就是最好的選擇。 分類集合(Sorted Sets)
最後也是最強大的資料結構是分類集合資料結構。如果說散列資料結構類似於字串資料結構,主要區分是域(field)的概念;那麼分類集合資料結構就類似於集合資料結構,主要區分是標記(score)的概念。標記提供了排序(sorting)和秩劃分(ranking)的功能。如果我們想要一個秩分類的朋友名單,可以這樣做:
zadd friends:duncan 70 ghanima 95 paul 95 chani 75 jessica 1 vladimir
對於duncan的朋友,要怎樣計算出標記(score)為90或更高的人數。
zcount friends:duncan 90 100
如何擷取chani在名單裡的秩(rank)。
zrevrank friends:duncan chani
(譯註:zrank命令的具體構成是ZRANK Key menber,要知道Key儲存的Sorted Set預設是根據Score對各個menber進行升序的排列,該命令就是用來擷取menber在該排列裡的次序,這就是所謂的秩。)
我們使用了zrevrank命令而不是zrank命令,這是因為Redis的預設排序是從低到高,但是在這個例子裡我們的秩劃分是從高到低。對於分類集合資料結構,最常見的應用案例是用來實現熱門排行榜系統。事實上,對於一些基於整數排序,且能以標記(score)來進行有效操作的東西,使用分類集合資料結構來處理應該都是不錯的選擇。 小結
對於Redis的5種資料結構,我們進行了高層次的概述。一件有趣的事情是,相對於最初構建時的想法,你經常能用Redis創造出一些更具實效的事情。對於字串資料結構和分類集合資料結構的使用,很有可能存在一些構建方法是還沒有人想到的。當你理解了那些常用的應用案例後,你將發現Redis對於許多類型的問題,都是很理想的選擇。還有,不要因為Redis展示了5種資料結構和相應的各種方法,就認為你必須要把所有的東西都用上。只使用一些命令去構建一個特性是很常見的。