Cluster Meet 节点握手与集群信息查看
CLUSTER MEET 是集群一切故事的起点:没有握手,就没有 gossip、没有槽位同步、没有故障转移。日常大家用 redis-cli --cluster create 一键建集群,很少直接接触 MEET,但排障时——节点握手卡住、扩容节点加不进来、两个集群意外分裂——理解握手机制和信息命令是基本功。本文基于 Redis 7 展开。
一、CLUSTER MEET 做了什么
MEET 的语义是:告诉当前节点「把对方纳入集群视图,并主动发起握手」。它和普通 PING 的区别在于信任起点:收到 MEET 的节点会把对方记入自己的节点表,状态为 handshake,随后双方通过 PING/PONG 互认;一旦握手成功,这个事实会通过 gossip 消息在集群内传播,其余节点会陆续与新节点握手——所以理论上只需向集群中任意一个成员执行 MEET,新节点就能进入整个集群。
握手发生在两条通道上:业务端口之外,还有一条集群总线(bus)通道,默认端口是业务端口加 10000,Redis 7 可以用 cluster-port 配置项显式指定。gossip 心跳、槽位图、故障投票全部走总线端口,这也是握手失败最常见的原因。
握手的时间尺度值得量化,否则很难判断"卡住"和"还在进行"的区别。MEET 发出后,源节点立刻建立到目标的 TCP 连接并发 PING,目标回复 PONG 后双方各写入对方节点信息,状态从 handshake 转为正常;随后源节点在下一轮 gossip 周期把新节点信息打包发给已知的其他节点。整个握手本身在毫秒级完成,但 gossip 传播依赖每轮心跳,节点越多、规模越大,全部节点认识新成员所需的时间越长。
节点间协议还有一层细节:每条消息都带一个 currentEpoch 与发送者的 configEpoch。currentEpoch 是集群级别的单调计数器,用于选举与冲突仲裁;configEpoch 是每个主节点的槽配置版本号,槽归属变更时递增,节点之间以"configEpoch 更大者的槽配置为准"来收敛。手工 CLUSTER MEET 后如果发现某些槽的归属在不同节点上不一致,根因往往就是 configEpoch 冲突,用 CLUSTER BUMPEPOCH 提升当前节点的纪元可以打破僵局。
二、手动搭建一个三主三从集群
手动流程能看清集群的三个构成要素:互相认识、分配槽、建立主从。
# 每个节点的 redis.conf 必备项
port 6379
cluster-enabled yes
cluster-config-file nodes-6379.conf
cluster-node-timeout 15000
cluster-port 16379
# 1. 依次执行 MEET,让节点互相认识
redis-cli -h 10.0.0.11 -p 6379 cluster meet 10.0.0.12 6379
redis-cli -h 10.0.0.11 -p 6379 cluster meet 10.0.0.13 6379
# 2. 给三个主节点分配槽,Redis 7 支持区间语法
redis-cli -h 10.0.0.11 -p 6379 cluster addslotsrange 0 5460
redis-cli -h 10.0.0.12 -p 6379 cluster addslotsrange 5461 10922
redis-cli -h 10.0.0.13 -p 6379 cluster addslotsrange 10923 16383
# 3. 让 14/15/16 作为 11/12/13 的副本(id 用 cluster myid 查询)
redis-cli -h 10.0.0.14 -p 6379 cluster replicate <11 的 id>
生产上更推荐 redis-cli --cluster create ... --cluster-replicas 1,它把上面三步连同配置纪元(config epoch)的初始化一起处理好;手工 MEET 只建议用于修复场景。
三、集群信息查看命令全景
排障时最常用的两个命令是 CLUSTER INFO 与 CLUSTER NODES:
cluster_enabled:1 # 集群模式开关
cluster_state:ok # 全部槽有主且可达,否则 fail
cluster_slots_assigned:16384 # 已分配槽数,不足说明有槽失主
cluster_slots_ok:16384 # 正常槽数
cluster_slots_pfail:0 # 疑似下线节点持有的槽数
cluster_slots_fail:0 # 确认下线节点持有的槽数
cluster_known_nodes:6 # 节点总数
cluster_size:3 # 持有槽的主节点数
cluster_current_epoch:6 # 全局纪元,故障转移时递增
CLUSTER NODES 每行的格式是 id ip:port@bus-port flags master-id ping-sent pong-recv config-epoch link-state slots。flags 字段要重点掌握:myself 是当前连接的节点,master/slave 是角色,pfail 表示部分主节点疑似其下线,fail 表示多数派确认下线,handshake 表示正在握手,noaddr 表示地址失效,nofailover 表示该副本被禁止参与故障选举。
把一行真实输出逐字段拆开看,比读格式说明更有效:
07c37dfeb235213a872192d90877d0cd55635b91 10.0.0.12:6379@16379 slave 4e07408562bedb8b60ce05c1decfe3ad16b72230 0 1761350401000 3 connected
07c37dfeb...:40 字符节点 ID,加入集群时生成,CLUSTER RESET会重新生成;10.0.0.12:6379@16379:业务地址加总线地址,@后面就是握手用的端口;slave:角色,副本节点会紧接着给出跟随的主节点 ID;4e0740856...:主节点 ID,仅副本节点有,主节点这里显示-;0:ping-sent 时间戳,0表示尚未发送;1761350401000:pong-recv 时间,形如毫秒时间戳,与当前时间差距过大意味着心跳丢失;3:configEpoch,副本节点显示的是它记录的主节点配置纪元;connected:link-state,另一个取值是disconnected,出现即代表总线通道断开;- 末尾空:槽区间,只有主节点才有,例如
0-5460。
ping-sent 与 pong-recv 的差值是最实用的健康指标。正常情况下两者相差不过几百毫秒;如果 pong-recv 长时间不更新,说明这个节点要么网络不通、要么进程已经挂死但还没被判定为 fail,此时 flags 里可能还没有 fail 标记,属于"看起来正常实则失去响应"的窗口期。
redis-cli -p 6379 cluster myid # 本节点 id
redis-cli -p 6379 cluster slots # 槽区间到节点的映射(7.0 后推荐 cluster shards)
redis-cli -p 6379 cluster links # 查看节点间总线连接,排障用
redis-cli -p 6379 cluster countkeysinslot 2926
redis-cli -p 6379 cluster getkeysinslot 2926 100
四、握手失败的排查路径
节点长期卡在 handshake 状态,按这个顺序查:先查总线端口连通性,telnet 对方的 16379(或 port+10000),防火墙与安全组是头号原因;再查 NAT 与容器环境,节点上报的地址不对时配置 cluster-announce-ip;然后查是否复用了旧的 nodes-*.conf,带着旧拓扑、旧纪元回归的节点可能造成集群视图混乱,正确做法是先在空节点执行 CLUSTER RESET HARD 清空集群状态再加入。另外,如果 MEET 之后忘了分配槽,cluster_state 会一直是 fail,这不是故障,补上 ADDSLOTS 即可。
按症状反查比按顺序逐个试更快。整理成一张对照表:
| 症状 | 最可能原因 | 确认方式 |
| --- | --- | --- |
| 一直 handshake,无任何变化 | 总线端口不通或被安全组拦截 | telnet <ip> 16379 |
| 握手成功但地址是内网或 127.0.0.1 | 容器或 NAT 环境未配置 announce | CLUSTER NODES 看上报地址 |
| 节点表里出现重复 ID | nodes-*.conf 未清理 | 对比文件内 myself 行 |
| cluster_state:fail 且槽数不足 | 忘记 ADDSLOTS 或有槽失主 | CLUSTER INFO 看 cluster_slots_assigned |
| 槽归属在不同节点上不一致 | configEpoch 冲突 | --cluster check 报槽配置分歧 |
| 两套集群互相不可见 | 一开始就 MEET 错了地址 | 核对启动参数与拓扑图 |
最后一行是最难发现的:把节点 A 与另一个集群的节点 B 做了 MEET,两个集群会合并成一个,槽位表互相覆盖一部分,表现为"部分 key 路由到意外节点"。合并之后没法自动拆回去,只能手工清理槽归属,所以 MEET 之前务必确认目标地址属于当前集群。
五、CLUSTER SLOTS 与 CLUSTER SHARDS 的取舍
两者都能给出"槽归谁",但结构不同。CLUSTER SLOTS 返回的是"起始槽、结束槽、主节点地址"的嵌套数组,一个槽区间对应一组节点,副本信息隐含在主节点之后;CLUSTER SHARDS(7.0 引入)以分片为单位返回,每个分片显式列出全部节点及其角色、健康状态,结构更清晰,也更适合表达"一个分片多个副本"这种关系。
对客户端而言,改造成本决定了实际选择。老版本客户端普遍依赖 CLUSTER SLOTS,新代码可以用 CLUSTER SHARDS 拿到更完整的拓扑。迁移期两种格式必须共存,因此不要期望短期内彻底切换到 SHARDS——自研客户端在支持 SHARDS 的同时保留 SLOTS 解析,是更稳妥的做法。用 CLUSTER SHARDS 检查副本归属很直接:
redis-cli -p 6379 cluster shards
# 1) 1) "slots"
# 2) 1) (integer) 0
# 2) (integer) 5460
# 3) "nodes"
# 4) 1) 1) "id"
# 2) "07c37dfeb..."
# 3) "port"
# 4) (integer) 6379
# 5) "role"
# 6) "master"
# 7) "health"
# 8) "online"
关键字段是 role 与 health:health 为 fail 或 loading 时,即使槽归属正确,客户端也应避免把请求打过去。这比在 CLUSTER NODES 里逐个比对 flags 省事得多。
常见坑 / 最佳实践
- 防火墙只放通业务端口是最高频错误。 总线端口不通,节点永远停在 handshake。上线检查清单里要同时包含两个端口的双向连通性。
- 回归的旧节点先 RESET 再加入。 曾脱离集群的节点带着旧的 nodes 文件重新上线,可能把过期拓扑传播回来,加入前执行
CLUSTER RESET HARD。 - 手工建集群时先分槽再观察 epoch 冲突。 多个节点几乎同时分配槽可能产生配置纪元冲突,Cluster 会按节点 id 自动仲裁,但生成的集群要用
--cluster check确认一遍。 - 生产优先用 cluster manager 子命令。
create、check、fix封装了纪元初始化与一致性校验,手工 MEET/ADDSLOTS 留给修复场景。 - 把 cluster info 接进监控。
cluster_state、cluster_slots_fail、cluster_size三个字段足以提前发现槽失主与节点失联,比业务报错早得多。
小结
- MEET 是建立集群信任起点的一次性动作,握手完成后由 gossip 自行传播,新节点无需逐个 MEET。
- 集群通信走总线端口(默认业务端口加 10000,可用 cluster-port 指定),它是握手失败的头号原因。
- 手动建集群的三要素:MEET 互认、ADDSLOTSRANGE 分槽、REPLICATE 建主从;日常用 cluster create 更稳妥。
- CLUSTER INFO 的 state/slots_fail/size 与 CLUSTER NODES 的 flags 字段覆盖了日常排障的大部分信息。
- 旧节点回归前先 CLUSTER RESET HARD,避免旧拓扑与旧纪元污染集群视图。
- 点赞
- 收藏
- 关注作者
评论(0)