Redis 的 intset 和 hashtable 编码转换条件

在 Redis 的 Set 类型底层实现中,intset 和 hashtable 是两种最常见的编码方式。理解它们之间的转换条件,不仅是面试中的高频考点,也是掌握 Redis 内存优化思想的关键。本文将从底层结构出发,系统梳理 intset 与 hashtable 的转换触发条件,并延伸讨论相关的配置参数与实战建议。

一、先搞清楚:Set 类型到底有几种编码?

Redis 的 Set 类型底层并不是只有一种数据结构,而是根据元素特征动态选择编码,常见的有:

  • intset(整数集合):当集合中所有元素都是整数,且元素数量不超过 set-max-intset-entries 时使用。
  • hashtable(字典):当集合中包含非整数元素,或整数元素数量超过阈值时使用。
  • listpack(旧版本为 ziplist):Redis 7.2 之前,当集合元素较少且元素为字符串时,可能使用 listpack/ziplist 编码。需要注意,从 Redis 7.2 开始,Set 类型不再使用 listpack 编码,小集合也直接使用 hashtable。

因此,讨论 intset 和 hashtable 的转换,核心是围绕整数集合何时退化为字典这一问题展开。

二、intset 的结构回顾

intset 是 Redis 为整数集合专门设计的一种紧凑结构,定义大致如下:

typedef struct intset {
    uint32_t encoding;   // 编码方式:int16 / int32 / int64
    uint32_t length;     // 元素个数
    int8_t contents[];   // 真正存储数据的柔性数组
} intset;

它的特点非常鲜明:

  1. 内存紧凑:所有元素按升序排列,连续存储在一块内存中,没有指针开销。
  2. 支持升级:当插入的整数超出当前编码范围时,会触发整体升级(如 int16 升级为 int32),但不支持降级
  3. 查找高效:由于有序,可以使用二分查找,时间复杂度为 O(log N)。

正是这些特点,使得 intset 在存储纯整数小集合时,比 hashtable 节省大量内存。

三、intset 转换为 hashtable 的条件

这是面试中最核心的问题。触发转换的条件主要有两个:

条件一:插入非整数元素

intset 只能存储整数。一旦向集合中插入任何非整数元素(如字符串 "hello"),intset 就无法继续使用,Redis 会立即将其转换为 hashtable。

例如:

SADD nums 1 2 3        # 使用 intset
SADD nums "abc"        # 触发转换为 hashtable

条件二:元素数量超过 set-max-intset-entries

即使集合中全部是整数,当元素个数超过配置项 set-max-intset-entries 的限制时,也会转换为 hashtable。

该配置的默认值为:

set-max-intset-entries 512

也就是说,当整数集合的元素数量超过 512 个时,Redis 会将其编码从 intset 转换为 hashtable。

需要特别注意的是:这个判断发生在插入操作时。当插入第 513 个整数元素时,转换被触发。

四、转换的源码逻辑

在 Redis 源码 t_set.c 中,相关逻辑大致如下:

if (setTypeSize(set) > server.set_max_intset_entries) {
    setTypeConvert(set, OBJ_ENCODING_HT);
}

以及插入非整数时的判断:

if (set->encoding == OBJ_ENCODING_INTSET &&
    !string2ll(value, sdslen(value), &llval)) {
    setTypeConvert(set, OBJ_ENCODING_HT);
}

可以看到,转换是单向的:intset → hashtable,一旦转换完成,即使后续删除了所有非整数元素,或者元素数量降到阈值以下,也不会回退为 intset。这一点与 ziplist/listpack 的转换规则类似,都是为了避免频繁的结构抖动。

五、与 listpack 编码的关系(补充说明)

在 Redis 7.2 之前,Set 还有一个 set-max-listpack-entries(旧版为 set-max-ziplist-entries)和 set-max-listpack-value 配置,用于控制小字符串集合使用 listpack 编码。其转换条件为:

  • 元素数量超过 set-max-listpack-entries(默认 128);
  • 或插入的元素长度超过 set-max-listpack-value(默认 64)。

但如前所述,Redis 7.2 起已移除 Set 的 listpack 编码,小集合直接使用 hashtable。因此在新版本中,Set 的编码实际上只剩 intset 和 hashtable 两种。

六、面试常见追问

追问 1:为什么 intset 转 hashtable 后不能转回来?

答:主要是为了避免在元素频繁增删时反复进行结构转换,造成性能抖动。同时,转换本身涉及内存重新分配和数据迁移,代价较高。

追问 2:intset 的升级和 Set 编码转换有什么区别?

答:intset 升级(int16→int32→int64)是 intset 内部编码的变化,仍然是 intset;而 intset 转 hashtable 是整体数据结构的变化,两者不是一回事。

追问 3:如何查看一个 Set 的当前编码?

答:

OBJECT ENCODING myset

返回 intsethashtable

追问 4:set-max-intset-entries 设置得越大越好吗?

答:不一定。设置得越大,intset 能容纳的整数越多,内存越省;但 intset 的插入和删除涉及内存重分配(尤其是升级时),过大可能导致单次操作延迟升高。需要根据业务场景权衡。

七、实战建议

  1. 纯整数集合尽量控制在 512 以内,以享受 intset 的内存优势。
  2. 避免在整数集合中混入字符串,否则会提前触发 hashtable 转换,内存占用显著上升。
  3. 关注 Redis 版本差异,7.2 前后 Set 的编码策略有变化,升级时需留意。
  4. 使用 OBJECT ENCODING 定期检查关键 Set 的编码,及时发现意外的转换。

八、小结

转换方向 触发条件
intset → hashtable 插入非整数元素
intset → hashtable 元素数量超过 set-max-intset-entries(默认 512)
hashtable → intset 不支持回退

掌握 intset 与 hashtable 的转换条件,本质上是理解 Redis “以内存换效率、以结构换紧凑”的设计哲学。在面试中,能够说清触发条件、源码位置以及不可逆的原因,基本就能拿到这道题的高分。

未经允许不得转载:任鹏个人博客 » Redis 的 intset 和 hashtable 编码转换条件

赞 (0) 打赏

评论 0

取消
  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