日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > 数据库 >内容正文

数据库

Redis进阶-List底层数据结构精讲

發布時間:2025/3/21 数据库 65 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Redis进阶-List底层数据结构精讲 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

文章目錄

  • Pre
  • list 列表
    • 隊列 O(1)
    • 棧 O(1)
    • 查詢 O(n)
    • 快速列表 quicklist
  • 壓縮列表 ziplist
    • ziplist 源碼
      • entry
    • 增加元素
  • 快速列表 quicklist
    • ziplist 存多少元素?
    • 壓縮深度
  • 延伸


Pre

Redis進階-核心數據結構進階實戰

Algorithms_基礎數據結構(03)_線性表之鏈表_雙向鏈表

Redis 有 5 種基礎數據結構,分別為:string (字符串)、list (列表)、set (集合)、hash (哈希) 和 zset (有序集合) 。

Redis 所有的數據結構都是以唯一的key 字符串作為名稱,然后通過這個唯一 key 值來獲取相應的 value 數據。不同類型的數據結構的差異就在于 value 的結構不一樣。


list 列表

  • Redis 的列表相當于 Java 語言里面的 LinkedList,是鏈表而不是數組 。

    這意味著list 的插入和刪除操作非常快,時間復雜度為 O(1),但是查找數據很慢,時間復雜度為 O(n) 。

  • 當列表彈出了最后一個元素之后,該數據結構自動被刪除,內存被回收。

  • Redis 的列表結構常用來做異步隊列使用

    將需要延后處理的任務結構體序列化成字符串塞進 Redis 的列表,另一個線程從這個列表中輪詢數據進行處理


隊列 O(1)

右邊進左邊出:隊列

192.168.18.131:8001> rpush artisan art1 art2 art3 art4 (integer) 4 192.168.18.131:8001> llen artisan (integer) 4 192.168.18.131:8001> LRANGE artisan 0 999 1) "art1" 2) "art2" 3) "art3" 4) "art4" 192.168.18.131:8001> LPOP artisan "art1" 192.168.18.131:8001> LPOP artisan "art2" 192.168.18.131:8001> LPOP artisan "art3" 192.168.18.131:8001> LPOP artisan "art4" 192.168.18.131:8001> LPOP artisan (nil) 192.168.18.131:8001>

當然了,你也可以左邊進,右邊出,保證FIFO就行。

除了rpush 和 lpop, 還可以使用 lpush 和 rpop 結合使用,效果是一樣的。


棧 O(1)

右邊進右邊出:棧

192.168.18.131:8001> rpush artisan art1 art2 art3 art4 (integer) 4 192.168.18.131:8001> RPOP artisan "art4" 192.168.18.131:8001> RPOP artisan "art3" 192.168.18.131:8001> RPOP artisan "art2" 192.168.18.131:8001> RPOP artisan "art1" 192.168.18.131:8001> RPOP artisan (nil) 192.168.18.131:8001>

查詢 O(n)

lindex & ltrim

  • lindex 相當于 Java 鏈表的 get(int index)方法,它需要對鏈表進行遍歷,性能隨著參數index 增大而變差.

  • ltrim : 兩個參數 start_index 和 end_index 定義了一個區間,在這個區間內的值, ltrim 要保留,區間之外統統砍掉。

  • 我們可以通過 ltrim 來實現一個定長的鏈表,這一點非常有用。

  • index 可以為負數,index=-1 表示倒數第一個元素,同樣 index=-2 表示倒數第二個元素。

