在 Redis 3.0 之前,List 對象的底層數據結構是雙向鏈表或者壓縮列表,然后在 Redis 3.2 的時候,List 對象的底層改由 quicklist 數據結構實現。
其實 quicklist 就是【雙向鏈表 + 壓縮列表】組合,因為一個 quicklist 就是一個鏈表,而鏈表中的每個元素又是一個壓縮列表。
在前面講壓縮列表的時候,提到過壓縮列表的不足,雖然壓縮列表是通過緊湊型的內存布局節省了內存開銷,但是因為它的結構設計,如果保存的元素數量增加,或者元素變大了,壓縮列表會有【連鎖更新】的風險,一旦發生,會造成性能下降。
quicklist 解決辦法,通過控制每個鏈表節點中的壓縮列表的大小或者元素個數,來規避連鎖更新的問題,因為壓縮列表元素越少或越小,連鎖更新帶來的影響就越小,從而提供了更好的訪問性能。
1. quicklist 結構設計
quicklist 的結構體跟鏈表的結構體類似,都包含了表頭和表尾,區別在于 quicklist 的節點是 quicklistNode。
typedef struct quicklist {// quicklist 的鏈表頭quicklistNode *head;// quicklist 的鏈表尾quicklistNode *tail;// 所有壓縮列表中的總元素個數unsigned long count;// quicklistNode 的個數unsigned long len;...
} quicklist;
接下來,是quicklistNode 的結構定義:
typedef struct quicklistNode {// 前一個 quicklistNodestruct quicklistNode *prev;// 下一個 quicklistNodestruct quicklistNode *next;// quicklistNode 指向的壓縮列表unsigned char *zl;// 壓縮列表的字節大小unsigned int sz;// 壓縮列表的元素個數unsigned int count : 16;...
} quicklistNode;
可以看到,quicklistNode 結構體里包含了前一個節點和下一個節點指針,這樣每個 quicklistNode 形成了一個 雙向鏈表,但是鏈表節點的元素不再是單純保存元素值,而是保存了一個壓縮列表,所以 quicklistNode 結構體里有個指向壓縮列表的指針 *zl。
如下:
在向 quicklist 添加一個元素的時候,不會像普通的鏈表那樣,直接新建一個鏈表節點,而是會檢查插入位置的壓縮列表是否能容納該元素,如果能容納就直接保存到 quicklistNode 結構里的壓縮列表,如果不能容納,才會新建一個新的 quicklistNode 結構。
quicklist 會控制 quicklistNode 結構里的壓縮列表的大小或者元素個數,來規避潛在的連鎖更新的風險,但是這并沒有完全解決連鎖更新的問題。