1. 程式人生 > >Redis——2、API的理解和使用——2.5、有序集合

Redis——2、API的理解和使用——2.5、有序集合

有序集合相對於雜湊、列表、集合來說會有一點點陌生,但既然叫有序集合,那麼它和集合必然有著聯絡,它保留了集合不能有重複成員的特性,但不同的是,有序集合中的元素可以排序。但是它和列表使用索引下標作為排序依據不同的是,它給每個元素設定一個分數(score)作為排序的依據。如圖2-24所示,該有序集合包含kris、mike、frank、tim、martin、tom,它們的分數分別是1、 91、 200、 220、 250、 251, 有序集合提供了獲取指定分數和元素範圍查詢、計算成員排名等功能,合理的利用有序集合,能幫助我們在實際開發中解決很多問題。

開發提示
有序集合中的元素不能重複,但是score可以重複,就和一個班裡的同學學號不能重複, 但是考試成績可以相同。表2-7給出了列表、 集合、 有序集合三者的異同點。

2.6.1 命令
本節依舊按照集合內和集合外兩個維度對有序集合的命令進行介紹。
1.集合內
(1)新增成員
zadd key score member [score member ...]
下面操作向有序集合user:ranking新增使用者tom和他的分數251:
127.0.0.1:6379> zadd user:ranking 251 tom
(integer) 1
返回結果代表成功新增成員的個數:
127.0.0.1:6379> zadd user:ranking 1 kris 91 mike 200 frank 220 tim 250 martin
(integer) 5
有關zadd命令有兩點需要注意:
·Redis3.2為zadd命令添加了nx、 xx、 ch、 incr四個選項:
·nx:member必須不存在,才可以設定成功, 用於新增。
·xx:member必須存在,才可以設定成功, 用於更新。
·ch:返回此次操作後,有序集合元素和分數發生變化的個數
·incr:對score做增加,相當於後面介紹的zincrby。

·有序集合相比集合提供了排序欄位,但是也產生了代價,zadd的時間複雜度為O(log(n)),sadd的時間複雜度為O(1)。

(2)計算成員個數
zcard key
例如下面操作返回有序集合user:ranking的成員數為5,和集合型別的scard命令一樣,zcard的時間複雜度為O(1) 。
127.0.0.1:6379> zcard user:ranking

(integer) 5

(3)計算某個成員的分數
zscore key member
tom的分數為251, 如果成員不存在則返回nil:
127.0.0.1:6379> zscore user:ranking tom
"251"
127.0.0.1:6379> zscore user:ranking test

(nil)

(4)計算成員的排名
zrank key member
zrevrank key member
zrank是從分數從低到高返回排名, zrevrank反之。 例如下面操作中, tom在zrank和zrevrank分別排名第5和第0( 排名從0開始計算) 。
127.0.0.1:6379> zrank user:ranking tom
(integer) 5
127.0.0.1:6379> zrevrank user:ranking tom

(integer) 0

(5)刪除成員
zrem key member [member ...]
下面操作將成員mike從有序集合user:ranking中刪除。
127.0.0.1:6379> zrem user:ranking mike
(integer) 1

返回結果為成功刪除的個數。

(6)增加成員的分數
zincrby key increment member
下面操作給tom增加了9分,分數變為了260分:
127.0.0.1:6379> zincrby user:ranking 9 tom

"260"

(7)返回指定排名範圍的成員
zrange key start end [withscores]
zrevrange key start end [withscores]
有序集合是按照分值排名的,zrange是從低到高返回,zrevrange反之。下面程式碼返回排名最低的是三個成員,如果加上withscores選項,同時會返回成員的分數:
127.0.0.1:6379> zrange user:ranking 0 2 withscores
1) "kris"
2) "1"
3) "frank"
4) "200"
5) "tim"
6) "220"
127.0.0.1:6379> zrevrange user:ranking 0 2 withscores
1) "tom"
2) "260"
3) "martin"
4) "250"
5) "tim"

6) "220"

(8)返回指定分數範圍的成員

zrangebyscore key min max [withscores] [limit offset count]
zrevrangebyscore key max min [withscores] [limit offset count]
其中zrangebyscore按照分數從低到高返回,zrevrangebyscore反之。例如下面操作從低到高返回200到221分的成員, withscores選項會同時返回每個成員的分數。[limit offset count]選項可以限制輸出的起始位置和個數:
127.0.0.1:6379> zrangebyscore user:ranking 200 tinf withscores
1) "frank"
2) "200"
3) "tim"
4) "220"
127.0.0.1:6379> zrevrangebyscore user:ranking 221 200 withscores
1) "tim"
2) "220"
3) "frank"
4) "200"
同時min和max還支援開區間(小括號) 和閉區間(中括號) , -inf和+inf分別代表無限小和無限大:
127.0.0.1:6379> zrangebyscore user:ranking (200 +inf withscores
1) "tim"
2) "220"
3) "martin"
4) "250"
5) "tom"

6) "260"

(9)返回指定分數範圍成員個數
zcount key min max
下面操作返回200到221分的成員的個數:
127.0.0.1:6379> zcount user:ranking 200 221

(integer) 2

(10)刪除指定排名內的升序元素
zremrangebyrank key start end
下面操作刪除第start到第end名的成員:
127.0.0.1:6379> zremrangebyrank user:ranking 0 2

(integer) 3

