目录

1、quicklist概述

2、quicklist源码分析

2.1 定义

2.2 push操作

2.3 节点压缩

3、总结


1、quicklist概述

   quicklist是一个3.2版本之后新增的基础数据结构,是 redis 自定义的一种复杂数据结构,将ziplistadlist结合到了一个数据结构中。主要是作为list的基础数据结构。
在3.2之前,list是根据元素数量的多少采用ziplist或者adlist作为基础数据结构,3.2之后统一改用quicklist,从数据结构的角度来说quicklist结合了两种数据结构的优缺点,复杂但是实用:

  • 链表在插入,删除节点的时间复杂度很低;但是内存利用率低,且由于内存不连续容易产生内存碎片
  • 压缩表内存连续,存储效率高;但是插入和删除的成本太高,需要频繁的进行数据搬移、释放或申请内存

quicklist通过将每个压缩表用双向链表的方式连接起来,来寻求一种收益最大化。

2、quicklist源码分析

2.1 定义

首先是quicklist的节点quicklistNode

typedef struct quicklistNode {struct quicklistNode *prev; // 前一个节点struct quicklistNode *next; // 后一个节点unsigned char *zl;  // ziplistunsigned int sz;             // ziplist的内存大小unsigned int count : 16;     // zpilist中数据项的个数unsigned int encoding : 2;   // 1为ziplist 2是LZF压缩存储方式unsigned int container : 2;  unsigned int recompress : 1;   // 压缩标志, 为1 是压缩unsigned int attempted_compress : 1; // 节点是否能够被压缩,只用在测试unsigned int extra : 10; /* more bits to steal for future usage */
} quicklistNode;

quicklistNode实际上就是对ziplist的进一步封装,其中包括:

  • 指向前后压缩表节点的两个指针
  • zlziplist指针
  • szziplist的内存占用大小
  • countziplist内部数据的个数
  • encodingziplist编码方式,1为默认方式,2为LZF数据压缩方式
  • recompress:是否压缩,1表示压缩
    这里从变量count开始,都采用了位域的方式进行数据的内存声明,使得6个unsigned int变量只用到了一个unsigned int的内存大小。

    C语言支持位域的方式对结构体中的数据进行声明,也就是可以指定一个类型占用几位:
    1) 如果相邻位域字段的类型相同,且其位宽之和小于类型的sizeof大小,则后面的字段将紧邻前一个字段存储,直到不能容纳为止;
    2) 如果相邻位域字段的类型相同,但其位宽之和大于类型的sizeof大小,则后面的字段将从新的存储单元开始,其偏移量为其类型大小的整数倍;
    3) 如果相邻的位域字段的类型不同,则各编译器的具体实现有差异,VC6采取不压缩方式,Dev-C++采取压缩方式;
    4) 如果位域字段之间穿插着非位域字段,则不进行压缩;
    5) 整个结构体的总大小为最宽基本类型成员大小的整数倍。

sizeof(quicklistNode); // output:32,通过位域的声明方式,quicklistNode可以节省24个字节。

通过quicklistquicklistNode连接起来就是一个完整的数据结构了

typedef struct quicklist {quicklistNode *head;    // 头结点quicklistNode *tail;    // 尾节点unsigned long count;    // 所有数据的数量unsigned int len;       // quicklist节点数量int fill : 16;          // 单个ziplist的大小限制unsigned int compress : 16;   // 压缩深度
} quicklist;

由于quicklist结构包含了压缩表和链表,那么每个quicklistNode的大小就是一个需要仔细考量的点。如果单个quicklistNode存储的数据太多,就会影响插入效率;但是如果单个quicklistNode太小,就会变得跟链表一样造成空间浪费。
quicklist通过fill对单个quicklistNode的大小进行限制:fill可以被赋值为正整数或负整数,当fill为负数时:

  • -1:单个节点最多存储4kb
  • -2:单个节点最多存储8kb
  • -3:单个节点最多存储16kb
  • -4:单个节点最多存储32kb
  • -5:单个节点最多存储64kb
  • 为正数时,表示单个节点最大允许的元素个数,最大为32768个
#define FILL_MAX (1 << 15)  // 32768
void quicklistSetFill(quicklist *quicklist, int fill) { // set ziplist的单个节点最大存储数据量if (fill > FILL_MAX) {  // 个数fill = FILL_MAX;} else if (fill < -5) { // 内存大小fill = -5;}quicklist->fill = fill;
}

在 redis 内部使用中,默认的最大单节点数据量设置是-2,也就是8kb

图2.1 quicklsit结构图

