redis quicklist剖析
说明
本篇文章详细剖析了redis的quicklist结构。前面我们已经介绍了两种redis数据结构:adlist
和ziplist
,这两中数据结构各有各的优缺点。
adlist
: 双向链表,任意位置插入和删除非常方便,缺点也很显而易见,任意一个node都是独立的内存块,所以内存碎片化很严重ziplist
:压缩双向链表,一个链表就是一整块内存,同时如果元素值为整数的话可以进一步压缩,所以很省内存;缺点就是任意一次插入删除操作都会导致重新分配内存的操作,效率不高。
quicklist结构就是adlist
和ziplist
两种结构的中间体,整个结构分为两层:外层使用类似adlist
的结构串联,内层使用ziplist
来节约内存
为了进一步的优化内存,quicklist还有一个参数compress用来控制除了两头多少个节点外,中间的节点使用压缩算法进行压缩(quicklist使用lzf压缩)。例如如果compress==1,则除了头尾两个节点,中间的节点都将被压缩。
实现
为了实现这些功能,quicklist实现非常复杂,应该是redis中最复杂的一个结构体了。但是不考虑它的实现细节,它的一些实现这些功能的思想,可以很简单的通过它定义的一些结构体很容易的理解。
quicklistNode
1 | typedef struct quicklistNode { |
对应于adlist
的listNode结构,用做一个双向链表的节点。不同的是该节点内部的值指向的是一个ziplist/quicklistLZF数据结构,具体指向哪个结构通过encoding变量控制。
- prev, next:指向前后两个quicklistNode地址
- zl:如果当前node的数据没有被压缩(encoding==RAW),则zl指向一个ziplist结构,否则指向quicklistLZF结构
- sz:ziplist内存大小
- count:存放该节点的元素个数
- encoding:表示当前节点的数据是以什么编码的,RAW:原始数据,LZF:通过LZF压缩数据
- container:表示当前节点通过什么类型数据结构存储的,NONE:没有数据,ZIPLIST:通过ziplist存储,目前也就只有ziplist这一种
- recompress:如果该值为true,表示只是临时将数据解压用于使用,需要再次被压缩
- attempted_compress:用于测试用例
- extra:保留,暂未使用
quicklistLZF
1 | typedef struct quicklistLZF { |
quicklistNode中的ziplist压缩之后使用该结构体存储,quicklistNode中zl将会指向该结构体
quicklist
1 | typedef struct quicklist { |
- head,tail:指向该双向链表的头尾节点
- count:总元素个数(不是quicklistNode个数),所有node指向的ziplist的元素总数
- len:quicklistNode个数
- fill:控制每个quicklistNode中的ziplist大小,如果为正值,则表示以ziplist中元素个数为限制,但总的ziplist占用内存不能大于8K,如果未负值,则以ziplist占用内存大小来控制,-1:4k,-2:8k(redis默认),-3:16k,-4:32k,-5,64k
- compress:如果非0,表示头尾多少个节点不需要压缩,例如如果是2的话,表示除头尾各2个quicklistNode不被压缩外,其他都会被压缩,以减少内存
quicklistIter
1 | typedef struct quicklistIter { |
quicklist迭代器,不仅要知道当前指向哪个quicklistNode,还需要知道当前node中的ziplist的元素地址及偏移量
quicklistEntry
1 | typedef struct quicklistEntry { |
该结构体指向quicklist中具体的一个元素,quicklist中的get方法(quicklistIndex, quicklistNext)都是通过该结构体来返回元素的值。这里需要注意两点:
- 如果get方法返回正确,但是value指向null,则表示该元素被ziplist使用整形进行压缩了,返回的值存放在longval中。
- 如果value不为null,则value实际指向的是ziplist中的元素地址,所以应该假设这一块地址为只读的,不应该修改他,如果需要保存该value,则应该复制这一块的内存,以防止该指针会失效(ziplist插入/删除都会重新分配内存)
主要函数分析
时间复杂度不太好分析,但是肯定是优于ziplist,但差于adlist。所以直接看每个函数的声明及作用,具体细节可以查看我注释的redis源码
1 | // 创建一个空的quicklist,需要通过quicklistRelease释放 |