192.168.18.131:8001> rpush artisan art1 art2 art3 art4 (integer) 4 192.168.18.131:8001> LINDEX artisan 0 # O(n) 慎用 "art1" 192.168.18.131:8001> LINDEX artisan 3 "art4" 192.168.18.131:8001> LTRIM artisan 0 2 OK 192.168.18.131:8001> LRANGE artisan 0 999 1) "art1" 2) "art2" 3) "art3"192.168.18.131:8001> LRANGE artisan 0 -1 # 獲取所有元素,O(n) 慎用 1) "art1" 2) "art2" 3) "art3"192.168.18.131:8001> LTRIM artisan 1 -1 # O(n) 慎用 OK 192.168.18.131:8001> LRANGE artisan 0 -1 # 獲取所有元素,O(n) 慎用 1) "art2" 2) "art3" 192.168.18.131:8001> 192.168.18.131:8001> LTRIM artisan 1 0 # 這其實是清空了整個列表,因為區間范圍長度為負 OK 192.168.18.131:8001> LLEN artisan (integer) 0 192.168.18.131:8001>

快速列表 quicklist

Redis 底層存儲的還不是一個簡單的 linkedlist,而是稱之為快速鏈表 quicklist 的一個結構。

  • 首先在列表元素較少的情況下會使用一塊連續的內存存儲,這個結構是 ziplist ,即壓縮列表 . 它將所有的元素緊挨著一起存儲,分配的是一塊連續的內存

  • 當數據量比較多才會改成 quicklist.

因為普通的鏈表需要的附加指針空間太大,會比較浪費空間,而且會加重內存的碎片化 .

比如這個列表里存的只是 int 類型的數據,結構上還需要兩個額外的指針 prev 和 next 。所以 Redis 將鏈表和 ziplist 結合起來組成了 quicklist。也就是將多個ziplist 使用雙向指針串起來使用。這樣既滿足了快速的插入刪除性能,又不會出現太大的空間冗余。


壓縮列表 ziplist

Redis 為了節約內存空間使用,zset 和 hash 容器對象在元素個數較少的時候,采用壓縮列表 (ziplist) 進行存儲。

壓縮列表是一塊連續的內存空間,元素之間緊挨著存儲,沒有任何冗余空隙。

使用DEBUG OBJECT 查看內部存儲結構

192.168.18.131:8001> ZADD artisan 1.0 java 2.0 go 3.0 python (integer) 3 192.168.18.131:8001> DEBUG OBJECT artisan # 查看內部存儲結構 Value at:0x7f131e2ba5d0 refcount:1 encoding:ziplist serializedlength:36 lru:9895943 lru_seconds_idle:6 192.168.18.131:8001> 192.168.18.131:8001> HMSET artisan2 name ysw sex male -> Redirected to slot [6066] located at 192.168.18.132:8002 OK 192.168.18.132:8002> DEBUG OBJECT artisan2 Value at:0x7fb74eaba5f0 refcount:1 encoding:ziplist serializedlength:34 lru:9896028 lru_seconds_idle:9 192.168.18.132:8002>

觀察 debug object 輸出的 encoding 字段都是 ziplist,這就表示內部采用壓縮列表結構進行存儲。


ziplist 源碼

struct ziplist<T> {int32 zlbytes; // 整個壓縮列表占用字節數int32 zltail_offset; // 最后一個元素距離壓縮列表起始位置的偏移量,用于快速定位到最后一個節點int16 zllength; // 元素個數T[] entries; // 元素內容列表,挨個挨個緊湊存儲int8 zlend; // 標志壓縮列表的結束,值恒為 0xFF }

壓縮列表為了支持雙向遍歷,所以才會有 ztail_offset 這個字段,用來快速定位到最后一 個元素,然后倒著遍歷。


entry

entry 塊隨著容納的元素類型不同,也會有不一樣的結構

struct entry {int<var> prevlen; // 前一個 entry 的字節長度int<var> encoding; // 元素類型編碼optional byte[] content; // 元素內容 }

它的 prevlen 字段表示前一個 entry 的字節長度,當壓縮列表倒著遍歷時,需要通過這個字段來快速定位到下一個元素的位置。

  • 它是一個變長的整數,當字符串長度小于254(0xFE) 時,使用一個字節表示;

  • 如果達到或超出 254(0xFE) 那就使用 5 個字節來表示。

