天天看點

Redis(八):zset/zadd/zrange/zrembyscore 指令源碼解析

前面幾篇文章,我們完全領略了redis的string,hash,list,set資料類型的實作方法,相信對redis已經不再神秘。

本篇我們将介紹redis的最後一種資料類型: zset 的相關實作。

 本篇過後,我們對redis的各種基礎功能,應該不會再有疑惑。有可能的話,我們後續将會對redis的進階功能的實作做解析。(如複制、哨兵模式、叢集模式)

 回歸本篇主題,zset。zset 又稱有序集合(sorted set),即是序版本的set。經過上篇的介紹,大家可以看到,redis的讀取功能相當有限,許多是基于随機數的方式進行讀取,其原因就是set是無序的。當set有序之後,查詢能力就會得到極大的提升。

可以根據下标進行定位元素;

可以範圍查詢元素; 這是有序帶來的好處。

那麼,我們不妨先思考一下,如何實作有序?兩種方法:1. 根據添加順序定義,1、2、3... ; 2. 自定義排序值; 第1種方法實作簡單,添加時複雜度小,但是功能受限;第2種方法相對自由,對于每次插入都可能涉及重排序問題,但是查詢相對穩定,可以不必完全受限于系統實作;

同樣,我們以功能清單,到資料結構,再功能實作的思路,來解析redis的zset有序集合的實作方式吧。

零、redis zset相關操作方法

zset: Redis 有序集合是string類型元素的集合,且不允許重複的成員。每個元素都會關聯一個double類型的分數,通過分數來為集合中的成員進行從小到大的排序。

使用場景如: 儲存任務隊列,該隊列由背景定時掃描; 排行榜;

從官方手冊上查到相關使用方法如下:

1> ZADD key score1 member1 [score2 member2] 功能: 向有序集合添加一個或多個成員,或者更新已存在成員的分數 傳回值: 添加成功的元素個數(已存在的添加不成功) 2> ZCARD key 功能: 擷取有序集合的成員數 傳回值: 元素個數或0 3> ZCOUNT key min max 功能: 計算在有序集合中指定區間分數的成員數 傳回值: 區間内的元素個數 4> ZINCRBY key increment member 功能: 有序集合中對指定成員的分數加上增量 increment 傳回值: member增加後的分數 5> ZINTERSTORE destination numkeys key [key ...] 功能: 計算給定的一個或多個有序集的交集并将結果集存儲在新的有序集合 key 中 傳回值: 交集元素個數 6> ZLEXCOUNT key min max 功能: 在有序集合中計算指定字典區間内成員數量 7> ZRANGE key start stop [WITHSCORES] 功能: 通過索引區間傳回有序集合指定區間内的成員 傳回值: 區間内元素清單 8> ZRANGEBYLEX key min max [LIMIT offset count] 功能: 通過字典區間傳回有序集合的成員 9> ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT] 功能: 通過分數傳回有序集合指定區間内的成員 10> ZRANK key member 功能: 傳回有序集合中指定成員的索引 傳回值: member的排名或者 nil 11> ZREM key member [member ...] 功能: 移除有序集合中的一個或多個成員 傳回值: 成功移除的元素個數 12> ZREMRANGEBYLEX key min max 功能: 移除有序集合中給定的字典區間的所有成員 13> ZREMRANGEBYRANK key start stop 功能: 移除有序集合中給定的排名區間的所有成員 14> ZREMRANGEBYSCORE key min max 功能: 移除有序集合中給定的分數區間的所有成員 15> ZREVRANGE key start stop [WITHSCORES] 功能: 傳回有序集中指定區間内的成員,通過索引,分數從高到低 傳回值: 區間内元素清單及分數 16> ZREVRANGEBYSCORE key max min [WITHSCORES] 功能: 傳回有序集中指定分數區間内的成員,分數從高到低排序 17> ZREVRANK key member 功能: 傳回有序集合中指定成員的排名,有序內建員按分數值遞減(從大到小)排序 傳回值: member排名或者 nil 18> ZSCORE key member 功能: 傳回有序集中,成員的分數值 傳回值: member分數 19> ZUNIONSTORE destination numkeys key [key ...] 功能: 計算給定的一個或多個有序集的并集,并存儲在新的 key 中 傳回值: 存儲到新key的元素個數 20> ZSCAN key cursor [MATCH pattern] [COUNT count] 功能: 疊代有序集合中的元素(包括元素成員和元素分值) 傳回值: 元素清單 21> ZPOPMAX/ZPOPMIN/BZPOPMAX/BZPOPMIN

一、zset 相關資料結構

zset 的實作,使用了 ziplist, zskiplist 和 dict 進行實作。

二、zadd 添加成員操作

從添加實作中,我們可以完整領略資料結構的運用。

ziplist添加沒啥好說的,skiplist可以稍微提提,大體步驟為四步: 

1. 找位置, 從最高層開始, 判斷是否後繼節點小,如果小則直接在本層疊代,否則轉到下一層疊代; (每一層都要疊代至相應的位置)

2. 計算得到一新的随機level,用于決定目前節點的層級;

3. 依次對每一層與原跳表做關聯;

4. 設定backward指針;(雙向連結清單)

相對說,skiplist 還是有點抽象,我們畫個圖來描述下上面的操作:

Redis(八):zset/zadd/zrange/zrembyscore 指令源碼解析

先看插入過程的目的,主要是為了先了解 skiplist 的構造過程。而在zset的更新過程,是先删除原節點,再進行插入的這麼個過程。是以咱們還是有必要再來看看 skiplist 的删除節點過程。

skiplist 删除過程的示意圖如下:

Redis(八):zset/zadd/zrange/zrembyscore 指令源碼解析

最後,我們再來看另一種情況,即zset發生編碼轉換時,是如何做的。即如何從 ziplist 轉換到 skiplist 中呢?

至此,整個添加過程結束。本身是不太複雜的,主要針對 ziplist 和 skiplist 的分别處理(注意有逆向編碼)。但為了講清整體關系,稍顯雜亂。

三、zrange 範圍查詢

範圍查詢功能,redis提供了好幾個,zrange/zrangebyscore/zrangebylex... 應該說查詢方式都不太一樣,不過我們也不必糾結這些,隻管理會大概就行。就挑一個以 下标進行範圍查詢的實作講解下就行。

根據範圍查找元素,整體是比較簡單,疊代輸出而已。隻是 skiplist 的span維護,得好好想想。

四、zrembyscore 根據分數删除元素

zrembyscore, 首先這是個删除指令,其實它是根據分數查詢,我們可以同時解析這兩種情況。

删除的邏輯比較清晰,ziplist和skiplist分開處理。大體思路相同是:找到第一個符合條件的元素,然後疊代,直到第一個不符合條件的元素為止。

 set雖然從定義上與zset有很多相通之處,然而在實作上卻是截然不同的。由于很多東西和之前介紹的知識有重合的地方,也沒啥好特别說的。zset 的解析差不多就到這裡了。

你覺得zset還有什麼有意思的實作呢?歡迎讨論。

繼續閱讀