天天看點

《Redis入門指南(第2版)》一3.3 散列類型

本節書摘來異步社群《redis入門指南(第2版)》一書中的第3章,第3.3節,作者: 李子骅 責編: 楊海玲,更多章節内容可以通路雲栖社群“異步社群”公衆号檢視。

小白隻用了半個多小時就把通路統計和發表文章兩個部分做好了。同時借助bootstrap架構7,老師花了一小會兒時間教會了之前隻涉獵過html的小白如何做出一個像樣的網頁界面。

接着小白發問:

接下來我想要做的功能是部落格的文章清單頁,我設想在清單頁中每個文章隻顯示标題部分,可是使用您剛才介紹的方法,若想取得文章的标題,必須把整個文章資料字元串取出來反序列化,而其中占用空間最大的文章内容部分卻是不需要的,這樣難道不會在傳輸和處理時造成資源浪費嗎?

老師有些驚喜地看着小白答道:“很對!”同時以一個誇張的幅度點了下頭,接着說:

這正是我接下來準備講的。不僅取資料時會有資源浪費,在修改資料時也會有這個問題,比如當你隻想更改文章的标題時也不得不把整個文章資料字元串更新一遍。

沒等小白再問,老師就又繼續說道:

前面我說過redis的強大特性之一就是提供了多種實用的資料類型,其中的散列類型可以非常好地解決這個問題。

我們現在已經知道redis是采用字典結構以鍵值對的形式存儲資料的,而散列類型(hash)的鍵值也是一種字典結構,其存儲了字段(field)和字段值的映射,但字段值隻能是字元串,不支援其他資料類型,換句話說,散列類型不能嵌套其他的資料類型。一個散列類型鍵可以包含至多232−1個字段。

提示

除了散列類型,redis 的其他資料類型同樣不支援資料類型嵌套。比如集合類型的每個元素都隻能是字元串,不能是另一個集合或散清單等。

散列類型适合存儲對象:使用對象類别和id構成鍵名,使用字段表示對象的屬性,而字段值則存儲屬性值。例如要存儲id為2的汽車對象,可以分别使用名為color、name和price的3個字段來存儲該輛汽車的顔色、名稱和價格。存儲結構如圖3-5所示。

《Redis入門指南(第2版)》一3.3 散列類型

回想在關系資料庫中如果要存儲汽車對象,存儲結構如表3-2所示。

《Redis入門指南(第2版)》一3.3 散列類型

資料是以二維表的形式存儲的,這就要求所有的記錄都擁有同樣的屬性,無法單獨為某條記錄增減屬性。如果想為id為1的汽車增加生産日期屬性,就需要把資料表更改為如表3-3所示的結構。

《Redis入門指南(第2版)》一3.3 散列類型

對于id為2和3的兩條記錄而言date字段是備援的。可想而知當不同的記錄需要不同的屬性時,表的字段數量會越來越多以至于難以維護。而且當使用orm8将關系資料庫中的對象實體映射成程式中的實體時,修改表的結構往往意味着要中斷服務(重新開機網站程式)。為了防止這些問題,在關系資料庫中存儲這種半結構化資料還需要額外的表才行。

8即object-relational mapping(對象關系映射)。

而redis的散列類型則不存在這個問題。雖然我們在圖3-5中描述了汽車對象的存儲結構,但是這個結構隻是人為的約定,redis并不要求每個鍵都依據此結構存儲,我們完全可以自由地為任何鍵增減字段而不影響其他鍵。

1.指派與取值

hset指令用來給字段指派,而hget指令用來獲得字段的值。用法如下:

hset指令的友善之處在于不區分插入和更新操作,這意味着修改資料時不用事先判斷字段是否存在來決定要執行的是插入操作(update)還是更新操作(insert)。當執行的是插入操作時(即之前字段不存在)hset指令會傳回1,當執行的是更新操作時(即之前字段已經存在)hset指令會傳回0。更進一步,當鍵本身不存在時,hset指令還會自動建立它。

在redis中每個鍵都屬于一個明确的資料類型,如通過hset指令建立的鍵是散列類型,通過set指令建立的鍵是字元串類型等等。使用一種資料類型的指令操作另一種資料類型的鍵會提示錯誤:"err operation against a key holding the wrong kind of value"9。

9并不是所有指令都是如此,比如set指令可以覆寫已經存在的鍵而不論原來鍵是什麼類型。

當需要同時設定多個字段的值時,可以使用hmset指令。例如,下面兩條語句

可以用hmset指令改寫成

