Codis的槽位迁移与ConcurrentHashMap扩容的相似之处
Codis 的槽位迁移过程与 ConcurrentHashMap (CHM) 的扩容在核心思想上非常相似,都采用了 “渐进式数据迁移” 和 “请求转发” 的机制来保证平滑性和高可用性。它们都避免了传统的一次性全量迁移导致的长时间服务中断。
相似之处 :
- 分片/分段:
- Codis: 数据被分配到固定的 1024 个 Slot 中。迁移的单位是 Slot。
- CHM: 数据被分配到多个 Segment/Bucket 中 (Java 8 之前是 Segment,之后是 Node 数组)。迁移的单位是 Segment/Bucket 内的链表/树节点。
- 增量迁移 (渐进式迁移):
- Codis: 迁移 Slot 时,不是一次性迁移 Slot 下的所有 Key,而是按需迁移(当请求遇到正在迁移的 Key 时触发迁移)或者后台分批扫描迁移(更常见且高效的方式)。
- CHM: 扩容时(如从 16 扩容到 32),不是一次性重新计算所有键的哈希并移动,而是锁定当前正在操作的 Segment/Bucket,只迁移该 Segment/Bucket 内的数据。其他未被锁定的 Segment/Bucket 可以继续正常读写。
- 读写操作协助迁移:
- Codis::
- 读/写请求到达
A
(源节点)。 - 如果 Key 属于
A
且 Slot 未迁移或已完成迁移:A
直接处理。 - 如果 Key 属于正在迁移的 Slot
S
:A
首先检查该 Key 是否仍在本地(未迁移)。- 如果仍在本地,
A
处理请求(如果涉及写操作,会同步到B
)。 - 如果 Key 已迁移(或
A
确定需要迁移它),A
会转发请求到B
(目标节点)。 B
处理请求并将结果返回给A
,A
再返回给客户端。
- 读/写请求到达
- CHM:
- 读操作到达:如果 Key 在当前 Bucket 且未迁移,直接读。如果 Key 在当前 Bucket 但已迁移到新表,则去新表读。
- 写操作到达:锁定当前 Bucket。如果 Key 在当前 Bucket 且未迁移,直接操作。如果 Key 在当前 Bucket 但已迁移,则迁移该 Key(或整个链表/树)到新表,然后在新表上操作。
- 关键点: 无论是读还是写操作,当线程发现当前 Bucket 正在扩容迁移时,它有义务协助完成该 Bucket 的迁移工作(帮助移动节点)。这就是“请求协助迁移”的思想。
- Codis::
- 新旧结构并存与原子切换:
- Codis: 在迁移过程中,
A
和B
同时存在。集群元数据(记录 Slot 在哪个节点)的变更(A
->B
)是通过 Zookeeper/Etcd 等原子性地更新的。客户端通过查询代理(Proxy)获取最新的元数据。迁移完成后,元数据指向B
,旧数据在A
上可以被异步清理。 - CHM: 扩容时,会创建一个新的、更大的 Node 数组(新表)。迁移是逐个 Bucket 进行的。当一个 Bucket 迁移完成,其指针会原子性地从旧表切换到新表。整个扩容完成时,所有 Bucket 指针都指向新表,旧表可以被回收。读操作可以无锁访问新表或旧表(取决于 Bucket 是否已迁移)。写操作需要锁住 Bucket。
- Codis: 在迁移过程中,