Redis源码剖析之快速列表(quicklist)( 二 )
从上文中我们已经了解了一个quicklist某个时刻在内存中的样子 , 接下我们来看下它是如何在数据插入删除时变化的 。
quicklist的操作创建/* 创建一个新的quicklist. * 使用quicklistRelease()释放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;}
create就没啥好说的了 , 但这里需要提醒下 , fill值默认是-2 , 也就是说每个quicklistNode中的ziplist最长是8k字节 , 可以更具自己业务需求调整具体配置 。
头插和尾插对于list而已 , 头部或者尾部插入是最常见的操作了 , 但其实头插和尾插还算是比较简单 。
/* 在quicklist的头部插入一条数据* 如果在已存在节点插入 , 返回0 * 如果是在新的头结点插入 , 返回1 */int quicklistPushHead(quicklist *quicklist, void *value, size_t sz) {quicklistNode *orig_head = quicklist->head;if (likely(_quicklistNodeAllowInsert(quicklist->head, quicklist->fill, sz))) {quicklist->head->zl =ziplistPush(quicklist->head->zl, value, sz, ZIPLIST_HEAD);// 在头结点对应的ziplist中插入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);}/* 在quicklist的尾部插入一条数据* 如果在已存在节点插入 , 返回0 * 如果是在新的头结点插入 , 返回1 */int quicklistPushTail(quicklist *quicklist, void *value, size_t sz) {quicklistNode *orig_tail = quicklist->tail;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);}
头插和尾插都调用了_quicklistNodeAllowInsert
先判断了是否能直接在当前头|尾节点能插入 , 如果能就直接插入到对应的ziplist里 , 否则就需要新建一个新节点再操作了 。还记得上文中我们说的fill
字段吗 , _quicklistNodeAllowInsert
其实就是根据fill的具体值来判断是否已经超过最大容量 。
特定位置插入头插尾插比较简单 , 但quicklist在非头尾插入就比较繁琐了 , 因为需要考虑到插入位置、前节点、后节点的存储情况 。
/* 在一个已经存在的entry前面或者后面插入一个新的entry* 如果after==1表示插入到后面 , 否则是插入到前面*/REDIS_STATIC void _quicklistInsert(quicklist *quicklist, quicklistEntry *entry,void *value, const size_t sz, int after) {int full = 0, at_tail = 0, at_head = 0, full_next = 0, full_prev = 0;int fill = quicklist->fill;quicklistNode *node = entry->node;quicklistNode *new_node = NULL;if (!node) {/* 如果entry中未填node , 则重新创建一个node并插入到quicklist中 */D("No node given!");new_node = quicklistCreateNode();new_node->zl = ziplistPush(ziplistNew(), value, sz, ZIPLIST_HEAD);__quicklistInsertNode(quicklist, NULL, new_node, after);new_node->count++;quicklist->count++;return;}/* 检查要插入的节点是否是满的 */if (!_quicklistNodeAllowInsert(node, fill, sz)) {D("Current node is full with count %d with requested fill %lu",node->count, fill);full = 1;}if (afterat_tail = 1;if (!_quicklistNodeAllowInsert(node->next, fill, sz)) {D("Next node is full too.");full_next = 1;}}if (!afterat_head = 1;if (!_quicklistNodeAllowInsert(node->prev, fill, sz)) {D("Prev node is full too.");full_prev = 1;}}/* 不确定把新元素插到哪 */if (!fullquicklistDecompressNodeForUse(node);unsigned char *next = ziplistNext(node->zl, entry->zi);if (next == NULL) {node->zl = ziplistPush(node->zl, value, sz, ZIPLIST_TAIL);} else {node->zl = ziplistInsert(node->zl, next, value, sz);}node->count++;quicklistNodeUpdateSz(node);quicklistRecompressOnly(quicklist, node);} else if (!fullquicklistDecompressNodeForUse(node);node->zl = ziplistInsert(node->zl, entry->zi, value, sz);node->count++;quicklistNodeUpdateSz(node);quicklistRecompressOnly(quicklist, node);} else if (fullinserting next node head");new_node = node->next;quicklistDecompressNodeForUse(new_node);new_node->zl = ziplistPush(new_node->zl, value, sz, ZIPLIST_HEAD);new_node->count++;quicklistNodeUpdateSz(new_node);quicklistRecompressOnly(quicklist, new_node);} else if (fullnew_node = node->prev;quicklistDecompressNodeForUse(new_node);new_node->zl = ziplistPush(new_node->zl, value, sz, ZIPLIST_TAIL);new_node->count++;quicklistNodeUpdateSz(new_node);quicklistRecompressOnly(quicklist, new_node);} else if (fullnew_node = quicklistCreateNode();new_node->zl = ziplistPush(ziplistNew(), value, sz, ZIPLIST_HEAD);new_node->count++;quicklistNodeUpdateSz(new_node);__quicklistInsertNode(quicklist, node, new_node, after);} else if (full) {/* 否则 , 当前节点是满的 , 我们需要把它分裂成两个新节点 , 一般用于插入到当前节点ziplist中间某个位置时 */D("\tsplitting node...");quicklistDecompressNodeForUse(node);new_node = _quicklistSplitNode(node, entry->offset, after);new_node->zl = ziplistPush(new_node->zl, value, sz,after ? ZIPLIST_HEAD : ZIPLIST_TAIL);new_node->count++;quicklistNodeUpdateSz(new_node);__quicklistInsertNode(quicklist, node, new_node, after);_quicklistMergeNodes(quicklist, node);}quicklist->count++;}
- 次世代大作标配 光线追踪DLSS深度剖析
- java 从零实现属于你的 redis 分布式锁
- Redis集群做法的难点,百万并发客户端「实战」
- C语言火车订单管理源码
- HFL Redis_10_set类型底层存储数据结构
- 为什么 Redis 单线程能支撑高并发?
- 清华教授整理535本编程电子书+视频教程+项目源码限时领取
- Redis流行的原因
- 掌握会用了,为什么还要源码学习?
- 阿里爆款SpringBoot项目实战PDF+源码+视频分享