Redis 渐进式 Rehash 的完成判断机制
在 Redis 的字典(dict
)结构扩容或缩容时,会使用 渐进式 Rehash 来避免一次性迁移所有键值对导致的阻塞。以下是判断旧哈希表(ht[0]
)是否全部迁移完毕的核心逻辑:
1. 渐进式 Rehash 的核心流程
Redis 的字典结构包含两个哈希表:
typedef struct dict {
dictht ht[2]; // 两个哈希表(ht[0] 和 ht[1])
int rehashidx; // Rehash 进度标志(-1 表示未进行)
} dict;
ht[0]
:当前使用的哈希表(旧表)。ht[1]
:扩容/缩容后的新哈希表。rehashidx
:记录当前迁移的桶索引(从 0 开始)。
2. 如何判断 Rehash 完成?
(1)迁移进度跟踪
rehashidx
表示旧表ht[0]
中 下一个待迁移的桶的索引。- 每次触发 Rehash(如执行增删改查命令时),Redis 会迁移
ht[0]
的rehashidx
位置的桶到ht[1]
,然后rehashidx++
。
(2)完成条件
当满足以下条件时,认为 Rehash 完成:
if (rehashidx == ht[0].size) {
// 1. 释放 ht[0] 的内存
free(ht[0].table);
// 2. 将 ht[1] 设置为 ht[0]
ht[0] = ht[1];
// 3. 重置 ht[1] 为空表
_dictReset(&ht[1]);
// 4. 关闭 Rehash 标志
rehashidx = -1;
}
rehashidx == ht[0].size
:表示所有桶(从 0 到size-1
)均已迁移。- 此时旧表
ht[0]
被释放,新表ht[1]
替换为ht[0]
,rehashidx
重置为-1
。
3. 触发迁移的时机
Redis 在以下操作中 逐步迁移一个或多个桶:
- 增删改查命令(如
GET
、SET
、DEL
):- 每次命令执行时,迁移
ht[0]
的rehashidx
位置的桶。
- 每次命令执行时,迁移
- 定时任务(
serverCron
):- 在事件循环中,Redis 会主动迁移一定数量的桶(默认每次 1 个桶)。
4. 为什么需要渐进式 Rehash?
- 避免阻塞:如果一次性迁移所有键值对,在大字典时会导致 Redis 短暂无响应。
- 分摊开销:将迁移成本分散到每次操作中,保证高性能。
5. 访问数据时的逻辑
在 Rehash 过程中,数据访问需检查两个表:
value = dictFind(key):
if dict->rehashidx != -1:
// 1. 先在 ht[0] 查找
if (entry = ht[0][hash(key) % ht[0].size]) != NULL:
return entry.value
// 2. 如果 ht[0] 未找到,继续查 ht[1]
return ht[1][hash(key) % ht[1].size]
else:
// 未在 Rehash,直接查 ht[0]
return ht[0][hash(key) % ht[0].size]
总结
- 完成标志:当
rehashidx
等于旧表ht[0]
的大小时,表示所有桶已迁移。 - 渐进式迁移:通过分散到每次操作和定时任务,避免集中式阻塞。
- 数据访问:Rehash 期间需同时查询
ht[0]
和ht[1]
。
参考源码:Redis dict.c