ZipList 的結構
ZipList 是 Redis 中用于實現 ZSet 的壓縮數據結構,其元素采用連續存儲方式,具有很高的內存緊湊性。
ZipList 結構組成如下:
- zlbytes:4字節,記錄整個ziplist的字節數
- zltail:4字節,記錄最后一個entry的偏移量
- zllen:2字節,記錄entry數量
- Entry:實際存儲的數據項
- zlend:1字節,結束標記
其中Entry的結構包含:
- prevlen:記錄前一個entry的長度
- 前一個entry長度 <254:占用1字節
- 前一個entry長度 ≥254:占用5字節(首字節固定為0xFE)
- encoding:內容編碼(包含類型和長度信息)
- content:實際數據
需要注意的是,prevlen采用可變長度存儲方案,根據前一個entry的長度動態調整存儲空間。
ZipList的級聯更新問題分析
假設當前ZipList中包含3個Entry,每個Entry的總長度均為253字節。此時在Entry1后插入一個長度為300字節的新Entry,將觸發以下連鎖反應:
- Entry2的prevlen需要更新,因為其前驅節點變為新插入的Entry
- 由于新Entry長度超過254字節,Entry2的prevlen需要擴展為5字節
- Entry2長度增加可能導致其總長度超過254字節,進而影響Entry3的prevlen存儲
- 這種連鎖反應可能持續傳播,最壞情況下需要更新所有后續Entry
這種級聯更新機制在極端情況下(如大量連續邊緣長度的Entry)會導致顯著的性能損耗。
ListPack是如何解決的
ListPack為解決這一問題,直接移除了prevlen字段。其核心改動在于Entry結構的設計:
- 廢棄原有prevlen字段
- 引入backlen字段記錄整個Entry的字節數
- backlen位于元素末尾,采用變長存儲(1-5字節)
通過這種設計,ListPack中的每個數據項只需記錄自身長度,不再存儲前驅節點長度。因此,進行增刪操作時僅影響當前元素,無需觸發級聯更新,從而徹底解決了級聯更新的問題。