Redis渐进式rehash

Redis渐进式rehash

渐进式 rehash 是 Redis 为了避免在扩容或收缩哈希表(Dict)时导致服务器“卡死”而设计的一种高明策略。

如果 Redis 像传统方式那样一次性完成百万级键值的搬迁,会导致主线程长时间阻塞,对于追求高并发、低延迟的 Redis 来说,这显然是不可接受的。

1. 核心数据结构

在 Redis 的字典结构中,实际上维护了两个哈希表:

  • ht[0]:平时使用的哈希表。
  • ht[1]:只在 rehash 期间使用的哈希表(通常大小是 ht[0] 的两倍)。

2. 搬迁过程:化整为零

核心思想:将巨大的计算成本,平摊到了每一次请求和后台空闲时间中。这避免了集中式处理带来的长耗时,保证了 Redis 的 响应时间 始终保持在极低水平。

Redis 不会一次性把 ht[0] 的所有数据搬到 ht[1],而是采取了“分而治之”的办法:

触发式搬迁

每当客户端对该字典执行 添加、删除、查找或更新 操作时,Redis 除了执行指定操作外,还会顺带将 ht[0]rehashidx 索引上的所有键值对搬迁到 ht[1]

定时同步

即便没有客户端访问,Redis 也会利用 定时任务,在系统空闲时主动搬迁一部分数据,确保 rehash 最终能完成。

3. rehash的触发条件

在 Redis 中,rehash 的触发主要取决于负载因子(Load Factor)。负载因子的计算公式非常简单:

$$\text{Load Factor} = \frac{\text{ht[0].used}}{\text{ht[0].size}}$$

即:已保存节点的数量与哈希表大小的比值

3.1 扩容(Expand)的触发条件

Redis 会根据当前是否在执行后台指令,采取不同的扩容阈值:

  • 常规状态:当负载因子 $\ge 1$ 时,Redis 可能会触发扩容
  • 强制状态:当负载因子 $> 5$ 时,无论是否有后台进程,Redis 都会立即开始扩容(因为出现hash冲突时使用拉链法解决,所以实际保存的节点数可能会超过哈希表大小)

3.2 为什么有两个标准?

这涉及到 Redis 的 Copy-On-Write (COW) 机制。当 Redis 正在执行 BGSAVE(生成 RDB 快照)或 BGREWRITEAOF(AOF 重写)时,会创建子进程。

  • 为了尽可能减少父子进程间的内存页写入,Redis 会提高扩容门槛(从 1 提升到 5)。
  • 这样可以避免在子进程工作期间发生大规模的内存地址变动,从而节省系统内存并提高效率。

3.3 收缩(Shrink)的触发条件

当数据库中的键值对大量减少时,为了不浪费内存,Redis 会对哈希表进行收缩。

  • 触发条件:当负载因子 $< 0.1$ 时(即已用空间不足 10%),Redis 会自动开始收缩操作。

3.4 rehash 的空间分配规则

无论是扩容还是收缩,新哈希表 ht[1] 的大小都是有讲究的:

  • 如果是扩容ht[1] 的大小为第一个大于等于 ht[0].used * 2 的 $2^n$。
    • 例如:当前用了 100 个空间,扩容大小就是 $2^8 = 256$
  • 如果是收缩ht[1] 的大小为第一个大于等于 ht[0].used 的 $2^n$。
    • 例如:当前只剩 10 个键,收缩大小就是 $2^4 = 16$

4. rehash 的详细步骤

img

⚠️:图中ht[1]大小应为16,即首个大于2 * 6 = 12 的2次幂

  1. 分配空间:为 ht[1] 分配空间:
    • 如果是扩容,大小通常是第一个大于等于 ht[0].used * 2 的 $2^n$(首个大于已使用空间两倍的2次幂)
    • 如果是收缩,则是第一个大于等于 ht[0].used 的 $2^n$(首个大于已使用空间的2次幂)
  2. 设置索引:将变量 rehashidx 设置为 0,代表 rehash 正式开始。
  3. 分步搬迁
    • 每次操作字典,将 ht[0]rehashidx 索引位置上的桶(Bucket)移动到 ht[1]
    • 搬迁完成后,rehashidx 自增 1
  4. 完成收尾:当 ht[0] 的所有数据都迁移到 ht[1] 后,将 rehashidx 设为 -1。释放 ht[0],将 ht[1] 设置为新的 ht[0],并创建一个空的 ht[1] 为下次做准备。

5. rehash 期间的特殊行为

在搬迁过程中,字典处于一个“过渡状态”,其行为如下:

  • 查找操作:程序会先在 ht[0] 找,如果没找到,再去 ht[1] 找。
  • 添加操作:所有新键值对一律直接插入 ht[1]。这样可以保证 ht[0] 的数据只减不增,直到变空。
  • 删除/更新:同样会在两个表上进行,确保数据一致性。

Redis渐进式rehash
http://example.com/2026/02/04/Redis渐进式rehash/
作者
Kon4tsu
发布于
2026年2月4日
许可协议