引子
《Redis实践》中提到过,通过合理的使用短结构(即ziplist)可以节省存储内存,提高内存利用率。这里主要谈谈在Redis中如何对list、hash、set、zset这四种数据结构进行存储优化及原理。
ziplist压缩列表
首先我们知道在Redis中,list类型底层是一个双向链表结构<早期版本,在3.2之后改为了quicklist,下文介绍>,hash/set底层是散列表结构,而有序集合是散列+skip跳表结构。这种存储方式是一种结构化形式的存储。但如果列表、散列或者有序集合的长度或者体积较小时,Redis会选择一种名为ziplist(压缩列表)的数据结构来存储它们。
ziplist与常规存储结构不同,它以序列化的方式存储数据(读取的时候进行反序列化),是数据的一种非结构化表示,它更加紧凑,相当于对原结构数据的压缩,它避免了存储额外的指针和元数据,所以消耗内存少于常规结构。
但是由于ziplist的非结构化存储,在读写时需要进行反序列化和序列化,所以ziplist的不宜过大,这也是为什么只转换量小的结构体的原因。ziplist结构如下,ztail_offset用于快速的定位到最后一个节点,实现倒序遍历,也就是说 ziplist支持双向遍历。
struct ziplist<T>{
int32 zlbytes; //压缩列表占用字节数
int32 zltail_offset; //最后一个元素距离起始位置的偏移量,用于快速定位到最后一个节点
int16 zllength; //元素个数
T[] entries; //元素内容
int8 zlend; //结束位 0xFF
}
在redis.conf中,可以设置常规结构转换成ziplist结构的条件,如下
# 表示当hash表中的条目数小于512条<即键值对数量>
# 且每个value的长度不超过64字节时,就将该hash结构转换成ziplist结构存储。
hash-max-ziplist-entries 512
hash-max-ziplist-value 64
# 当存储个数小于512的时候,并且数据全为整型时 转换成ziplist。
# 否则退化为hash结构。
set-max-intset-entries 512
# 与上面的hash一样,只是这里为zset结构进行设置
zset-max-ziplist-entries 128
zset-max-ziplist-value 64
随着数据的增加,当常规结构中存储的数据突破了上述条件时,Redis会将ziplist压缩列表会退化成相应的常规结构,这样做的原因是,当ziplist压缩列表的体积越来越大时,操作这些数据结构的速度也会越来越慢,特别是当需要扫描整个列表的时候,因为它是一种非结构化存储,读取时Redis需要解码很多单独的节点。
当ziplist被转换成常规结构之后,即使以后常规结构再次符合了转换成ziplist结构的条件,redis也不会再进行转换。
实际生产环境中,关于上面配置值的设置,《Redis实战》中建议将压缩列表长度限制在1024个元素之内,并且每个元素体积不超过64字节。当然实际可能还得由应用的实际场景来定,但至少这应该是一很有意义的参考值。
quicklist 快速列表
在Redis3.2及以后,list的内部实现变成了quicklist而非ziplist或者传统的双端链表。quicklist是一个由ziplist组成的双向链表。 下面是quicklist和node的部分数据结构:
struct quicklist{
quicklistNode* head; //指向头结点
quicklistNode* tail; //指向尾节点
long count; //元素总数
int nodes; //quicklistNode节点的个数
int compressDepth; //压缩算法深度
...
}
struct quicklistNode{
quicklistNode* prev; //前一个节点
quicklistNode* next; //后一个节点
ziplist* zl; //压缩列表
int32 size; //ziplist大小
int16 count; //ziplist 中元素数量
int2 encoding; //编码形式 存储 ziplist 还是进行 LZF 压缩储存
...
}
redis.conf提供了以下参数来设置quicklist的相关属性
# quicklist中每个ziplist大小(默认8kb,也是官方的建议值)
# Lists are also encoded in a special way to save a lot of space.
# The number of entries allowed per internal list node can be specified
# as a fixed maximum size or a maximum number of elements.
# For a fixed maximum size, use -5 through -1, meaning:
# -5: max size: 64 Kb <-- not recommended for normal workloads
# -4: max size: 32 Kb <-- not recommended
# -3: max size: 16 Kb <-- probably not recommended
# -2: max size: 8 Kb <-- good
# -1: max size: 4 Kb <-- good
list-max-ziplist-size -2
# quicklist的压缩深度,代表两端不被压缩的个数。
# 默认是0,表示不压缩任何元素。
# 1表示“从head或tail开始,直到第一个节点之后才开始压缩”,也就是说第1个和最后一个不压缩
# N表示"从head或tail开始,直到第N个节点之后才开始压缩“,也就说前N个和最后N个不压缩。
list-compress-depth 0