前面我们分析了redis中的ziplist相关实现,在结构上一个链表结构,但是在底层用的是一整块内存,在数据量较小的时候,其查找和分配都比较快,但是当数据量比较大的时候,查找和分配就比较慢了。
因此,redis提供了quicklist
数据结构,
quicklist的声明如下:
typedef struct quicklist {
quicklistNode *head; //
quicklistNode *tail;
unsigned long count; /* total count of all entries in all ziplists */
unsigned long len; /* number of quicklistNodes */
int fill : QL_FILL_BITS; /* fill factor for individual nodes */
unsigned int compress : QL_COMP_BITS; /* depth of end nodes not to compress;0=off */
unsigned int bookmark_count: QL_BM_BITS;
quicklistBookmark bookmarks[];
} quicklist;
typedef struct quicklistNode {
struct quicklistNode *prev;
struct quicklistNode *next;
unsigned char *zl;
unsigned int sz; /* ziplist size in bytes */
unsigned int count : 16; /* count of items in ziplist */
unsigned int encoding : 2; /* RAW==1 or LZF==2 */
unsigned int container : 2; /* NONE==1 or ZIPLIST==2 */
unsigned int recompress : 1; /* was this node previous compressed? */
unsigned int attempted_compress : 1; /* node can't compress; too small */
unsigned int extra : 10; /* more bits to steal for future usage */
} quicklistNode;
typedef struct quicklistEntry {
const quicklist *quicklist;
quicklistNode *node;
unsigned char *zi;
unsigned char *value;
long long longval;
unsigned int sz;
int offset;
} quicklistEntry;
quicklist底层是基于ziplist的,其中count
表示的是quicklist中所有的ziplist的entry
数量之和,而len表示的quicklistNode
之和。
默认可以通过quicklistCreate
创建一个空的quicklist
:
quicklist *quicklistCreate(void) {
struct quicklist *quicklist;
quicklist = zmalloc(sizeof(*quicklist));
quicklist->head = quicklist->tail = NULL;
quicklist->len = 0;
quicklist->count = 0;
quicklist->compress = 0;
quicklist->fill = -2;
quicklist->bookmark_count = 0;
return quicklist;
}
默认情况下,quicklist的容量大小为quicklist->fill = -2;
65534。
void quicklistPush(quicklist *quicklist, void *value, const size_t sz,
int where) {
if (where == QUICKLIST_HEAD) {
quicklistPushHead(quicklist, value, sz);
} else if (where == QUICKLIST_TAIL) {
quicklistPushTail(quicklist, value, sz);
}
}
插入数据quicklistPush
来进行插入。我们研究下是如何插入到尾结点中的:
int quicklistPushTail(quicklist *quicklist, void *value, size_t sz) {
quicklistNode *orig_tail = quicklist->tail;
assert(sz < UINT32_MAX); /* TODO: add support for quicklist nodes that are sds encoded (not zipped) */
if (likely(
_quicklistNodeAllowInsert(quicklist->tail, quicklist->fill, sz))) {
quicklist->tail->zl =
ziplistPush(quicklist->tail->zl, value, sz, ZIPLIST_TAIL);
quicklistNodeUpdateSz(quicklist->tail);
} else {
quicklistNode *node = quicklistCreateNode();
node->zl = ziplistPush(ziplistNew(), value, sz, ZIPLIST_TAIL);
quicklistNodeUpdateSz(node);
_quicklistInsertNodeAfter(quicklist, quicklist->tail, node);
}
quicklist->count++;
quicklist->tail->count++;
return (orig_tail != quicklist->tail);
}
```
1. 这里首先会判断当前quicklist的tail尾结点是否还有容量进行插入,如果有的话,那么会新建一个ziplist并插入到当前尾结点的zl代表的ziplist的尾部。
2. 如果当前quicklist的tail尾结点没有容量或者尾结点是空,那么先会创建一个`quicklistNode`,同时创建一个新的ziplist,将当前节点的zl指向该ziplist节点。(如果是要加入到头结点,ziplist中也是加入到头结点)
`quicklistNodeUpdateSz`会更新`quicklistNode`的`sz`字段,也就是该node下跌ziplist的字节长度
如果是新插入一个quicklistNode节点,并且前一个节点存在(这时候表明前一个节点已经到达了最大的容量),这时候会对前一个节点进行压缩,将前一个节点`quicklistNode`的zl(ziplist)转换为:quicklistLZF结构:
```
typedef struct quicklistLZF {
unsigned int sz; /* LZF size in bytes*/
char compressed[];
} quicklistLZF;
```
通过`lzf_compress`方法,将节点中的ziplist进行压缩。