Redis zset 底層結構

Redis zset 底層結構

   概要

   在 Redis 的五種主要數據類型中,zset(有序集合)類型可能是最複雜,但也是最強大的一種。zset 不僅可以存儲鍵值對,還可以爲每個元素分配一個分數,然後根據這個分數進行排序。這使得 Zset 非常適合用於實現排行榜、時間線等功能。

   一、Zset底層結構

   Redis的zset(有序集合)類型的底層實現會根據實際情況選擇使用壓縮列表(ziplist)或者跳躍表(skiplist)。Redis會根據實際情況動態地在這兩種底層結構之間切換,以在內存使用和性能之間找到一個平衡。

   1. 什麼時候使用壓縮鏈表,什麼時候使用跳錶呢 ?

   這主要取決於兩個配置參數:zset-max-ziplist-entries (默認值爲128 單位:個) 和 zset-max-ziplist-value (默認值爲64,單位:字節)。

   使用壓縮列表:當 zset 存儲的元素數量小於 zset-max-ziplist-entries 的值,且所有元素的最大長度小於 zset-max-ziplist-value 的值時,Redis會選擇使用壓縮列表作爲底層實現。壓縮列表佔用的內存較少,但是在需要修改數據時,可能需要對整個壓縮列表進行重寫,性能較低。

   使用跳躍表:當 zset 存儲的元素數量超過 zset-max-ziplist-entries 的值,或者任何元素的長度超過 zset-max-ziplist-value 的值時,Redis 會將底層結構從壓縮列表轉換爲跳躍表。跳躍表的查找和修改數據的性能較高,但是佔用的內存也較多。

   這兩個參數都可以在 Redis 的配置文件中進行設置。通過調整這兩個參數,就可以根據自己的應用特性,選擇更傾向於節省內存,還是更傾向於提高性能。

   2. 壓縮表 ziplist
   壓縮列表是一種爲節省內存而設計的特殊編碼結構,它將所有的元素和分數緊湊地存儲在一起。這種方式的優點是佔用內存少,但是在需要修改數據時,可能需要對整個壓縮列表進行重寫,性能較低。當 Zset 存儲的元素數量較少,且元素的字符串長度較短時,Redis 會選擇使用壓縮列表作爲底層實現。

   3. 跳躍表 skiplist

   跳躍表(skiplist)是一種可以進行快速查找的有序數據結構,它通過維護多級索引來實現快速查找。這種方式的優點是查找和修改數據的性能較高,但是佔用的內存也較多。當 zset 存儲的元素數量較多,或者元素的字符串長度較長時,Redis 會選擇使用跳躍表作爲底層實現。

   一個zset結構同時包含一個字典和一個跳躍表。跳躍表按score從小到大保存所有集合元素。而字典則保存着從member到score的映射,這樣就可以用O(1)的複雜度來查找member對應的score值。雖然同時使用兩種結構,但它們會通過指針來共享相同元素的member和score,因此不會浪費額外的內存。

   在 Redis 的源代碼中,跳躍表的結構定義如下:

 1 typedef struct zskiplistNode {
 2     robj *obj;
 3     double score;
 4     struct zskiplistNode *backward;
 5     struct zskiplistLevel {
 6         struct zskiplistNode *forward;
 7         unsigned int span;
 8     } level[];
 9 } zskiplistNode;
10 
11 typedef struct zskiplist {
12     struct zskiplistNode *header, *tail;
13     unsigned long length;
14     int level;
15 } zskiplist;

   zskiplistNode 結構體表示跳躍表中的一個節點,包含元素對象(obj)、分數(score)、指向前一個節點的指針(backward)和一個包含多個層的數組(level)。每一層都包含一個指向下一個節點的指針(forward)和一個表示當前節點到下一個節點的跨度(span)

   zskiplist 結構體表示一個跳躍表,包含頭節點(header)、尾節點(tail)、跳躍表中的節點數量(length)和當前跳躍表的最大層數(level)

   4. 小結

   當數據較少時:zset是由一個壓縮鏈表來實現的

   當數據較多時:zset是由一個 字典+跳錶 來實現的。簡單來講,dict用來查詢數據到分數的對應關係,而skiplist用來根據分數查詢數據(可能是範圍查找)

   第1層鏈表不是一個單向鏈表,而是一個雙向鏈表,這是爲了方便以倒序方式獲取一個範圍內的元素。

   二、Redis跳錶與MySQL B+樹
   MySQL 的 B+ 樹和 Redis 的跳錶都是用於存儲有序數據的數據結構,但它們有一些關鍵的區別,使得它們在不同的場景和用途中各有優勢。

   1.  結構差異

   B+ 樹是一種多路搜索樹,每個節點可以有多個子節點,而跳錶是一種基於鏈表的數據結構,每個節點只有一個下一個節點,但可以有多個快速通道指向後面的節點。

   2. 空間利用率

   B+ 樹的磁盤讀寫操作是以頁(通常是 4KB)爲單位的,每個節點存儲多個鍵值對,可以更好地利用磁盤空間,減少 I/O 操作。而跳錶的空間利用率相對較低。

   3. 插入和刪除操作

   跳錶的插入和刪除操作相對簡單,時間複雜度爲 O(logN),並且不需要像 B+ 樹那樣進行復雜的節點分裂和合並操作。

   4. 範圍查詢

   B+ 樹的所有葉子節點形成了一個有序鏈表,因此非常適合進行範圍查詢。而跳錶雖然也可以進行範圍查詢,但效率相對較低。

   因此,B+ 樹和跳錶不能簡單地相互替換。在需要大量進行磁盤 I/O 操作和範圍查詢的場景(如數據庫索引)中,B+ 樹可能是更好的選擇。而在主要進行內存操作,且需要頻繁進行插入和刪除操作的場景(如 Redis)中,跳錶可能更有優勢。

   三、Mysql 爲什麼使用 B +樹,而不是跳錶?

   MySQL 數據庫是持久化數據庫,即是存儲到磁盤上的,因此查詢時要求更少磁盤 IO,且 MySQL是讀多寫少的場景較多,顯然 B+ 樹更加適合M ysql。

   Redis 的 ZSet 爲什麼使用跳錶而不是B+樹

   Redis 是內存存儲,不存在IO的瓶頸,所以跳錶層數的耗時可以忽略不計,而且插入數據時不需要開銷以平衡數據結構(寫多)。

   參考鏈接:https://juejin.cn/post/7114672722981945375

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章