天天看點

《Redis入門指南(第2版)》一3.6 有序集合類型

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

了解了集合類型後,小白終于被redis的強大功能所折服了,但他卻不願止步于此。這不,小白又想給部落格加上按照文章通路量排序的功能:

老師您好,之前您已經介紹過了如何使用清單類型鍵存儲文章id清單,不過我還想加上按照文章通路量排序的功能,因為我覺得很多訪客更希望看那些熱門的文章。

宋老師回答到:

這個功能很好實作,不過要用到一個新的資料類型,也是我要介紹的最後一個資料類型—有序集合。

有序集合類型(sorted set)的特點從它的名字中就可以猜到,它與上一節介紹的集合類型的差別就是“有序”二字。

在集合類型的基礎上有序集合類型為集合中的每個元素都關聯了一個分數,這使得我們不僅可以完成插入、删除和判斷元素是否存在等集合類型支援的操作,還能夠獲得分數最高(或最低)的前n個元素、獲得指定分數範圍内的元素等與分數有關的操作。雖然集合中每個元素都是不同的,但是它們的分數卻可以相同。

有序集合類型在某些方面和清單類型有些相似。

(1)二者都是有序的。

(2)二者都可以獲得某一範圍的元素。

但是二者有着很大的差別,這使得它們的應用場景也是不同的。

(1)清單類型是通過連結清單實作的,擷取靠近兩端的資料速度極快,而當元素增多後,通路中間資料的速度會較慢,是以它更加适合實作如“新鮮事”或“日志”這樣很少通路中間元素的應用。

(2)有序集合類型是使用散清單和跳躍表(skip list)實作的,是以即使讀取位于中間部分的資料速度也很快(時間複雜度是o(log(n)))。

(3)清單中不能簡單地調整某個元素的位置,但是有序集合可以(通過更改這個元素的分數)。

(4)有序集合要比清單類型更耗費記憶體。

有序集合類型算得上是redis的5種資料類型中最進階的類型了,在學習時可以與清單類型和集合類型對照了解。

1.增加元素

zadd指令用來向有序集合中加入一個元素和該元素的分數,如果該元素已經存在則會用新的分數替換原有的分數。zadd指令的傳回值是新加入到集合中的元素個數(不包含之前已經存在的元素)。

假設我們用有序集合模拟計分闆,現在要記錄tom、peter和david三名運動員的分數(分别是89分、67分和100分):

這時我們發現peter的分數錄入有誤,實際的分數應該是76分,可以用zadd指令修改peter的分數:

分數不僅可以是整數,還支援雙精度浮點數:

其中+inf和-inf分别表示正無窮和負無窮。

2.獲得元素的分數

示例如下:

3.獲得排名在某個範圍的元素清單

zrange指令會按照元素分數從小到大的順序傳回索引從start到stop之間的所有元素(包含兩端的元素)。zrange指令與lrange指令十分相似,如索引都是從0開始,負數代表從後向前查找(−1表示最後一個元素)。就像這樣:

如果需要同時獲得元素的分數的話可以在zrange指令的尾部加上withscores參數,這時傳回的資料格式就從“元素1, 元素2, …, 元素n”變為了“元素1, 分數1, 元素2, 分數2, …, 元素n, 分數n”,例如:

zrange指令的時間複雜度為o(log n+m)(其中n為有序集合的基數,m為傳回的元素個數)。

如果兩個元素的分數相同,redis會按照字典順序(即"0" < "9" < "a" < "z" < "a" < "z"這樣的順序)來進行排列。再進一步,如果元素的值是中文怎麼處理呢?答案是取決于中文的編碼方式,如使用utf-8編碼:

可見此時redis依然按照字典順序排列這些元素。

zrevrange指令和zrange的唯一不同在于zrevrange指令是按照元素分數從大到小的順序給出結果的。

4.獲得指定分數範圍的元素

zrangebyscore指令參數雖然多,但是都很好了解。該指令按照元素分數從小到大的順序傳回分數在min和max之間(包含min和max)的元素:

如果希望分數範圍不包含端點值,可以在分數前加上“(”符号。例如,希望傳回”80分到100分的資料,可以含80分,但不包含100分,則稍微修改一下上面的指令即可:

min和max還支援無窮大,同zadd指令一樣,-inf和+inf分别表示負無窮和正無窮。比如你希望得到所有分數高于80分(不包含80分)的人的名單,但你卻不知道最高分是多少(雖然有些背離現實,但是為了叙述友善,這裡假設可以獲得的分數是無上限的),這時就可以用上+inf了:

withscores參數的用法與zrange指令一樣,不再贅述。

了解sql語句的讀者對limit offset count應該很熟悉,在本指令中limit offset count 與 sql 中的用法基本相同,即在獲得的元素清單的基礎上向後偏移offset個元素,并且隻擷取前count個元素。為了便于示範,我們先向scoreboard鍵中再增加些元素:

現在scoreboard鍵中的所有元素為:

想獲得分數高于60分的從第二個人開始的3個人:

那麼,如果想擷取分數低于或等于 100 分的前 3 個人怎麼辦呢?這時可以借助zrevrangebyscore指令實作。對照前文提到的zrange指令和zrevrange指令之間的關系,相信讀者很容易能明白 zrevrangebyscore 指令的功能。需要注意的是zrevrangebyscore 指令不僅是按照元素分數從大往小的順序給出結果的,而且它的min和max參數的順序和zrangebyscore指令是相反的。就像這樣:

5.增加某個元素的分數

zincrby 指令可以增加一個元素的分數,傳回值是更改後的分數。例如,想給jerry加4分:

如果指定的元素不存在,redis 在執行指令前會先建立它并将它的分數賦為 0 再執行操作。

1.實作按點選量排序

要按照文章的點選量排序,就必須再額外使用一個有序集合類型的鍵來實作。在這個鍵中以文章的 id 作為元素,以該文章的點選量作為該元素的分數。将該鍵命名為posts:page.view,每次使用者通路一篇文章時,部落格程式就通過zincrby posts:page. view 1文章id更新通路量。

需要按照點選量的順序顯示文章清單時,有序集合的用法與清單的用法大同小異:

另外3.2節介紹過使用字元串類型鍵post:文章id:page.view來記錄單個文章的通路量,現在這個鍵已經不需要了,想要獲得某篇文章的通路量可以通過zscore posts:page. view文章id來實作。

2.改進按時間排序

3.4節介紹了每次釋出新文章時都将文章的id加入到名為posts:list的清單類型鍵中來獲得按照時間順序排列的文章清單,但是由于清單類型更改元素的順序比較麻煩,而如今不少部落格系統都支援更改文章的釋出時間,為了讓小白的部落格同樣支援該功能,我們需要一個新的方案來實作按照時間順序排列文章的功能。

為了能夠自由地更改文章釋出時間,可以采用有序集合類型代替清單類型。自然地,元素仍然是文章的id,而此時元素的分數則是文章釋出的unix時間14。通過修改元素對應的分數就可以達到更改時間的目的。

14 unix時間指utc時間1970年1月1日0時0分0秒起至現在的總秒數(不包括閏秒)。為什麼是1970年呢?因為unix在1970年左右誕生。

另外借助zrevrangebyscore指令還可以輕松獲得指定時間範圍的文章清單,借助這個功能可以實作類似wordpress的按月份檢視文章的功能。

1.獲得集合中元素的數量

zcard key

例如:

2.獲得指定分數範圍内的元素個數

zcount指令的min和max參數的特性與zrangebyscore指令中的一樣:

3.删除一個或多個元素

zrem指令的傳回值是成功删除的元素數量(不包含本來就不存在的元素)。

4.按照排名範圍删除元素

zremrangebyrank指令按照元素分數從小到大的順序(即索引0表示最小的值)删除處在指定排名範圍内的所有元素,并傳回删除的元素數量。如:

5.按照分數範圍删除元素

zremrangebyscore指令會删除指定分數範圍内的所有元素,參數min和max的特性和zrangebyscore指令中的一樣。傳回值是删除的元素數量。如:

zrank指令會按照元素分數從小到大的順序獲得指定的元素的排名(從0開始,即分數最小的元素排名為0)。如:

zrevrank指令則相反(分數最大的元素排名為0):

7.計算有序集合的交集

zinterstore指令用來計算多個有序集合的交集并将結果存儲在destination鍵中(同樣以有序集合類型存儲),傳回值為destination鍵中的元素個數。

destination鍵中元素的分數是由aggregate參數決定的。

(1)當aggregate是sum時(也就是預設值),destination鍵中元素的分數是每個參與計算的集合中該元素分數的和。例如:

(2)當aggregate是min時,destination鍵中元素的分數是每個參與計算的集合中該元素分數的最小值。例如:

(3)當aggregate是max時,destination鍵中元素的分數是每個參與計算的集合中該元素分數的最大值。例如:

zinterstore指令還能夠通過weights參數設定每個集合的權重,每個集合在參與計算時元素的分數會被乘上該集合的權重。例如:

另外還有一個指令與zinterstore指令的用法一樣,名為zunionstore,它的作用是計算集合間的并集,這裡不再贅述。