  • 第一個字節是 0xFE(254),剩余四個字節表示字符串長度。

用 5 個字節來表示字符串長度,是不是太浪費了?

我們可以算一下,當字符串長度比較長的時候,其實 5個字節也只占用了不到(5/(254+5))<2%的空間。


encoding 字段存儲了元素內容的編碼類型信息,ziplist 通過這個字段來決定后面的content 內容的形式。


增加元素

因為 ziplist 都是緊湊存儲,沒有冗余空間 (對比一下 Redis 的字符串結構)。意味著插入一個新的元素就需要調用 realloc 擴展內存。

取決于內存分配器算法和當前的 ziplist 內存大小,realloc 可能會重新分配新的內存空間,并將之前的內容一次性拷貝到新的地址,也可能在原有的地址上進行擴展,這時就不需要進行舊內容的內存拷貝。

如果 ziplist 占據內存太大,重新分配內存和拷貝內存就會有很大的消耗。所以 ziplist不適合存儲大型字符串,存儲的元素也不宜過多。


快速列表 quicklist

Redis 早期版本存儲 list 列表數據結構使用的是壓縮列表 ziplist 和普通的雙向鏈表linkedlist,也就是元素少時用 ziplist,元素多時用 linkedlist。

// 鏈表的節點 struct listNode<T> {listNode* prev;listNode* next;T value; } // 鏈表 struct list {listNode *head;listNode *tail;long length; }

考慮到鏈表的附加空間相對太高,prev 和 next 指針就要占去 16 個字節 (64bit 系統的指針是 8 個字節),另外每個節點的內存都是單獨分配,會加劇內存的碎片化,影響內存管理效率。后續版本對列表數據結構進行了改造,使用 quicklist 代替了 ziplist 和 linkedlist。

192.168.18.132:8002> RPUSH test artisan1 artisan2 artisan3 (integer) 3 192.168.18.132:8002> DEBUG OBJECT test Value at:0x7fb74eaba610 refcount:1 encoding:quicklist serializedlength:36 lru:9897276 lru_seconds_idle:4 ql_nodes:1 ql_avg_node:3.00 ql_ziplist_max:-2 ql_compressed:0 ql_uncompressed_size:41 192.168.18.132:8002>

觀察上面輸出字段 encoding 的值。quicklist 是 ziplist 和 linkedlist 的混合體,它將 linkedlist 按段切分,每一段使用 ziplist 來緊湊存儲,多個 ziplist 之間使用雙向指針串接起來。

struct ziplist {... } struct ziplist_compressed {int32 size;byte[] compressed_data; }struct quicklistNode {quicklistNode* prev;quicklistNode* next;ziplist* zl; // 指向壓縮列表int32 size; // ziplist 的字節總數int16 count; // ziplist 中的元素數量int2 encoding; // 存儲形式 2bit,原生字節數組還是 LZF 壓縮存儲... } struct quicklist {quicklistNode* head;quicklistNode* tail;long count; // 元素總數int nodes; // ziplist 節點的個數int compressDepth; // LZF 算法壓縮深度... }

上述代碼簡單地表示了 quicklist 的大致結構。為了進一步節約空間,Redis 還會對ziplist 進行壓縮存儲,使用 LZF 算法壓縮,可以選擇壓縮深度。


ziplist 存多少元素?

quicklist 內部默認單個 ziplist 長度為 8k 字節,超出了這個字節數,就會新起一個ziplist。

ziplist 的長度由配置參數 list-max-ziplist-size 決定。


壓縮深度

quicklist 默認的壓縮深度是 0,也就是不壓縮。壓縮的實際深度由配置參數 list-compress-depth 決定.

為了支持快速的 push/pop 操作,quicklist 的首尾兩個 ziplist 不壓縮,此時深度就是 1。

如果深度為 2,就表示 quicklist 的首尾第一個 ziplist 以及首尾第二個 ziplist 都不壓縮。


延伸

參考:《Redis深度歷險:核心原理和應用實踐》

ziplist、linkedlist 和 quicklist 的性能對比

總結

以上是生活随笔為你收集整理的Redis进阶-List底层数据结构精讲的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。