2.2 push操作

  quicklist只能在头尾插入节点,以在头部插入节点为例:

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的api在头部插入数据quicklistNodeUpdateSz(quicklist->head); // 更新节点的sz} else {    // 需要新增节点quicklistNode *node = quicklistCreateNode();    // 新建节点node->zl = ziplistPush(ziplistNew(), value, sz, ZIPLIST_HEAD);  // 新建一个ziplist并插入一个节点quicklistNodeUpdateSz(node);    // 更新节点的sz_quicklistInsertNodeBefore(quicklist, quicklist->head, node);   // 将新节点插入到头节点之前}quicklist->count++; // count自增quicklist->head->count++;return (orig_head != quicklist->head);  // 返回0为用已有节点 返回1为新建节点
}

quicklist的主要操作基本都是复用ziplist的api,其中likely是针对条件语句的优化,告知编译器这种情况很可能出现,让编译器针对这种条件进行优化;与之对应的还有unlikely。由于绝大部分时候都不需要新增节点,因此用likely做了优化
_quicklistNodeAllowInsert函数中,针对单个节点的内存大小做了校验

REDIS_STATIC int _quicklistNodeAllowInsert(const quicklistNode *node,const int fill, const size_t sz) {   // 判断当前node是否还能插入数据if (unlikely(!node))return 0;int ziplist_overhead;/* size of previous offset */if (sz < 254)   // 小于254时后一个节点的pre只有1字节,否则为5字节ziplist_overhead = 1;elseziplist_overhead = 5;/* size of forward offset */if (sz < 64)    // 小于64字节当前节点的encoding为1ziplist_overhead += 1;else if (likely(sz < 16384))    // 小于16384 encoding为2字节ziplist_overhead += 2;else    // encoding为5字节ziplist_overhead += 5;/* new_sz overestimates if 'sz' encodes to an integer type */unsigned int new_sz = node->sz + sz + ziplist_overhead; // 忽略了连锁更新的情况if (likely(_quicklistNodeSizeMeetsOptimizationRequirement(new_sz, fill)))   // // 校验fill为负数是否超过单存储限制return 1;else if (!sizeMeetsSafetyLimit(new_sz)) // 校验单个节点是否超过8kb,主要防止fill为正数时单个节点内存过大return 0;else if ((int)node->count < fill)   // fill为正数是否超过存储限制return 1;elsereturn 0;
}

同样,因为默认的fill为-2,所以针对为负数并且不会超过单个节点存储限制的条件做了likely优化;除此之外在计算的时候还忽略了ziplist可能发生的连锁更新;以及fill为正数时单个节点不能超过8kb

2.3 节点压缩

由于list这个结构大部分时候只会用到头尾的数据,因此 redis 利用lzf算法对节点中间的元素进行压缩,以达到节省内存空间的效果。压缩节点的结构体和具体函数如下:

typedef struct quicklistLZF {  // lzf结构体unsigned int sz; /* LZF size in bytes*/char compressed[];
} quicklistLZF;REDIS_STATIC int __quicklistCompressNode(quicklistNode *node) { // 压缩节点
#ifdef REDIS_TESTnode->attempted_compress = 1;
#endif/* Don't bother compressing small values */if (node->sz < MIN_COMPRESS_BYTES)  // 小于48字节不进行压缩return 0;quicklistLZF *lzf = zmalloc(sizeof(*lzf) + node->sz);/* Cancel if compression fails or doesn't compress small enough */if (((lzf->sz = lzf_compress(node->zl, node->sz, lzf->compressed,node->sz)) == 0) ||lzf->sz + MIN_COMPRESS_IMPROVE >= node->sz) {   // 如果压缩失败或压缩后节省的空间不到8字节放弃压缩/* lzf_compress aborts/rejects compression if value not compressable. */zfree(lzf);return 0;}lzf = zrealloc(lzf, sizeof(*lzf) + lzf->sz);    // 重新分配内存zfree(node->zl);    // 释放原有节点node->zl = (unsigned char *)lzf;    // 将压缩节点赋值给nodenode->encoding = QUICKLIST_NODE_ENCODING_LZF;   // 记录编码node->recompress = 0;return 1;
}

quicklist结构体中还有一个compress变量是用来控制压缩的深度,例如compress为1表示出了头尾节点其他全部都进行lzf压缩。

3、总结

quicklist除了常用的增删改查外还提供了merge、将ziplist转换为quicklist等api,这里就不详解了,可以具体查quicklist.hquicklist.c文件。

  • quicklist是 redis 在ziplistadlist两种数据结构的基础上融合而成的一个实用的复杂数据结构
  • quicklist在3.2之后取代adlistziplist作为list的基础数据类型
  • quicklist的大部分api都是直接复用ziplist
  • quicklist的单个节点最大存储默认为8kb
  • quicklist提供了基于lzf算法的压缩api,通过将不常用的中间节点数据压缩达到节省内存的目的

