虽然没有完全去理解它的源码,但我们也可以通过这篇文章来熟悉 redis 的一个设计思路。并知道它是怎么一步一步优化过来的。让我们有一个大概的性能认知。 ...
list 基本功能
单链表在学习 redis 的 list 实现之前,我们先来看一下单链表 list 怎么实现: 每一个节点都有一个向后的指针(引用)指向下一个节点,最后一个节点指向NULL表示结束,有一个Head(头)指针指向第一个节点表示开始。 类似于这样,新建和删除虽然只需要 增加一个节点: 删除一个节点: 双向链表双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点。 特点:
这好像就解决 redis 可以实现前后都可以遍历的问题了。 那么我们来看看 redis 的链表 怎么处理的: 再来看一下它的结构体定义源码 ListNode: typedef struct listNode { // 前驱节点 struct listNode *prev; // 后继节点 struct listNode *next; // 节点的值 void *value; } listNode; 登录后复制 List: typedef struct listNode { // 表头节点 listNode *head; // 表尾节点 listNode *tail; // 链表所包含的节点数量 unsigned long len; // 节点值复制函数 void *(*dup)(void *ptr); // 节点值释放函数 void *(*free)(void *ptr); // 节点值对比函数 int (*match)(void *ptr,void *key) } list; 登录后复制 redis 链表的特性:
由于 list 还存在一个内存分配不连续,并引发的内存碎片的问题,是否有办法让他们的内存优化一下? redis 压缩列表ZipList不是基础数据结构,是Redis自己设计的一种数据存储结构。它有点类似数组,通过一片连续的内存空间来存储数据。 与数组不同的是,它允许所存储的列表元素所占据的内存空间不同。说到压缩这两个字,大家第一时间可能想到的就是节省内存。之所以说这种存储结构节省内存,是相对数组而言的。 我们都知道,数组要求每个元素存储空间的大小相同,如果我们要存储不同长度的字符串,就要以最大长度的字符串所占的存储空间作为字符串数组每个元素存储空间的大小(假如是50字节)。 因此在字符数数值中存储小于50字节长度的字符串时就会浪费一部分存储空间。 数组 的优势就是占用一片连续的空间,可以很好地利用CPU缓存快速访问数据。 如果既想保留数组的这种优势又想节省存储空间,那么我们可以对数组进行压缩: 不过,有一个问题,在遍历压缩列表的时候我们并不知道每个元素占用的内存大小是多少,因此无法计算出下一个元素的具体起始位置。 但是转念一想,如果每个元素访问之前我们能有它的长度,那么不就可以解决这个问题了嘛。 接下来我们看看Redis是如何通过实现ZipList使之结合既保留了数组的优点又节省了内存。
ZipList变量的读取和赋值都是通过宏来实现的,代码片段如下: //返回整个压缩列表的总字节 #define ZIPLIST_BYTES(zl) (*((uint32_t*)(zl))) //返回压缩列表的tail_offset变量,方便获取最后一个节点的位置 #define ZIPLIST_TAIL_OFFSET(zl) (*((uint32_t*)((zl)+sizeof(uint32_t)))) //返回压缩列表的节点数量 #define ZIPLIST_LENGTH(zl) (*((uint16_t*)((zl)+sizeof(uint32_t)*2))) //返回压缩列表的表头的字节数 //(内存字节数zlbytes,最后一个节点地址ztail_offset,节点总数量zllength) #define ZIPLIST_HEADER_SIZE (sizeof(uint32_t)*2+sizeof(uint16_t)) //返回压缩列表最后结尾的字节数 #define ZIPLIST_END_SIZE (sizeof(uint8_t)) //返回压缩列表首节点地址 #define ZIPLIST_ENTRY_HEAD(zl) ((zl)+ZIPLIST_HEADER_SIZE) //返回压缩列表尾节点地址 #define ZIPLIST_ENTRY_TAIL(zl) ((zl)+intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))) //返回压缩列表最后结尾的地址 #define ZIPLIST_ENTRY_END(zl) ((zl)+intrev32ifbe(ZIPLIST_BYTES(zl))-1) 登录后复制 对此,可以通过定义的结构体和对应的操作定义知道大概 redis 设计 压缩list 的思路; 这种方式虽然节约了空间,但是会存在每次插入和创建存在内存重新分配的问题。 quicklistquicklist是Redis 3.2中新引入的数据结构,能够在时间效率和空间效率间实现较好的折中。Redis中对quciklist的注释为A doubly linked list of ziplists。顾名思义,quicklist是一个双向链表,链表中的每个节点是一个ziplist结构。quicklist可以看成是用双向链表将若干小型的ziplist连接到一起组成的一种数据结构。 当ziplist节点个数过多,quicklist退化为双向链表,一个极端的情况就是每个ziplist节点只包含一个entry,即只有一个元素。当ziplist元素个数过少时,quicklist可退化为ziplist,一种极端的情况就是quicklist中只有一个ziplist节点。 quicklist 结构定义: typedef struct quicklist { // 指向quicklist的首节点 quicklistNode *head; // 指向quicklist的尾节点 quicklistNode *tail; // quicklist中元素总数 unsigned long count; /* total count of all entries in all ziplists */ // quicklistNode节点个数 unsigned long len; /* number of quicklistNodes */ // ziplist大小设置,存放list-max-ziplist-size参数的值 int fill : 16; /* fill factor for individual nodes */ // 节点压缩深度设置,存放list-compress-depth参数的值 unsigned int compress : 16; /* depth of end nodes not to compress;0=off */ unsigned int bookmark_count: 4; quicklistBookmark bookmarks[]; } quicklist; typedef struct quicklistBookmark { quicklistNode *node; char *name; } quicklistBookmark; 登录后复制 quicklistNode定义如下: 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; 登录后复制 redis为了节省内存空间,会将quicklist的节点用LZF压缩后存储,但这里不是全部压缩,可以配置compress的值,compress为0表示所有节点都不压缩,否则就表示从两端开始有多少个节点不压缩;compress如果是1,表示从两端开始,有1个节点不做LZF压缩。compress默认是0(不压缩),具体可以根据你们业务实际使用场景去配置。 redis 的配置文件可以配置该参数
还有个fill字段,它的含义是每个quicknode的节点最大容量,不同的数值有不同的含义,默认是-2,当然也可以配置为其他数值;
为什么会有配置提供出来呢?
结束语虽然没有完全去理解它的源码,但我们也可以通过这篇文章来熟悉 redis 的一个设计思路。并知道它是怎么一步一步优化过来的。让我们有一个大概的性能认知。 |