Redis底层数据结构-Quicklist源码分析
简介
quicklist是Redis 3.2中引入的数据结构,其本质是一个双向链表,链表的节点类型是ziplist,当ziplist节点过多,quicklist会退化成双向链表,以提高效率。
数据结构
quicklistNode
该结构为节点类型。
- prev:指向前驱节点
- next:指向后继节点
- zl:指向对应的压缩列表
- sz:压缩列表的大小
- encoding:采用的编码方式,1为原生,2代表使用LZF进行压缩
- container:为节点指向的容器类型,1代表none,2代表ziplist存储数据
- recompress:代表这个节点是否是压缩节点,如果是,则使用压缩节点前先进行解压缩,使用后重新压缩
- attempted_compress:测试时使用
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;
quicklistLZF
如果使用LZF算法进行压缩,节点指向的结构为quicklistLZF,其中sz表示compressed数组所占用字节大小。
typedef struct quicklistLZF {
unsigned int sz; /* LZF size in bytes*/
char compressed[];
} quicklistLZF;
quicklist
- head:指向头节点
- tail:指向尾节点
- count:quicklist中压缩列表的entry总数
- len:节点个数
- fill:每个节点的ziplist长度,可以通过参数
list-max-ziplist-size
配置节点所占内存大小 - compress:该值表示两端各有compress个节点不压缩
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 : 16; /* fill factor for individual nodes */ unsigned int compress : 16; /* depth of end nodes not to compress;0=off */ } quicklist;
quicklistEntry
- quicklist:指向当前元素所在的quicklist
- node:指向当前元素所在的节点
- zi:当前元素所在的ziplist
- value:当前节点的字符串内容
- longval:当前节点的整型值
- sz:节点的大小
- offset:该节点相对于整个ziplist的偏移量,即相当于第几个entry
typedef struct quicklistEntry { const quicklist *quicklist; quicklistNode *node; unsigned char *zi; unsigned char *value; long long longval; unsigned int sz; int offset; } quicklistEntry;
quicklistIter
用于quicklist遍历的迭代器。
- quicklist:指向对应的quicklist
- current:指向元素所在的节点
- zi:指向元素所在的ziplist
- offset:表示节点在ziplist中的偏移量
- direction:表示迭代器的方向
typedef struct quicklistIter { const quicklist *quicklist; quicklistNode *current; unsigned char *zi; long offset; /* offset in current ziplist */ int direction; } quicklistIter;
接口
quicklistPushHead
该接口在头部插入元素。传入参数分别是待插入的quicklist,插入的数据value,大小sz。返回指为0代表没有新建head节点,1代表新建了head节点
基本逻辑如下:
- 查看quicklist的head节点是否允许插入,可以就利用ziplist的接口进行插入。
- 否则新建head节点进行插入。
int quicklistPushHead(quicklist *quicklist, void *value, size_t sz) { quicklistNode *orig_head = quicklist->head; //检查头节点是否允许插入 if (likely( _quicklistNodeAllowInsert(quicklist->head, quicklist->fill, sz))) { //使用头节点中的ziplist接口进行插入 quicklist->head->zl = ziplistPush(quicklist->head->zl, value, sz, ZIPLIST_HEAD); quicklistNodeUpdateSz(quicklist->head); } else { //创建新节点进行插入 quicklistNode *node = quicklistCreateNode(); node->zl = ziplistPush(ziplistNew(), value, sz, ZIPLIST_HEAD); quicklistNodeUpdateSz(node); _quicklistInsertNodeBefore(quicklist, quicklist->head, node); } //更新对应数据 quicklist->count++; quicklist->head->count++; return (orig_head != quicklist->head); }
quicklistReplaceAtIndex
更改元素需要传入index,基本逻辑是:
- 删除原有元素
- 插入新元素
int quicklistReplaceAtIndex(quicklist *quicklist, long index, void *data, int sz) { quicklistEntry entry; //检查index位置的元素是否存在 if (likely(quicklistIndex(quicklist, index, &entry))) { //删除 entry.node->zl = ziplistDelete(entry.node->zl, &entry.zi); //添加 entry.node->zl = ziplistInsert(entry.node->zl, entry.zi, data, sz); //更新sz字段 quicklistNodeUpdateSz(entry.node); quicklistCompress(quicklist, entry.node); return 1; } else { return 0; } }
quicklistIndex
该方法主要通过元素的下标查找对应元素,找到index对应数据所在的节点,再调用ziplist的ziplistGet
得到Index对应的数据。
//idx为需要查找的下标, 结果写入entry,返回0代表没找到,1代表找到
int quicklistIndex(const quicklist *quicklist, const long long idx,
quicklistEntry *entry) {
quicklistNode *n;
unsigned long long accum = 0;
unsigned long long index;
//idx为负值,表示从尾部向头部的偏移量
int forward = idx < 0 ? 0 : 1; /* < 0 -> reverse, 0+ -> forward */
//初始化entry
initEntry(entry);
entry->quicklist = quicklist;
if (!forward) {
//idx小于0,将idx设置为(-idx) - 1,从尾部查找
index = (-idx) - 1;
n = quicklist->tail;
} else {
index = idx;
n = quicklist->head;
}
if (index >= quicklist->count)
return 0;
//遍历Node节点,找到对应的index元素
while (likely(n)) {
if ((accum + n->count) > index) {
break;
} else {
D("Skipping over (%p) %u at accum %lld", (void *)n, n->count,
accum);
accum += n->count;
n = forward ? n->next : n->prev;
}
}
if (!n)
return 0;
D("Found node: %p at accum %llu, idx %llu, sub+ %llu, sub- %llu", (void *)n,
accum, index, index - accum, (-index) - 1 + accum);
//计算index所在的ziplist的偏移量
entry->node = n;
if (forward) {
/* forward = normal head-to-tail offset. */
entry->offset = index - accum;
} else {
/* reverse = need negative offset for tail-to-head, so undo
* the result of the original if (index < 0) above. */
entry->offset = (-index) - 1 + accum;
}
quicklistDecompressNodeForUse(entry->node);
entry->zi = ziplistIndex(entry->node->zl, entry->offset);
//利用ziplistGet获取元素
ziplistGet(entry->zi, &entry->value, &entry->sz, &entry->longval);
/* The caller will use our result, so we don't re-compress here.
* The caller can recompress or delete the node as needed. */
return 1;
}
总结
本文主要介绍了Quicklist的基本数据结构,并对该结构的基本增删改查接口做了简要分析。
Redis底层数据结构-Quicklist源码分析
https://l1n.wang/2020/中间件/Redis/redis-quicklist/