redis源码剖析(7):基础数据结构quicklist相关推荐

  1. redis源码剖析(3):基础数据结构dict

    目录 1.dict概述 2.字典的定义 3.哈希算法 4.字典的初始化及新增键值对 4.1 字典初始化 4.2 新增键值对 5.rehash(重新散列)操作 5.1 rehash操作方式 5.2 re ...

  2. 【Redis源码剖析】 - Redis内置数据结构之压缩列表ziplist

    在前面的一篇文章[Redis源码剖析] - Redis内置数据结构之双向链表中,我们介绍了Redis封装的一种"传统"双向链表list,分别使用prev.next指针来指向当前节点 ...

  3. 【Redis源码剖析】 - Redis持久化之RDB

    原创作品,转载请标明:http://blog.csdn.net/xiejingfa/article/details/51553370 Redis源码剖析系列文章汇总:传送门 Redis是一个高效的内存 ...

  4. redis源码剖析(十五)——客户端思维导图整理

    redis源码剖析(十五)--客户端执行逻辑结构整理 加载略慢

  5. Redis源码剖析和注释(十六)---- Redis输入输出的抽象(rio)

    Redis源码剖析和注释(十六)---- Redis输入输出的抽象(rio) . https://blog.csdn.net/men_wen/article/details/71131550 Redi ...

  6. 【Redis源码剖析】 - Redis IO操作之rio

    原创作品,转载请标明:http://blog.csdn.net/xiejingfa/article/details/51433696 Redis源码剖析系列文章汇总:传送门 Reids内部封装了一个I ...

  7. Redis源码剖析之GEO——Redis是如何高效检索地理位置的?

    Redis GEO 用做存储地理位置信息,并对存储的信息进行操作.通过geo相关的命令,可以很容易在redis中存储和使用经纬度坐标信息.Redis中提供的Geo命令有如下几个: geoadd:添加经 ...

  8. Redis源码剖析之内存淘汰策略(Evict)

    文章目录 何为Evict 如何Evict Redis中的Evict策略 源码剖析 LRU具体实现 LFU具体实现 LFU计数器增长 LFU计数器衰减 evict执行过程 evict何时执行 evict ...

  9. Redis源码分析:基础概念介绍与启动概述

    Redis源码分析 基于Redis-5.0.4版本,进行基础的源码分析,主要就是分析一些平常使用过程中的内容.仅作为相关内容的学习记录,有关Redis源码学习阅读比较广泛的便是<Redis设计与 ...

最新文章

  1. 基于OHCI的USB主机 —— UFI数据结构1
  2. PHP网站如何搬迁,如何搬迁DedeCMS站点数据
  3. 帕斯卡三角形杨辉三角
  4. cordova监听事件中调用其他方法_Laravel模型事件的实现原理详解
  5. vim 函数列表插件
  6. window搭建python环境
  7. java画笔覆盖在界面_Java画笔的简单实用方法
  8. 50行代码实现3D模拟真实撒金币动效
  9. Android BlueDroid(三):BlueDroid蓝牙开启过程enable
  10. Android Studio运行程序出现Session ‘app’: Error Launching activity 解决办法
  11. Morrios灵敏度分析法
  12. 基于CEP的量化交易平台建设
  13. java++pdf文档合并_Java多个PDF文件合并成一个PDF文件-Go语言中文社区
  14. 【Power BI】分析仪在餐饮业中的应用
  15. Pytorch Note46 生成对抗网络的数学原理
  16. 作为产品经理的你,画原型图时崩溃过吗?
  17. app切换到后台一分钟后锁定,需要输入手势密码才能打开(程序锁)
  18. BugkuCTF web18_秋名山车神 writeup
  19. 存储基础知识之存储介质(机械式硬盘)
  20. word论文排版插件_用这个Word插件,瞬间完成一键排版

热门文章

  1. sap采购申请自动转采购订单_SAP idoc功能够强大: 采购订单修改自动触发销售订单修改...
  2. android工具栏设为底层,Android 隐藏底部工具栏
  3. 修复jqgrid setgridparam postdata 的多次查询条件累加
  4. Ubuntu18.04安装CUDA10.1和cuDNN v7.6.5
  5. smokeping的启动脚本
  6. outermost shell_outermost是什么意思_outermost怎么读_outermost翻译_用法_发音_词组_同反义词_最外面的_离中心最远的-新东方在线英语词典...
  7. 从二进制数据流中构造GDAL可以读取的图像数据(C#)
  8. 修改GDAL库支持IRSP6数据
  9. 学术英语视听说2听力原文_做英语听力题有哪些非常实用的小技巧?
  10. Arcgis Javascript那些事儿(九)--自定义infowindow