redis list資料結構
redis list資料結構底層采用壓縮清單ziplist或linkedlist兩種資料結構進行存儲,首先以ziplist進行存儲,在不滿足ziplist的存儲要求後轉換為linkedlist清單。
當清單對象同時滿足以下兩個條件時,清單對象使用ziplist進行存儲,否則用linkedlist存儲。
- 清單對象儲存的所有字元串元素的長度小于64位元組
- 清單對象儲存的元素數量小于512個。
redis list元素添加過程
list的資料添加根據傳入的變量個數一個個順序添加,整個順序如下:
- 建立list對象并添加到db的資料結構當中
- 針對每個待插入的元素添加到list當中
void pushGenericCommand(redisClient *c, int where) {
int j, waiting = 0, pushed = 0;
// 取出清單對象
robj *lobj = lookupKeyWrite(c->db,c->argv[1]);
// 如果清單對象不存在,那麼可能有用戶端在等待這個鍵的出現
int may_have_waiting_clients = (lobj == NULL);
if (lobj && lobj->type != REDIS_LIST) {
addReply(c,shared.wrongtypeerr);
return;
}
// 将清單狀态設定為就緒
if (may_have_waiting_clients) signalListAsReady(c,c->argv[1]);
// 周遊所有輸入值,并将它們添加到清單中
for (j = 2; j < c->argc; j++) {
// 編碼值
c->argv[j] = tryObjectEncoding(c->argv[j]);
// 如果清單對象不存在,那麼建立一個,并關聯到資料庫
if (!lobj) {
lobj = createZiplistObject();
dbAdd(c->db,c->argv[1],lobj);
}
// 将值推入到清單
listTypePush(lobj,c->argv[j],where);
pushed++;
}
// 傳回添加的節點數量
addReplyLongLong(c, waiting + (lobj ? listTypeLength(lobj) : 0));
// 如果至少有一個元素被成功推入,那麼執行以下代碼
if (pushed) {
char *event = (where == REDIS_HEAD) ? "lpush" : "rpush";
// 發送鍵修改信号
signalModifiedKey(c->db,c->argv[1]);
// 發送事件通知
notifyKeyspaceEvent(REDIS_NOTIFY_LIST,event,c->argv[1],c->db->id);
}
server.dirty += pushed;
}
list的每個元素的插入過程中,我們會對是否需要進行轉碼作兩個判斷:
- 對每個插入元素的長度進行判斷是否進行ziplist->linkedlist的轉碼。
- 對list總長度是否超過ziplist最大長度的判斷。
/*
* 将給定元素添加到清單的表頭或表尾。
*
* 參數 where 決定了新元素添加的位置:
*
* - REDIS_HEAD 将新元素添加到表頭
*
* - REDIS_TAIL 将新元素添加到表尾
*
*
* 調用者無須擔心 value 的引用計數,因為這個函數會負責這方面的工作。
*/
void listTypePush(robj *subject, robj *value, int where) {
// 是否需要轉換編碼?
listTypeTryConversion(subject,value);
// #define REDIS_LIST_MAX_ZIPLIST_ENTRIES 512
if (subject->encoding == REDIS_ENCODING_ZIPLIST &&
ziplistLen(subject->ptr) >= server.list_max_ziplist_entries)
listTypeConvert(subject,REDIS_ENCODING_LINKEDLIST);
// ZIPLIST
if (subject->encoding == REDIS_ENCODING_ZIPLIST) {
int pos = (where == REDIS_HEAD) ? ZIPLIST_HEAD : ZIPLIST_TAIL;
// 取出對象的值,因為 ZIPLIST 隻能儲存字元串或整數
value = getDecodedObject(value);
subject->ptr = ziplistPush(subject->ptr,value->ptr,sdslen(value->ptr),pos);
decrRefCount(value);
// 雙端連結清單
} else if (subject->encoding == REDIS_ENCODING_LINKEDLIST) {
if (where == REDIS_HEAD) {
listAddNodeHead(subject->ptr,value);
} else {
listAddNodeTail(subject->ptr,value);
}
incrRefCount(value);
// 未知編碼
} else {
redisPanic("Unknown list encoding");
}
}
判斷ziplist中單個元素的長度是否超過64的長度,如果超過了長度那麼就需要轉編碼格式為linkedlist編碼。
/*
* 對輸入值 value 進行檢查,看是否需要将 subject 從 ziplist 轉換為雙端連結清單,
* 以便儲存值 value 。
*
* 函數隻對 REDIS_ENCODING_RAW 編碼的 value 進行檢查,
* 因為整數編碼的值不可能超長。
*/
void listTypeTryConversion(robj *subject, robj *value) {
// 確定 subject 為 ZIPLIST 編碼
if (subject->encoding != REDIS_ENCODING_ZIPLIST) return;
if (sdsEncodedObject(value) &&
// 看字元串是否過長,#define REDIS_LIST_MAX_ZIPLIST_VALUE 64
sdslen(value->ptr) > server.list_max_ziplist_value)
// 将編碼轉換為雙端連結清單
listTypeConvert(subject,REDIS_ENCODING_LINKEDLIST);
}
redis ziplist資料結構
ziplist又叫壓縮清單,整體的資料格式如下,暫時可以臨時看一看,後面會針對資料結構專門的文章來解釋。
/*
空白 ziplist 示例圖
area |<---- ziplist header ---->|<-- end -->|
size 4 bytes 4 bytes 2 bytes 1 byte
+---------+--------+-------+-----------+
component | zlbytes | zltail | zllen | zlend |
| | | | |
value | 1011 | 1010 | 0 | 1111 1111 |
+---------+--------+-------+-----------+
^
|
ZIPLIST_ENTRY_HEAD
&
address ZIPLIST_ENTRY_TAIL
&
ZIPLIST_ENTRY_END
非空 ziplist 示例圖
area |<---- ziplist header ---->|<----------- entries ------------->|<-end->|
size 4 bytes 4 bytes 2 bytes ? ? ? ? 1 byte
+---------+--------+-------+--------+--------+--------+--------+-------+
component | zlbytes | zltail | zllen | entry1 | entry2 | ... | entryN | zlend |
+---------+--------+-------+--------+--------+--------+--------+-------+
^ ^ ^
address | | |
ZIPLIST_ENTRY_HEAD | ZIPLIST_ENTRY_END
|
ZIPLIST_ENTRY_TAIL
*/
/*
* 儲存 ziplist 節點資訊的結構
*/
typedef struct zlentry {
// prevrawlen :前置節點的長度
// prevrawlensize :編碼 prevrawlen 所需的位元組大小
unsigned int prevrawlensize, prevrawlen;
// len :目前節點值的長度
// lensize :編碼 len 所需的位元組大小
unsigned int lensize, len;
// 目前節點 header 的大小
// 等于 prevrawlensize + lensize
unsigned int headersize;
// 目前節點值所使用的編碼類型
unsigned char encoding;
// 指向目前節點的指針
unsigned char *p;
}
redis linkedlist資料結構
雙向清單的格式是通用的資料結構,不用過多解釋大家都能了解了。
/*
* 雙端連結清單節點
*/
typedef struct listNode {
// 前置節點
struct listNode *prev;
// 後置節點
struct listNode *next;
// 節點的值
void *value;
} listNode;
/*
* 雙端連結清單疊代器
*/
typedef struct listIter {
// 目前疊代到的節點
listNode *next;
// 疊代的方向
int direction;
} listIter;
/*
* 雙端連結清單結構
*/
typedef struct list {
// 表頭節點
listNode *head;
// 表尾節點
listNode *tail;
// 節點值複制函數
void *(*dup)(void *ptr);
// 節點值釋放函數
void (*free)(void *ptr);
// 節點值對比函數
int (*match)(void *ptr, void *key);
// 連結清單所包含的節點數量
unsigned long len;
} list;