redis中的set
Redis 的 SET 数据结构根据集合的大小和元素类型动态选择底层实现方式:
- 整数集合(IntSet):当集合中的所有元素都是整数且集合大小较小时,使用整数集合以节省内存。
- 哈希表(HashTable):当集合中包含非整数元素或集合大小较大时,使用哈希表以支持高效的动态操作。
键值对存储:哈希表使用键值对的方式存储集合中的元素,其中键是集合中的元素,值是一个固定值(如 NULL)。
Redis中的set和java中的set集合有相似之处,它的元素不会按照插入的先后顺序而存储,且元素是不允许重复的。set内部使用到了intset(整数集合)和hashtable(哈希表)两种方式来存储元素,如果set存储的元素是整数,且当元素个数小于512个会选择intset存储,目的是减少内存空间,遇到两种情况会发生变化,
就是当存储的元素个数达到512(通过set-max-intset-entries 配置)
或者添加了非整数值时如:‘b’,set会选择hashtable作为存储结构。
整数集合intset
整数集合intset是用来存储整数的集合,且存储是按照小到大的顺序来存储(可以二分查找),intset目的是用来节省内存,
当Redis集合类型的元素都是整数并且都处在64位有符号整数范围之内时,使用该结构体存储,我们看一下它的结构:查看源码
解释:
length: contents中元素的个数
contents:存储元素的数组,需要根据encoding来决定多少个字节表示一个元素
encoding: 编码类型,决定每个元素占用几个字节,有三种类型
虽然intset结构将contents属性声明为int8_t类型的数组,但实际上contents数组并不保存任何int8_t类型的值,contents数组的真正类型取决于encoding属性的值:
如果encoding属性的值为INTSET_ENC_INT16,那么contents就是一个int16_t类型的数组,数组里的每个项都是一个int16_t类型的整数值(最小值为-32768,最大值为32767 )
如果encoding属性的值为INTSET_ENC_INT32,那么contents就是一个int32_t类型的数组,数组里的每个项都是一个int32_t类型的整数值(最小值为-2147483648,最大值为2147483647 )
如果encoding属性的值为INTSET_ENC_INT64,那么contents就是一个int64_t类型的数组,数组里的每个项都是一个int64_t类型的整数值(最小值为-9223372036854775808,最大值为9223372036854775807)
intset会根据插入的值判断是否扩容,根据插入内容来修改encoding使用什么类型,从而决定contents每个元素的字节数,紧跟着对contents进行扩容。
intset的升级
IntSet 的升级机制
1. 触发条件
当尝试向 intset 中插入一个新元素,而该元素的类型超出了当前 intset 的编码范围时,会触发升级操作。例如:
- 如果当前 intset 的编码是 INTSET_ENC_INT16(2 字节整数),而新插入的元素值为 65536(需要 32 位整数表示),则会触发升级。
2. 升级过程
升级过程主要包括以下步骤:
1.确定新编码:根据新元素的大小,确定新的编码方式(如从 INTSET_ENC_INT16 升级到 INTSET_ENC_INT32)。
2.分配新空间:根据新的编码方式和当前元素数量(包括新元素),分配新的内存空间。
3.复制和转换现有元素:将现有元素从旧编码转换为新编码,并按顺序复制到新的内存空间。
4.插入新元素:将新元素插入到合适的位置,保持 intset 的有序性。
5.更新元数据:更新 intset 的 encoding 属性为新的编码方式,并递增 length 属性。
3. 示例
假设有一个 intset,当前编码为 INTSET_ENC_INT16,包含元素 {5, 10, 20}。现在尝试插入元素 50000,该值超出了 int16_t 的范围(-32768 到 32767),因此需要升级:
- 升级编码为 INTSET_ENC_INT32,每个整数占用 4 字节。
- 分配新的内存空间,大小为 4 * (3 + 1) 字节(现有 3 个元素,加上新元素)。
- 将现有元素 {5, 10, 20} 转换为 32 位整数,并按顺序复制到新的内存空间。
- 插入新元素 50000 到数组末尾。
- 更新 encoding 为 INTSET_ENC_INT32,并将 length 增加到 4。
4. 不支持降级
一旦 intset 升级到更大的编码方式,就不会再降级。即使后续删除了导致升级的元素,也不会将编码方式降回原来的较小类型。这是因为降级操作可能会带来额外的性能开销,而且 Redis 设计上倾向于避免频繁的结构转换。
5. 性能和内存优化
- 性能:升级操作虽然涉及内存分配和数据复制,但由于 intset 通常用于存储小集合,因此整体性能影响较小。
- 内存优化:通过动态升级,intset 能够在不同阶段选择最合适的编码方式,从而节省内存。
总结
Redis 的 intset 升级机制是一种灵活且高效的内存管理策略,能够在插入超出当前编码范围的元素时自动调整编码方式,确保数据结构的紧凑性和操作的高效性。