摸透原理|一文帶你瞭解 Redis 列表底層的實現方式

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上次我們分享 Redis 字符串的底層原理 ,今天我們再來看下 Redis ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"List","attrs":{}}],"attrs":{}},{"type":"text","text":" 列表的底層原理。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"Redis List 命令","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Redis ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"List","attrs":{}}],"attrs":{}},{"type":"text","text":" 列表支持的相關指令比較多,比如單個元素增加、刪除操作,也支持多個元素範圍操作。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Redis ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"List","attrs":{}}],"attrs":{}},{"type":"text","text":" 列表支持列表表頭元素插入/彈出( ","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"LPUSH/LPOP","attrs":{}},{"type":"text","text":" ),也支持表尾元素插入/彈出( ","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"RPUSH/RPOP","attrs":{}},{"type":"text","text":" )。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"另外 Redis ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"List","attrs":{}}],"attrs":{}},{"type":"text","text":" 列表還支持根據下標( ","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"LINDEX","attrs":{}},{"type":"text","text":" )獲取元素,也支持根據根據下標覆蓋相應的元素( ","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"LSET","attrs":{}},{"type":"text","text":" )。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"除此之外,Redis ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"List","attrs":{}}],"attrs":{}},{"type":"text","text":" 列表還支持的範圍操作,比如獲取指定範圍內全部元素( ","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"LRANGE","attrs":{}},{"type":"text","text":" ),移除指定範圍內的全部元素( ","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"LTRIM","attrs":{}},{"type":"text","text":" )。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"瞭解完的 Redis 相關指令,我們來看下 Redis ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"List","attrs":{}}],"attrs":{}},{"type":"text","text":" 列表底層實現方式,使用兩種數據結構:","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"壓縮列表(ziplist)","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"雙向列表(linkedlist)","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/2c/2c65cbab0f2320ad83331ee9c1cfa6e0.jpeg","alt":"","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"​","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"ps:本篇文章基於 Redis 3.2 開始進行講解","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"雙向列表(linkedlist)","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上面我們知道了 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"List","attrs":{}}],"attrs":{}},{"type":"text","text":" 列表支持表頭/表尾元素的插入/彈出,這類操作使用鏈表那就非常高效,時間複雜度爲 O(1)。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Redis 雙向列表(linkedlist) 由兩個結構構成:","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"list","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"listnode","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"結構如下:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/59/59f7cdc9ca941e95fe00fa9428ccdf70.jpeg","alt":"","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"​","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"list","attrs":{}}],"attrs":{}},{"type":"text","text":" 結構體中保存了表頭節點,表尾節點以及鏈表包含的節點的數量,正因爲如此操作表頭/表尾元素的插入/彈出,鏈表長度的計算將會非常高效,時間複雜度爲 ","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"O(1)","attrs":{}},{"type":"text","text":" 。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"listnode","attrs":{}}],"attrs":{}},{"type":"text","text":" 結構體中除了保存節點的值以外,還會保存前後節點的指針,這樣如果需要獲取某個節點的前置節點與後置節點也會非常高效,時間複雜度爲 ","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"O(1)","attrs":{}},{"type":"text","text":" 。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"另外如果需要指定位置插入/刪除元素,那麼只需要變動當前位置節點前後指針即可,這個插入/刪除操作複雜度爲 ","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"O(1)","attrs":{}},{"type":"text","text":" 。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"不過需要注意了,插入/刪除動作前提我們需要找到這個指定位置,這個查找動作我們只能遍歷鏈表,複雜度爲 ","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"O(N)","attrs":{}},{"type":"text","text":" ,所以插入/刪除的複雜度爲 ","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"O(N)","attrs":{}},{"type":"text","text":" 。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"雙向列表(linkedlist)除了用作在列表鍵以外,還廣泛用於發佈/訂閱,慢查詢等內部操作。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"既然雙向列表(linkedlist)可以滿足列表鍵的操作,那爲什麼 Redis 列表還採用其他的數據結構?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"其實主要是因爲內存佔用問題,雙向鏈表由於使用兩個結構體,而這兩個結構體都需要保存一些必要信息,這必然將會佔用部分內存。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"而當元素很少的時候,如果直接使用雙向鏈表,內存還是比較浪費的。所以 Redis 引入壓縮列表。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"壓縮列表","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"壓縮列表是 Redis 爲了節約內存而開發,它由一系列的特殊編碼的的 ","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"連續內存塊","attrs":{}},{"type":"text","text":" 組成的順序型數據結構,整體結構如下:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/d6/d60ad3dbf332428525cad06554de5cfa.jpeg","alt":"","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"​","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"從上面結構可以看出來,壓縮列表實際上類似與我們使用的數組,數組中每一個元素保存一個數據。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"不過與數組不同的是,壓縮列表的表頭存在三個字段","attrs":{}}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"zlbytes\nzltail\nzllen\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"另外壓縮列表的表尾還有一個字段, ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"zlend","attrs":{}}],"attrs":{}},{"type":"text","text":" 裏面保存一個特殊的值, ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"OXFE","attrs":{}}],"attrs":{}},{"type":"text","text":" ,用於標記壓縮列表的末端。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一個壓縮列表可以由多個節點構成,每個節點可以保存整數值或字節數組,結構如下:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/f0/f0b41d5f685cb93b7945752631eb69bf.jpeg","alt":"","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"​","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"使用壓縮列表,如果查找定位表頭元素,我們只需要使用壓縮列表起始地址加上表頭三個字段長度就可以直接點位,查找非常快,複雜度是 O(1)。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"而壓縮列表的最後一個元素,查找起來也非常輕鬆,我們使用壓縮列表起始地址加上 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"zltail","attrs":{}}],"attrs":{}},{"type":"text","text":" 包含的長度就可以直接點位,查找也非常快,複雜度是 O(1)。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"至於列表中的其他元素,就沒有這麼好運了,我們只能從第一個元素或者最後一個元素,遍歷列表查找,此時的複雜度就是 O(N) 了。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"另外壓縮列表的新增、刪除元素,都將會導致重新分配內存,效率不高,平均複雜度爲 O(N),最壞福複雜度爲 O(N^2)。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"編碼轉換","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當我們創建一個 Redis 列表鍵,如果同時滿足以下兩個條件,列表對象將會使用壓縮列表作爲底層數據結構","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"列表對象保存的所有字符串元素的長度都小於 64 字節","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"列表對象中保存的元素數量小於 512 個","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果不能同時滿足這兩個條件,那麼默認將會使用雙向列表作爲底層數據結構。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"小結","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Redis 列表底層使用兩種數據結構,壓縮列表與雙向鏈表。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"壓縮列表由於使用了連續內存塊,內存佔用少,並且內存利用率高,但是新增、刪除由於涉及重新分配內存,效率不高。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"雙向列表呢,新增、刪除元素非常方便,但是由於每個節點都是獨立的內存快,內存佔用比較高,且內存碎片化嚴重。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這兩種數據結構在表頭/表尾插入與刪除元素,都十分高效。但是其他操作,可能就效率較低。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所以我們使用 Redis 列表,一定要因地制宜,可以將其當做 FIFO 隊列,這樣僅使用 POP/PUSH ,效率將會很高。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"原文:www.tuicool.com/articles/N7nQ73r","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"參考資料","attrs":{}}]},{"type":"numberedlist","attrs":{"start":null,"normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"Redis 設計與實現","attrs":{}}]}],"attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://www.bilibili.com/video/BV1D64y1o7FR/","title":null},"content":[{"type":"text","text":"從事開發一年的程序員能拿到多少錢?","attrs":{}}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://blog.csdn.net/Java0258/article/details/109403289","title":null},"content":[{"type":"text","text":"字節跳動總結的設計模式 PDF 火了,完整版開放下載","attrs":{}}],"marks":[{"type":"strong"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://blog.csdn.net/javachengzi/article/details/109729058","title":null},"content":[{"type":"text","text":"刷Github時發現了一本阿里大神的算法筆記!標星70.5K","attrs":{}}],"marks":[{"type":"strong"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://www.bilibili.com/video/BV1Ay4y167CV/","title":null},"content":[{"type":"text","text":"關於【暴力遞歸算法】你所不知道的思路","attrs":{}}],"marks":[{"type":"strong"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://www.bilibili.com/video/BV1Ba4y1W78p/","title":null},"content":[{"type":"text","text":"開闢鴻蒙,誰做系統,聊聊華爲微內核","attrs":{}}],"marks":[{"type":"strong"}]}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":" ","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"看完三件事❤️","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果你覺得這篇內容對你還蠻有幫助,我想邀請你幫我三個小忙:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"點贊,轉發,有你們的 『點贊和評論』,纔是我創造的動力。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"關注公衆號 『 Java鬥帝 』,不定期分享原創知識。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"同時可以期待後續文章ing🚀","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章