(11)刪除指定分數範圍的成員
zremrangebyscore key min max
下面操作將250分以上的成員全部刪除,返回結果為成功刪除的個數:
127.0.0.1:6379> zremrangebyscore user:ranking (250 +inf

(integer) 2

2.集合間的操作
將圖2-25的兩個有序集合匯入到Redis中。

127.0.0.1:6379> zadd user:ranking:1 1 kris 91 mike 200 frank 220 tim 250 martin 251 tom
(integer) 6
127.0.0.1:6379> zadd user:ranking:2 8 james 77 mike 625 martin 888 tom

(integer) 4

(1)交集
zinterstore destination numkeys key [key ...] [weights weight [weight ...]]
[aggregate sum|min|max]
這個命令引數較多,下面分別進行說明:
·destination:交集計算結果儲存到這個鍵。
·numkeys:需要做交集計算鍵的個數。
·key[key...]:需要做交集計算的鍵。
·weights weight[weight...]:每個鍵的權重,在做交集計算時,每個鍵中的每個member會將自己分數乘以這個權重,每個鍵的權重預設是1。
·aggregate sum|min|max:計算成員交集後,分值可以按照sum(和)、min(最小值)、 max(最大值)做彙總,預設值是sum。
下面操作對user:ranking:1和user:ranking:2做交集,weights和aggregate使用了預設配置可以看到目標鍵user:ranking:1_inter_2對分值做了sum操作:
127.0.0.1:6379> zinterstore user:ranking:1_inter_2 2 user:ranking:1 user:ranking:2
(integer) 3
127.0.0.1:6379> zrange user:ranking:1_inter_2 0 -1 withscores
1) "mike"
2) "168"
3) "martin"
4) "875"
5) "tom"
6) "1139"
如果想讓user:ranking:2的權重變為0.5,並且聚合效果使用max,可以執行如下操作:
127.0.0.1:6379> zinterstore user:ranking:1_inter_2 2 user:ranking:1 user:ranking:2 weights 1 0.5 aggregate max
(integer) 3
127.0.0.1:6379> zrange user:ranking:1_inter_2 0 -1 withscores
1) "mike"
2) "91"
3) "martin"
4) "312.5"
5) "tom"

6) "444"

(2)並集
zunionstore destination numkeys key [key ...] [weights weight [weight ...]] [aggregate sum|min|max]
該命令的所有引數和zinterstore是一致的,只不過是做並集計算,例如下面操作是計算user: ranking: 1和user: ranking: 2的並集, weights和aggregate使用了預設配置, 可以看到目標鍵user: ranking: 1_union_2對分值做了sum操作:
127.0.0.1:6379> zunionstore user:ranking:1_union_2 2 user:ranking:1 user:ranking:2
(integer) 7
127.0.0.1:6379> zrange user:ranking:1_union_2 0 -1 withscores
1) "kris"
2) "1"
3) "james"
4) "8"
5) "mike"
6) "168"
7) "frank"
8) "200"
9) "tim"
10) "220"
11) "martin"
12) "875"
13) "tom"
14) "1139"
至此有序集合的命令基本介紹完了,表2-8是這些命令的時間複雜度,開發人員在使用對應的命令進行開發時,不僅要考慮功能性,還要了解相應的時間複雜度,防止由於使用不當造成應用方效率下降以及Redis阻塞。
                                                                   表2-8 有序集合命令的時間複雜度

2.6.2 內部編碼
有序集合型別的內部編碼有兩種:
·ziplist(壓縮列表):當有序集合的元素個數小於zset-max-ziplistentries配置(預設128個),同時每個元素的值都小於zset-max-ziplist-value配置(預設64位元組)時,Redis會用ziplist來作為有序集合的內部實現,ziplist可以有效減少記憶體的使用。
·skiplist(跳躍表):當ziplist條件不滿足時,有序集合會使用skiplist作為內部實現,因為此時ziplist的讀寫效率會下降。
下面用示例來說明:
1)當元素個數較少且每個元素較小時, 內部編碼為skiplist:
127.0.0.1:6379> zadd zsetkey 50 e1 60 e2 30 e3
(integer) 3
127.0.0.1:6379> object encoding zsetkey
"ziplist"
2.1)當元素個數超過128個,內部編碼變為ziplist:
127.0.0.1:6379> zadd zsetkey 50 e1 60 e2 30 e3 12 e4 ...忽略... 84 e129
(integer) 129
127.0.0.1:6379> object encoding zsetkey
"skiplist"
2.2)當某個元素大於64位元組時,內部編碼也會變為hashtable:
127.0.0.1:6379> zadd zsetkey 20 "one string is bigger than 64 byte................................"
(integer) 1
127.0.0.1:6379> object encoding zsetkey

"skiplist"

2.6.3 使用場景
有序集合比較典型的使用場景就是排行榜系統。例如視訊網站需要對使用者上傳的視訊做排行榜,榜單的維度可能是多個方面的:按照時間、按照播放數量、按照獲得的贊數。本節使用贊數這個維度,記錄每天使用者上傳視訊的排行榜。主要需要實現以下4個功能。
(1)新增使用者贊數
例如使用者mike上傳了一個視訊,並獲得了3個贊,可以使用有序集合的zadd和zincrby功能:
zadd user:ranking:2016_03_15 mike 3
如果之後再獲得一個贊, 可以使用zincrby:
zincrby user:ranking:2016_03_15 mike 1
(2)取消使用者贊數
由於各種原因(例如使用者登出、使用者作弊)需要將使用者刪除,此時需要將使用者從榜單中刪除掉,可以使用zrem。例如刪除成員tom:
zrem user:ranking:2016_03_15 mike
(3)展示獲取贊數最多的十個使用者
此功能使用zrevrange命令實現:
zrevrangebyrank user:ranking:2016_03_15 0 9
(4)展示使用者資訊以及使用者分數
此功能將使用者名稱作為鍵字尾,將使用者資訊儲存在雜湊型別中,至於使用者的分數和排名可以使用zscore和zrank兩個功能:
hgetall user:info:tom
zscore user:ranking:2016_03_15 mike
zrank user:ranking:2016_03_15 mike