Redis技巧:有序集合(Sorted Set)的使用

有序集合(Sorted Set)是Redis一個(gè)很重要的數(shù)據(jù)結(jié)構(gòu),它用來(lái)保存需要排序的數(shù)據(jù)。例如排行榜,一個(gè)班的語(yǔ)文成績(jī),一個(gè)公司的員工工資,一個(gè)論壇的帖子等。有序集合中,每個(gè)元素都帶有score(權(quán)重),以此來(lái)對(duì)元素進(jìn)行排序。它有三個(gè)元素:key、member和score。以語(yǔ)文成績(jī)?yōu)槔?,key是考試名稱(期中考試、期末考試等),member是學(xué)生名字,score是成績(jī)。

有序集合有兩大基本用途:排序和聚合,以下用幾個(gè)例子分別說(shuō)明。

排序

假設(shè)老師需要處理期中考試的語(yǔ)文成績(jī),他做的第一件事是將學(xué)生成績(jī)錄入系統(tǒng)。

  Li Lei成績(jī)70分
  127.0.0.1:6379> ZADD mid_test 70 "Li Lei"
  (integer) 1

  Han Meimei成績(jī)70分
  127.0.0.1:6379> ZADD mid_test 70 "Han Meimei"
  (integer) 1

  tom成績(jī)99.5分
  127.0.0.1:6379> ZADD mid_test 99.5 "Tom"
  (integer) 1

排行榜

有序集合天然就是做排行榜的利器。只需將帶score的member塞到有序集合里,就可以正序或倒序取出數(shù)據(jù)。這要用到ZREVRANGE(倒序)和ZRANGE(正序)。

  分?jǐn)?shù)排行榜
  127.0.0.1:6379> ZREVRANGE mid_test 0 -1 WITHSCORES
  1) "Tom"
  2) "99.5"
  3) "Li Lei"
  4) "70"
  5) "Han Meimei"
  6) "70"

分段統(tǒng)計(jì)

有序集合還支持按score區(qū)間來(lái)查詢:ZREVRANGEBYSCORE為倒序查詢,ZRANGEBYSCORE為正序。例如要知道90分以上的學(xué)霸:

  127.0.0.1:6379> ZREVRANGEBYSCORE mid_test 100 90 WITHSCORES
  1) "Tom"
  2) "99.5"

聚合

有序集合,其本質(zhì)是集合,當(dāng)然會(huì)有交集(ZINTERSTORE)和并集(ZUNIONSTORE)運(yùn)算。

交集和并集

交集

ZINTERSTORE取所有集合的交集。以兩個(gè)集合A和B為例,要取交集C,是這樣的邏輯:

  • A和B中共有的member,會(huì)加入到C中,其score等于A、B中score之和。
  • 不同時(shí)在A和B的member,不會(huì)加到C中。

某班又進(jìn)行了期末考試,同時(shí)來(lái)了個(gè)新同學(xué)Jerry。

  127.0.0.1:6379> ZADD fin_test 88 "Li Lei"
  (integer) 1
  127.0.0.1:6379> ZADD fin_test 75 "Han Meimei"
  (integer) 1
  127.0.0.1:6379> ZADD fin_test 99.5 "Tom"
  (integer) 1
  127.0.0.1:6379> ZADD fin_test 100 "Jerry"
  (integer) 1

老師要按期中考試和期末考試的總成績(jī)來(lái)排座位,就對(duì)mid_test和fin_test做了個(gè)交集。

  127.0.0.1:6379> ZINTERSTORE sum_point 2 mid_test fin_test
  (integer) 3
  127.0.0.1:6379> ZREVRANGE sum_point 0 -1 WITHSCORES
  1) "Tom"
  2) "199"
  3) "Li Lei"
  4) "158"
  5) "Han Meimei"
  6) "145"

結(jié)果顯示了學(xué)生的總成績(jī)。
但結(jié)果中沒(méi)有新來(lái)的Jerry同學(xué)(盡管TA考了100分)。這是坑一。

并集

ZUNIONSTORE計(jì)算所有集合的并集。以兩個(gè)集合A和B為例,要取并集C,是這樣的邏輯:

  • A的所有member會(huì)加到C中,其score與A中相等
  • B的所有member會(huì)加到C中,其score與B中相等
  • A和B中共有的member,其score等于A、B中score之和。

假設(shè)某公司要核算工資總支出,先由各部門(mén)獨(dú)自核算,再由財(cái)務(wù)統(tǒng)一處理。

程序員工資

  127.0.0.1:6379> zadd programmer 2000 peter
  (integer) 1
  127.0.0.1:6379> zadd programmer 3500 jack
  (integer) 1
  127.0.0.1:6379> zadd programmer 5000 tom
  (integer) 1

經(jīng)理工資

  127.0.0.1:6379> zadd manager 2000 herry
  (integer) 1
  127.0.0.1:6379> zadd manager 3500 mary
  (integer) 1
  127.0.0.1:6379> zadd manager 4000 tom
  (integer) 1

財(cái)務(wù)統(tǒng)一處理。

  127.0.0.1:6379> zunionstore salary 2 programmer manager
  (integer) 5
  127.0.0.1:6379> zrange salary 0 -1 withscores
   1) "herry"
   2) "2000"
   3) "peter"
   4) "2000"
   5) "jack"
   6) "3500"
   7) "mary"
   8) "3500"
   9) "tom"
  10) "9000"

