bigywater.tech
Redis · 69

CRC16 与 16384 个槽位

槽位号是怎么来的?为什么是 16384 而不是 65536?这篇不背答案:先用自己写的 CRC16 去对拍服务器的 CLUSTER KEYSLOT,再量 100 万个 key 在槽里的分布,最后伪造一个集群总线对端,抓下节点发来的第一条消息,看看那个「槽位位图」到底有多大。

20000 / 20000
自己实现的 CRC16 与服务器 CLUSTER KEYSLOT 结果一致的 key 数
2048 B
集群总线消息(总长 2256 B)里的槽位位图 = 16384 位
σ = 6.74
100 万个连续 key 在槽里的标准差(纯随机期望 7.81)

算法:CRC16 mod 16384

实测 · 本机执行输出
crc16("123456789") = 0x31C3(XMODEM 标准校验值是 0x31C3)
随机 key 20000 个(含 { } 字符):自己算的槽位与服务器一致 20000 个,不一致 0 个 []
  CLUSTER KEYSLOT foo          → 12182   自己算 12182
  CLUSTER KEYSLOT bar          →  5061   自己算  5061
  CLUSTER KEYSLOT user:1000    →  1649   自己算  1649
  CLUSTER KEYSLOT {user1}:a    →  8106   自己算  8106
  CLUSTER KEYSLOT {user1}:b    →  8106   自己算  8106
  CLUSTER KEYSLOT a{}b         → 13694   自己算 13694

槽位 = CRC16(key) mod 16384,CRC16 是 XMODEM 变体(多项式 0x1021,初值 0)。我用 Python 写了一个表驱动的实现,输入 "123456789" 得到标准校验值 0x31C3;随机生成的 20000 个 key(包含花括号)全部与 CLUSTER KEYSLOT 一致,包括 hash tag 的规则({user1}:a 与 {user1}:b 都是 8106;a{}b 里花括号是空的,整个 key 参与计算)。

分布:比随机还均匀

实测 · 本机执行输出
平均 61.04 个/槽;最小 43,最大 79;标准差 6.74(泊松期望 7.81)
卡方统计量 = 12177(自由度 16383;若完全均匀随机,期望 ≈ 16383,标准差 ≈ 181)
三段槽位(0–5460 / 5461–10922 / 10923–16383)各占 key: [333354, 333279, 333367] ['33.34%', '33.33%', '33.34%']

100 万个连续 key(user:0…user:999999)在 16384 个槽里,平均 61.0 个,最少 43、最多 79,标准差 6.74,低于纯随机撒点的期望 7.81;卡方 12177 也低于期望 16383(±181)。也就是说,对「前缀相同、后缀是递增数字」这类规则的 key,CRC16 的分散效果比随机更均匀。这一点只对我测的这批 key 成立,不代表任意 key 集合。三段槽位各拿到约三分之一的 key。

为什么是 16384:看心跳里的位图

实测 · 本机执行输出
签名 b'RCmb';总长度 totlen = 2256 字节;消息类型编号 2(由 CLUSTER MEET 触发的第一条消息);gossip 条目数 count = 0
sender = f9d3e14e…(CLUSTER MYID = f9d3e14e…,一致: True);位图 2048 字节里置位的槽数 = 101(我分配了 0–100,共 101 个)
位图前 16 字节(十六进制):ffffffffffffffffffffffff1f000000(槽 0–100 → 前 12 个字节全 ff、第 13 个字节低位置位)

做法:开一个单节点的集群实例,分配槽 0–100,再让它 CLUSTER MEET 一个我用 socket 伪造的对端(监听总线端口 17331)。节点连过来,发来的第一条消息以 RCmb 开头,总长度 2256 字节;从偏移 80 开始有 2048 字节——那是本节点的槽位位图,16384 位,我分配的 101 个槽对应 101 个置位。发送方 ID 也与 CLUSTER MYID 一致。

这条消息里没有 gossip 条目(count = 0),因为节点还不认识别人;有邻居时会在头部之后追加条目。位图是固定大小,每次心跳都带:槽位数是 16384 时是 2048 字节,是 65536 时会是 8192 字节。这就是常被引用的理由之一;另外「集群不超过约 1000 个主节点」的设计假设也是官方说法,我没有做这一部分的实验。

分步演示:从 key 到槽位,再到心跳

CRC16 与 16384 个槽位
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 / Redis 8.10.2 独立实例(本机回环端口,save 关闭),redis-cli 风格输出由自写的 RESP 客户端打印;计时用 time.perf_counter,数字每次运行会有小幅抖动。集群总线消息由一个单节点集群实例主动发给我伪造的监听端口(CLUSTER MEET 触发)
    • 没有验证:集群总线消息中 gossip 条目的大小、6 节点以上集群的心跳流量、为什么官方选 16384 而不是别的数(只验证了位图大小)、CRC16 对非规则 key 集合的分布
    京ICP备2026067502号-1京公网安备11010502063487号