编码类型
Redis 内部使用不同的编码方式来存储不同类型的数据,这些编码方式有助于在不同场景下优化内存使用和提高性能。以下是 Redis 中常见的数据类型及其对应的内部编码方式:
-
字符串(String):
-
当字符串值的长度小于等于 44 字节时,Redis 使用
int
编码存储,将字符串值转换为整数。 -
当字符串值的长度大于 44 字节时,Redis 使用
raw
编码存储,将字符串值作为字节数组存储。
-
-
列表(List):
-
列表类型的编码可以是
ziplist
或linkedlist
。-
ziplist
编码用于较小的列表,以紧凑的方式存储多个列表元素。 -
linkedlist
编码用于较大的列表,以链表结构存储列表元素,支持快速的插入和删除操作。
-
-
-
哈希(Hash):
-
哈希类型的编码可以是
ziplist
、hashtable
或quicklist
。-
ziplist
编码用于较小的哈希表,以紧凑的方式存储多个键值对。 -
hashtable
编码用于较大的哈希表,以哈希表结构存储键值对,支持快速的查找操作。 -
quicklist
编码在哈希表键值对数量超过一定阈值时使用,将多个ziplist
结合成一个链表,以降低内存碎片化。
-
-
-
集合(Set):
-
集合类型的编码可以是
intset
或hashtable
。-
intset
编码用于存储整数集合,以紧凑的方式存储多个整数值。 -
hashtable
编码用于存储一般性的集合,以哈希表结构存储集合元素,支持快速的查找操作。
-
-
-
有序集合(Sorted Set):
-
有序集合类型的编码可以是
ziplist
或skiplist
。-
ziplist
编码用于较小的有序集合,以紧凑的方式存储多个成员和分值对。 -
skiplist
编码用于较大的有序集合,以跳跃表结构存储成员和分值对,支持快速的范围查询操作。
-
-
编码码类型转换在Redis写入数据时自动完成, 这个转换过程是不可逆的, 转换规则只能从小内存编码向大内存编码转换
设置编码方式
在 Redis 中,可以通过配置文件或者在命令行中动态设置一些参数来调整内部数据结构的编码方式。以下是针对哈希(Hash)、列表(List)、集合(Set)、有序集合(Sorted Set)的一些常见内部编码相关的配置:
-
哈希(Hash):
-
hash-max-ziplist-entries
: 用于配置哈希对象编码为ziplist
时,ziplist 中最大的哈希表项数目。 -
hash-max-ziplist-value
: 用于配置哈希对象编码为ziplist
时,ziplist 中最大的值大小。 -
hash-max-ziplist-value
: 用于配置哈希对象编码为ziplist
时,ziplist 中最大的值大小。
-
-
列表(List):
-
list-max-ziplist-entries
: 用于配置列表对象编码为ziplist
时,ziplist 中最大的列表项数目。 -
list-max-ziplist-value
: 用于配置列表对象编码为ziplist
时,ziplist 中最大的值大小。
-
-
集合(Set):
-
set-max-intset-entries
: 用于配置集合对象编码为intset
时,intset 中最大的整数元素数目。
-
-
有序集合(Sorted Set):
-
zset-max-ziplist-entries
: 用于配置有序集合对象编码为ziplist
时,ziplist 中最大的有序集合项数目。 -
zset-max-ziplist-value
: 用于配置有序集合对象编码为ziplist
时,ziplist 中最大的值大小。
-
这些参数的配置可以在 Redis 的配置文件中设置,也可以在运行时使用 CONFIG SET
命令动态地进行设置。通过调整这些参数,可以根据实际需求来优化内存使用和性能表现。
编码详解
ziplist
ziplist
是用于紧凑存储列表和哈希等数据结构的一种内部编码方式,它具有紧凑、轻量级和高效的特点,适用于存储长度不固定且数据量较小的数据集。
ziplist 编码主要目的是为了节约内存, 采用紧凑的线性结构存储数据,相邻的数据项之间紧密相连,不使用额外的指针来存储数据项之间的关系。
ziplist 编码可以动态地扩展或缩小以容纳不同数量的元素,这使得它非常适合存储长度不固定的列表和哈希, 可以分别作为hash、 list、zset类型的底层数据结构实现。
数据结构
-
ziplist
由一系列紧密相连的节点组成,每个节点包含一个数据项或者一个指向数据项的指针。 -
每个节点都有一个前置节点长度字段和一个当前节点长度字段,用于确定节点的位置和长度。
-
数据项可以是整数、字符串或者其他二进制数据,根据需要动态调整存储方式。
-
zlbytes: 记录整个压缩列表所占字节长度, 方便重新调整ziplist空间。 类型是int-32, 长度为4字节。
-
zltail: 记录距离尾节点的偏移量, 方便尾节点弹出操作。 类型是int-32, 长度为4字节。
-
zllen: 记录压缩链表节点数量, 当长度超过216-2时需要遍历整个列表获取长度, 一般很少见。 类型是int-16, 长度为2字节。
-
entry: 记录具体的节点, 长度根据实际存储的数据而定。
-
prev_entry_bytes_length: 记录前一个节点所占空间, 用于快速定位上一个节点, 可实现列表反向迭代。
-
encoding: 标示当前节点编码和长度, 前两位表示编码类型: 字符串/整数, 其余位表示数据长度。
-
contents: 保存节点的值, 针对实际数据长度做内存占用优化。
-
zlend: 记录列表结尾, 占用一个字节。
-
优缺点
-
优点:
-
节省内存:
ziplist
可以将多个数据项存储在一个紧凑的结构中,从而节省内存空间。 -
高效存取:
ziplist
中的数据项紧密相连,因此在顺序访问时具有较高的效率。
-
-
缺点:
-
动态扩展代价较高:当
ziplist
需要扩展时,可能需要重新分配内存并进行数据复制,这可能会带来性能损耗。 -
不适用于大型数据:
ziplist
在数据量很大时可能会占用过多的内存,因此不适合存储大型数据集。
-
intset
intset
被设计为节省内存空间,用于紧凑存储整数集合的一种内部编码方式,它具有紧凑、有序和高效查询的特点,适用于存储整数值集合并且在内存占用和查询效率上都有较好的表现。
-
intset
使用紧凑的数组结构存储整数值,相邻的整数值存储在连续的内存块中,不使用额外的指针来存储整数值之间的关系。 -
整数值在
intset
中是有序存储的,这使得在进行范围查询等操作时具有较高的效率。 -
intset
可以存储不同长度的整数值,包括 16 位、32 位和 64 位整数。 -
使用intset编码的集合时, 尽量保持整数范围一致, 如都在int-16范围内。 防止个别大整数触发集合升级操作, 产生内存浪费。
数据结构
-
intset
由一个整数类型标识字段、一个整数数量字段和一个整数数组组成。 -
整数类型标识字段用于标识整数值的类型,以便在存取时正确解释整数值。
-
整数数量字段记录了
intset
中存储的整数值的数量。 -
整数数组按照整数值的大小顺序存储,其中较小的整数值存储在数组的前面,较大的整数值存储在数组的后面。
-
encoding: 整数表示类型, 根据集合内最长整数值确定类型, 整数类型划分为三种: int-16、 int-32、 int-64。
-
length: 表示集合元素个数。
-
contents: 整数数组, 按从小到大顺序保存。
优缺点
-
优点:
-
节省内存:
intset
使用紧凑的数组结构存储整数值,节省了额外的内存空间。 -
高效查询:由于整数值是有序存储的,因此在进行范围查询等操作时具有较高的效率。
-
-
缺点:
-
动态扩展代价较高:当
intset
需要扩展以容纳更多的整数值时,可能需要重新分配内存并进行数据复制,这可能会带来性能损耗。 -
只适用于整数值:
intset
只能存储整数值,不适用于存储其他类型的数据。
-
hashtable 与 ziplist
-
ziplist 编码:
-
当哈希表中的键值对数量较少,并且每个键值对的键和值的大小都较小时,Redis 使用 ziplist(压缩列表)来编码哈希表。
-
ziplist 是一种紧凑的、压缩的列表结构,可以将多个小的键值对存储在一个连续的内存区域中,节省内存空间。
-
ziplist长度需要控制在1000以内, 否则由于存取操作时间复杂度在O(n) 到O(n2) 之间, 长列表会导致CPU消耗严重, 得不偿失。
-
ziplist适合存储小对象, 对于大对象不但内存优化效果不明显还会增加命令操作耗时。
-
-
hashtable 编码:
-
当哈希表中的键值对数量较多,或者键或值的大小较大时,Redis 使用 hashtable 来编码哈希表。
-
hashtable 是一种标准的哈希表结构,使用链地址法来解决冲突,能够处理大量的键值对并保持良好的性能。
-
hash类型节省内存的原理是使用ziplist编码, 如果使用hashtable编码方式反而会增加内存消耗。
-
hashtable 编码的内部结构
-
ht[table]:表示一个哈希表的结构。其中,ht 是哈希表的头部,包含了哈希表的一些元信息,例如大小、已使用节点数量等信息;table 则是一个数组,每个元素是一个指向哈希表节点的指针。
-
哈希表节点(hash table node):每个节点表示一个键值对。节点结构包含了键(key)和值(value),以及指向下一个节点的指针(用于解决冲突)。
-
扩容和缩容:当哈希表中的键值对数量超过一定阈值时,Redis 会自动扩容哈希表的大小,以减少冲突并保持较好的性能;反之,当哈希表中的键值对数量减少到一定程度时,Redis 会自动缩小哈希表的大小,以节省内存空间。
参考资料:《Redis 开发与运维》