結(jié)果顯示了總工資支出情況。

但結(jié)果中程序員tom和經(jīng)理tom是兩個(gè)人,但工資算在了一起。這是坑二。

避免踩坑

還記得上面說(shuō)的坑一和坑二嗎?

坑一:

當(dāng)進(jìn)行ZINTERSTORE操作時(shí),如果進(jìn)行聚合操作的源集合中元素不同,則聚合后的結(jié)果集僅為交集。如果發(fā)現(xiàn)聚合后少了一些元素,請(qǐng)查看源集合元素是否相同。

坑二:

當(dāng)進(jìn)行ZUNIONSTORE操作時(shí),如果進(jìn)行聚合操作的源集合中有相同元素,則聚合后的結(jié)果集中,相同元素的score等于源集合元素的score之和。如果發(fā)現(xiàn)聚合后某些元素的score異常,請(qǐng)查看源集合是否有相同元素。

我踩過(guò)的坑:

做用戶的feed(timeline)時(shí),需要將我關(guān)注的人和我自己發(fā)表的信息聚合起來(lái)。

timeline & feed

應(yīng)該用ZUNIONSTORE將所有信息聚合到一起。

后來(lái)有用戶反饋說(shuō)timeline排序錯(cuò)誤,自己發(fā)表發(fā)布的信息永遠(yuǎn)在最上面。后來(lái)查明原因,由于早期的bug,自己竟然可以關(guān)注自己,導(dǎo)致關(guān)注人和自己重復(fù)聚合。踩到了坑二。

為什么踩坑

以坑二為例,為什么有相同元素時(shí),score就會(huì)變成原來(lái)元素的和?

因?yàn)閆INTERSTORE和ZUNIONSTORE有個(gè)參數(shù)為AGGREGATE,表示結(jié)果集的聚合方式,可取SUM、MIN、MAX其中之一。默認(rèn)值為SUM。

所以不指定聚合方式時(shí),缺省值為SUM,即求和。

<blockquote>
默認(rèn)使用的參數(shù) SUM ,可以將所有集合中某個(gè)成員的 score 值之 和 作為結(jié)果集中該成員的 score 值;使用參數(shù) MIN ,可以將所有集合中某個(gè)成員的 最小 score 值作為結(jié)果集中該成員的 score 值;而參數(shù) MAX 則是將所有集合中某個(gè)成員的 最大 score 值作為結(jié)果集中該成員的 score 值。
</blockquote>

文檔如上。

有序集合之總結(jié)

使用場(chǎng)景:排行榜,有序列表,聚合;

算法復(fù)雜度:

  • 增刪:O(M*log(N)), N 為有序集的基數(shù), M 為被成功操作(新增、移除)的成員的數(shù)量。
  • 查詢:O(log(N)+M), N 為有序集的基數(shù),而 M 為結(jié)果集的基數(shù)。
  • 聚合:O(N)+O(M log(M)), N 為給定有序集基數(shù)的總和, M 為結(jié)果集的基數(shù)。
  • 總數(shù):O(1)

注意事項(xiàng):

  • ZINTERSTORE操作時(shí),如果發(fā)現(xiàn)聚合后少了一些元素,請(qǐng)查看源集合元素是否相同。
  • ZUNIONSTORE操作時(shí),如果發(fā)現(xiàn)聚合后某些元素的score異常,請(qǐng)查看源集合是否有相同元素。

博客鏈接:http://spetacular.github.io/2015/11/01/redis-zunionstore-tip.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Redis 數(shù)據(jù)結(jié)構(gòu)簡(jiǎn)介 Redis 可以存儲(chǔ)鍵與5種不同數(shù)據(jù)結(jié)構(gòu)類型之間的映射,這5種數(shù)據(jù)結(jié)構(gòu)類型分別為Stri...
    DreamerRzc閱讀 237,458評(píng)論 26 273
  • 和Sets相比,SortedSets增加了一個(gè)權(quán)重參數(shù)score,使得集合中的元素能夠按score進(jìn)行有序排列, ...
    架構(gòu)飛毛腿閱讀 4,002評(píng)論 0 0
  • 本文為筆者對(duì)在學(xué)習(xí)Redis過(guò)程中所收集資料的一個(gè)總結(jié),目的是為了以后方便回顧相關(guān)的知識(shí),大部分為非原創(chuàng)內(nèi)容。特此...
    EakonZhao閱讀 14,631評(píng)論 0 9
  • 梁頭,今天忙頭忙尾,回到家里,想向您嘮點(diǎn)閑科,有點(diǎn)感慨。 - 那一年,陽(yáng)泉站孫東昌來(lái)所,聯(lián)系一個(gè)項(xiàng)目,很有意思。我...
    江流今古愁閱讀 239評(píng)論 0 0
  • 前日子白晝,看了釜山行,到了晚上,總會(huì)睡的不安分。一方面,由僵尸勾起的各種恐怖的畫(huà)面。另一方面,總在想著,這樣子膽...
    SylviaDeer閱讀 371評(píng)論 0 0

友情鏈接更多精彩內(nèi)容