hmset key field1 value1 field2 value2

相應地,hmget指令可以同時獲得多個字段的值:

如果想擷取鍵中所有字段和字段值卻不知道鍵中有哪些字段時(如3.3.1節介紹的存儲汽車對象的例子,每個對象擁有的屬性都未必相同)應該使用hgetall指令。如:

傳回的結果是字段和字段值組成的清單,不是很直覺,好在很多語言的redis用戶端會将 hgetall的傳回結果封裝成程式設計語言中的對象,處理起來就非常友善了。例如,在node.js中:

2.判斷字段是否存在

hexists指令用來判斷一個字段是否存在。如果存在則傳回1,否則傳回0(如果鍵不存在也會傳回0)。

3.當字段不存在時指派

hsetnx 10指令與hset指令類似,差別在于如果字段已經存在,hsetnx`指令将不執行任何操作。其實作可以表示為如下僞代碼:

10hsetnx中的“nx”表示“if note xists”(如果不存在)。`

隻不過hsetnx指令是原子操作,不用擔心競态條件。

4.增加數字

上一節的指令拾遺部分介紹了字元串類型的指令incrby,hincrby指令與之類似,可以使字段值增加指定的整數。散列類型沒有hincr指令,但是可以通過hincrby key field 1來實作。

hincrby指令的示例如下:

之前person鍵不存在,hincrby指令會自動建立該鍵并預設score字段在執行指令前的值為“0”。指令的傳回值是增值後的字段值。

5.删除字段

hdel指令可以删除一個或多個字段,傳回值是被删除的字段個數:

1.存儲文章資料

3.2.3節介紹了可以将文章對象序列化後使用一個字元串類型鍵存儲,可是這種方法無法提供對單個字段的原子讀寫操作支援,進而産生競态條件,如兩個用戶端同時獲得并反序列化某個文章的資料,然後分别修改不同的屬性後存入,顯然後存入的資料會覆寫之前的資料,最後隻會有一個屬性被修改。另外如小白所說,即使隻需要文章标題,程式也不得不将包括文章内容在内的所有文章資料取出并反序列化,比較消耗資源。

除此之外,還有一種方法是組合使用多個字元串類型鍵來存儲一篇文章的資料,如圖3-6所示。

《Redis入門指南(第2版)》一3.3 散列類型

使用這種方法的好處在于無論擷取還是修改文章資料,都可以隻對某一屬性進行操作,十分友善。而本章介紹的散列類型則更适合此場景,使用散列類型的存儲結構如圖3-7所示。

從圖3-7可以看出使用散列類型存儲文章資料比圖3-6所示的方法看起來更加直覺,也更容易維護(比如可以使用hgetall指令獲得一個對象的所有字段,删除一個對象時隻需要删除一個鍵),另外存儲同樣的資料散列類型往往比字元串類型更加節約空間,具體的細節會在4.6節中介紹。

2.存儲文章縮略名

使用過wordpress的讀者可能會知道釋出文章時一般需要指定一個縮略名(slug)來構成該篇文章的網址的一部分,縮略名必須符合網址規範且最好可以與文章标題含義相似,如“this is a great post!”的縮略名可以為“this-is-a-great-post”。每個文章的縮略名必須是唯一的,是以在釋出文章時程式需要驗證使用者輸入的縮略名是否存在,同時也需要通過縮略名獲得文章的id。

《Redis入門指南(第2版)》一3.3 散列類型

我們可以使用一個散列類型的鍵slug.to.id來存儲文章縮略名和id之間的映射關系。其中字段用來記錄縮略名,字段值用來記錄縮略名對應的id。這樣就可以使用hexists指令來判斷縮略名是否存在,使用hget指令來獲得縮略名對應的文章id了。

現在釋出文章可以修改成如下代碼:

這段代碼使用了hsetnx指令原子地實作了hexists和hset兩個指令以避免競态條件。當使用者通路文章時,我們從網址中得到文章的縮略名,并查詢slug.to.id鍵來擷取文章id:

需要注意的是如果要修改文章的縮略名一定不能忘了修改slug.to.id鍵對應的字段。如要修改id為42的文章的縮略名為newslug變量的值:

1.隻擷取字段名或字段值

有時僅僅需要擷取鍵中所有字段的名字而不需要字段值,那麼可以使用hkeys指令,就像這樣:

hvals指令與hkeys指令相對應,hvals指令用來獲得鍵中所有字段值,例如:

2.獲得字段數量

hlen key

例如:

繼續閱讀