此篇文章是主要介绍Redis在数据存储方面的其中一种方式,压缩列表。本文会介绍1. 压缩列表(ziplist)的使用场景 2.如何达到节约内存的效果?3.压缩列表的存储格式 4. 连锁更新的问题 5. conf文件配置。在实践上的操作主要是对conf配置文件进行配置,具体上没有确切的一个值,更多是经验值。也有的项目会直接使用原本的默认值。此篇对于更好地理解一个数据库底层的存储逻辑会有一点帮助。修学储能,既要博,也要渊。希望这篇文章对同样也是在学习Redis的各位同伴有点用。
一、压缩列表(ziplist****)的使用场景:
Redis为了优化数据存储,节约内存,在列表、字典(哈希键)和有序集合的底层实现了使用压缩列表这一优化方案。
例如,假如一个哈希键里面存储的字符串比较短,那么Redis就会将它用压缩列表的格式去存储,即转换为字节数组存储。而一个哈希键内部存储的整数值比较小,同样也会把它存储为压缩列表的一个节点。同理,列表键的对小数据的存储跟哈希键的操作类似。
如此说来,压缩列表并不是开发者可以直接调用的Redis中的一种存储数据结构,而是Redis中为优化数据存储而在底层做的一项努力。理解好这点还是比较重要的。
二、如何达到节约内存的效果?
压缩列表是一种序列化的数据结构,这种数据结构的功能是将一系列数据与其编码信息存储在一块连续的内存区域,这块内存物理上是连续的。但逻辑上被分为多个组成部分,即节点。目的是为了在一定可控的时间复杂度条件下尽可能的减少不必要的内存开销,从而达到节省内存的效果。需要理解是怎么达到节约内存作用的,还需要去了解压缩列表的存储格式。
三、压缩列表的存储格式:
压缩列表(ziplist)是Redis列表键、哈希键和有序集合键的底层实现之一,其实质是一种序列化的数据存储结构。有别于普通情况下,Redis用双端链表表示列表,使用散列表表示哈希键,用散列表+跳跃表表示有序集合。当一个列表或者哈希字典/有序集合只包含很少内容,并且每一个列表项或者哈希项/有序集合项如果是小整数,或者比较短的字符串。那么Redis就会用压缩列表来做底层的实现。
压缩列表由一系列经Redis特殊编码的连续内存块组成,每一个内存块称为一个节点(entry),而一个压缩列表可以包含很多个节点。每个节点存储的数据格式可以是字节数组(中文字符串等都会转换为字节数组)或者整数值。
字节数组的长度可以是以下的其中一种:
1. 长度小于等于63字节(2的6次方)
2. 长度小于等于16383字节(2的14次方)
3. 长度小于等于4294967295字节(2的32次方)
整数值可能是以下六种中的其中一种:
1. 4位长,介于0-12之间的无符号整数
2. 1字节长的有符号整数
3. 3字节长的有符号整数
4. int16_t类型整数
5. int32_类型整数
6. int64_t类型整数
普通存储格式下和压缩列表存储格式下的不同点:
列表存储结构典型的为双端链表,每一个值都是用一个节点来表示,每个节点都会有指向前一个节点和后一个节点的指针,以及指向节点包含的字符串值的指针。而字符串值又分为3个部分存储,第一部分存储字符串长度,第二部分存储字符串值中剩余可用的字节量,第三部分存储的则是字符串数据本身。所以一个节点往往都需要存储3个指针、2个记录字符串信息的整数、字符串本省和一个额外的字节。总体上额外的开销是很大的(21字节)。
压缩列表节点的格式:
每一个节点都有previous_entry_length,encoding,content三个部分组成,在遍历压缩列表的时候是从后往前遍历的。
1. previous_entry_length记录了前一个节点的长度,只要用当前指针减去这个值就可以达到前一个节点的起始地址。
2. encoding记录了节点content属性所保存数据的类型和长度
3. content记录了一个节点的值
显然压缩列表这种方式节约了不少存储空间。但同时也会引发下面的问题。
四、连锁更新的问题:
一般而言如果前一个节点的整体长度小于254字节,previous_entry_length属性只需要1个字节的空间来保存这个长度值。而当前一个节点大于254字节的时候,previous_entry_length属性要用5个字节长的空间来记录长度值。
当长度为254字节左右的节点前插入一个新的节点的时候,需要增加previous_entry_length来记录这个节点到新节点的偏移量。这个时候,这个节点的长度肯定就大于254字节了。所以这个节点的后一个节点就不能只用一个字节的previous_entry_length来记录这个节点的信息了,而是需要5个字节来记录。如果连续多个节点的长度都为254字节左右,在其中的某一个节点前/后发生节点的插入和删除(删除的推理与插入相反,原本用5字节记录前一节点的可能变为1字节),都可能引发连锁的更新,显然,这样对系统地运行效率是很不利的。不过,在实际应用中这种情况还是比较少发生的。
而双端链表在节点的更新、增加和删除上显得就会“轻松”很多了。 因为每一个节点存储的信息都是相对独立的。
实践意义:
要预估一个节点大概占据多少字节的存储空间,适当地调整字段的存储格式而不要使存储的字段值占据存储空间落在254字节(除去encoding属性和previous_entry_length属性)左右。
Redis****中查看字符串和哈希键值的长度相关命令:
1. 查询字符串键对应的值长度
命令:
Strlen
例如:
127.0.0.1:6379> strlen m_name
(integer) 8
2. 查询哈希键某一个域长度
命令:
Hstrlen
例如:
127.0.0.1:6379> hstrlen good_list good_list1
(integer) 226
五、Conf****文件配置:
通过修改配置文件,可以控制是否使用压缩列表存储相关键的最大元素个数和最大元素的大小
Conf文件中的配置:
1.
[] -max-ziplist-entries : 表示对于键的最大元素个数,即一个键中在该指定值下的数量的节点个数都会用压缩列表来储存
[] -max-ziplist-value :表示压缩列表中每个节点的最大体积是多少字节
实际使用中,一个列表键/哈希键的某一个元素往往存储着比较大的信息量,会大于64字节,所以配置时很有可能会比64大,同时考虑到实际存储数据的容量大小以及上面谈到的previous_entry_length的大小问题,对[] -max-ziplist-value进行合理的配置。
配置文件内容:
############## ADVANCED CONFIG ##########################
哈希键
# Hashes are encoded using a memory efficient data structure when they have a
# small number of entries, and the biggest entry does not exceed a given
# threshold. These thresholds can be configured using the following directives.
hash-max-ziplist-entries 512
hash-max-ziplist-value 64
有序集合键
# Similarly to hashes and lists, sorted sets are also specially encoded in
# order to save a lot of space. This encoding is only used when the length and
# elements of a sorted set are below the following limits:
zset-max-ziplist-entries 128
zset-max-ziplist-value 64
列表键,比较特殊,直接使用制定大小kb字节数表示(有些conf文件的列表键与hash键的表达式没太大区别)
# 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
# Positive numbers mean store up to _exactly_ that number of elements
# per list node.
# The highest performing option is usually -2 (8 Kb size) or -1 (4 Kb size),
# but if your use case is unique, adjust the settings as necessary.
list-max-ziplist-size -2
案例:
修改配置前使用默认配置:
hash-max-ziplist-entries 512
hash-max-ziplist-value 64
127.0.0.1:6379> hstrlen good_list good_list1
(integer) 226
127.0.0.1:6379> object encoding good_list
"hashtable"
修改配置:
hash-max-ziplist-entries 512
hash-max-ziplist-value 254
注意:修改配置后需要重启服务器
127.0.0.1:6379> hstrlen good_list good_list1
(integer) 226
127.0.0.1:6379> object encoding good_list
"ziplist"
可以看到存储方式已将变为ziplist
较官方的压力测试和指导建议:
当一个压缩列表的元素数量上升到几千(实际使用可能远小于这个值)的时候,压缩列表的性能可能会下降,因为Redis在操作这种结构的时候,编解码会出现一定的压力。
压缩列表的长度限制在500-2000之内,每个元素体积限制在128字节或以下,压缩列表的的性能都会处于合理范围之内。
参考资料:
《redis设计与实现》