引子
《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支持双向遍历。
在redis.conf中,可以设置常规结构转换成ziplist结构的条件,如下
# 表示当hash表中的条目数小于512条<即键值对数量># 且每个value的长度不超过64字节时,就将该hash结构转换成ziplist结构存储。hash-max-ziplist-entries 512hash-max-ziplist-value 64# 当存储个数小于512的时候,并且数据全为整型时 转换成ziplist。# 否则退化为hash结构。set-max-intset-entries 512# 与上面的hash一样,只是这里为zset结构进行设置zset-max-ziplist-entries 128zset-max-ziplist-value 64 随着数据的增加,当常规结构中存储的数据突破了上述条件时,Redis会将ziplist压缩列表会退化成相应的常规结构,这样做的原因是,当ziplist压缩列表的体积越来越大时,操作这些数据结构的速度也会越来越慢,特别是当需要扫描整个列表的时候,因为它是一种非结构化存储,读取时Redis需要解码很多单独的节点。
当ziplist被转换成常规结构之后,即使以后常规结构再次符合了转换成ziplist结构的条件,redis也不会再进行转换。
实际生产环境中,关于上面配置值的设置,《Redis实战》中建议将压缩列表长度限制在1024个元素之内,并且每个元素体积不超过64字节。当然实际可能还得由应用的实际场景来定,但至少这应该是一很有意义的参考值。
quicklist 快速列表
在Redis3.2及以后,list的内部实现变成了quicklist而非ziplist或者传统的双端链表。quicklist是一个由ziplist组成的双向链表。 下面是quicklist和node的部分数据结构:
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 <-- goodlist-max-ziplist-size -2# quicklist的压缩深度,代表两端不被压缩的个数。# 默认是0,表示不压缩任何元素。# 1表示“从head或tail开始,直到第一个节点之后才开始压缩”,也就是说第1个和最后一个不压缩# N表示"从head或tail开始,直到第N个节点之后才开始压缩“,也就说前N个和最后N个不压缩。list-compress-depth 0