给集群扩容,最让人担心的是「搬数据的时候业务会不会抖、会不会丢」。这篇在真实集群上做:先写 30 万个 key,后台一直有读写流量,然后加一个空主库,让 redis-cli 自动把槽位均分给 4 个主库,同时测吞吐、延迟、错误,最后逐个校验数据。
>>> Send FUNCTION RESTORE to 127.0.0.1:7496 >>> Send CLUSTER MEET to node 127.0.0.1:7496 to make it join the cluster. [OK] New node added correctly. 新节点的槽位数: 0
先用管道把 30 万个 key 写进 3 个主库(约各 10 万)。再启动一个空的集群节点(7496),用 redis-cli --cluster add-node 加入:它成为集群成员,但是槽位数是 0,不承担任何数据——只是「加入」并不会自动搬走任何数据。
rebalance 输出:
>>> Performing Cluster Check (using node 127.0.0.1:7490)
>>> Rebalancing across 4 nodes. Total weight = 4.00
Moving 1366 slots from 127.0.0.1:7491 to 127.0.0.1:7496
Moving 1365 slots from 127.0.0.1:7492 to 127.0.0.1:7496
Moving 1365 slots from 127.0.0.1:7490 to 127.0.0.1:7496
搬了 4096 个槽,耗时 3.0 s;搬完后各主库槽位数: {7490: 4096, 7491: 4096, 7492: 4096, 7496: 4096}
阶段 操作数 ops/s p50 µs p99 µs max µs
搬迁前 59273 29660 32 54 1641
搬迁中 86820 28548 32 64 23153
搬迁后 62072 30881 31 54 344
客户端遇到的重定向:MOVED 4080 次,ASK 0 次;错误 0 次;读到错误值的操作 0 次redis-cli --cluster rebalance --cluster-use-empty-masters 计算出每个主库应有 4096 个槽,于是从三个老主库各搬一部分(1366 + 1365 + 1365)到新主库,共 4096 个槽,耗时 3.0 s。每个槽的搬迁走的是第 80 篇的 IMPORTING / MIGRATING / MIGRATE 流程。这里每个槽只有几十个 key(总共 30 万个 key ÷ 16384 个槽 ≈ 18),搬一个槽很快;key 更大、更多时耗时会大得多。
搬迁期间的客户端吞吐是 29660 → 28548 → 30881 ops/s(前 / 中 / 后),p50 都是约 32 µs,p99 54 → 64 µs,搬迁中的最大延迟是 23.2 ms(搬迁前 1.6 ms)。客户端遇到 4080 次 MOVED——每个被搬走的槽被指路一次——和搬走的 4096 个槽接近,而 ASK 是 0 次:每个槽从「开始迁移」到「迁移完成」只有很短的一瞬间,随机访问几乎撞不上它(第 80 篇会用确定性的方式复现 ASK)。
校验 300000 个 key(0.7 s):错误 0 个;新分布 DBSIZE: {7490: 74995, 7491: 75054, 7492: 75001, 7496: 74950}把 30 万个 key 全部按新槽位表回读:0 个错误。搬迁期间客户端 0 次错误、0 次读到错误值。新的 DBSIZE 分布是每个主库约 7.5 万个 key(和槽位数成比例,因为 key 在槽里几乎均匀)。
数据量小(每个 key 很小,总共 30 万个)、流量是单个同步客户端、没有大 key、也没有开持久化。真实业务里搬迁的耗时主要取决于槽里的数据量,最大延迟主要取决于单个 key 的大小(大 key 的 MIGRATE 会阻塞源节点,见第 81、82 篇)。