
前一阵帮一个业务团队排查网络告警规模已经到三百多个节点的 Redis Cluster业务量没怎么涨节点网卡却始终比平时高出不少。一开始怀疑是客户端读写、大 key 迁移或者主从全量同步但翻了一圈数据面监控发现真正的异常流量都打在集群总线上。后来抓包确认挑起大梁的不是业务命令而是大量 Gossip 协议产生的 PING/PONG 消息外加状态变化时的 FAIL 广播和 TCP 重传。这篇文章我想把这些经验拆开讲清楚Redis 集群节点间的网络带宽到底怎么监控Gossip 协议在内网通信里怎么把开销一点点堆起来以及拿到数据之后怎么评估一个大规模集群还能不能继续加节点。内容不适合刚开始用 Redis 的同学更适合那些已经用 Redis 集群扛过一段时间、准备扩容却拿不准网络会不会先撑不住的人。1. 为什么集群“没业务”也会吃带宽藏在 10000 端口偏移后面的控制面1.1 数据面与控制面共用一张网卡很多人手握着 Redis Desktop Manager 看 key、看命中率或者在 Windows 上装个 Redis 做本地实验都不会意识到 Redis 集群版底层的网络结构其实比“客户端连上服务端”要复杂得多。一个 Redis 节点监听两个端口数据面端口也是客户端常用的 6379负责处理命令请求、key 读写、主从数据同步这类业务流量集群总线端口默认是数据面端口加 10000也就是 16379负责节点间心跳、槽位信息同步、故障发现、选举消息等。集群总线端口在 Redis 7 之前不可配置新版可以在 redis.conf 里用cluster-port单独指定但不能与业务端口重复。问题在于这两条“道路”通常共用同一块物理网卡、同一个网络带宽池。业务低峰期时 6379 端口可能很空闲但 16379 端口上的 Gossip 消息不会因为夜里没人访问就停下来。只要集群没有缩到单机节点间就会持续交换状态信息。节点规模越大这条看不见的总线就有越多内容要传。我用“隐藏的第二张网”来描述它因为很多监控大盘默认只统计了数据面流量不会单独把 16379 端口的吞吐量统计进去于是带宽被吃掉的时候第一反应往往是业务流量异常而不是集群自身的管理通信出了问题。1.2 节点间是网状连接不是星型连接Redis Cluster 的节点之间在 cluster bus 上采用全连接网状结构。从集群的视角看每个节点都需要和其他所有节点保持通信才能通过 Gossip 协议交换状态。如果集群有 N 个节点单从节点连接数来看每个节点大概会维护 N-1 条到其他节点的 cluster bus 连接。300 个节点时单节点上光是总线的 outbound 连接就有 299 条再加上其他节点连过来的 inboundnetstat一拉就是大几百上千条。这个数量级在内存和文件描述符上还没到压垮服务器的程度但它意味着每一条连接上都会有状态维护成本而网络报文也要在这些连接之间持续流动。我不建议靠“N 个节点产生的连接数”直接推导带宽因为 Redis 不会真的给每条连接每秒发一个心跳。但从运维角度必须先理解这个网状架构否则看到抓包结果里全是单一的源端口和目的端口时会误以为节点在发生某种异常通信。2. Gossip 到底怎么“聊天”从 PING/PONG 消息结构到发送节奏2.1 PING、PONG、MEET、FAIL 各自负责什么要评估 Gossip 带来的通信开销不能只用“心跳”两个字概括。Redis cluster bus 上跑的是一套二进制协议主要消息有好几类PING主动探测其他节点是否存活顺便携带一批当前已知节点的状态PONG收到 PING 之后回给对方的响应也表示自己当前认知中的配置信息MEET新节点加入集群时由已加入的节点介绍给其他节点使用FAIL某个节点被多数节点判定为客观下线后在集群内广播UPDATE节点发现别人的配置纪元比自己旧用该消息同步新状态。这些消息里面PING/PONG 是每天每刻都在跑的常规流量FAIL/UPDATE 平时很少出现可一旦出现就是突发流量。我在很多次故障复盘里看到大家评估集群带宽时只算了常规心跳忽略了 FAIL 广播瞬间的流量尖峰结果带宽规划严重不足。2.2 一个小包里可能塞了半张“通讯录”Gossip 的设计思路可以类比成一群人不通过中心服务器而是靠互相闲聊来同步消息。A 和 B 聊天时不只是说“我还活着”还会顺便告诉 B我在最近一段时间收到过 C、D、E 的报文它们的状态我也一并发给你。在 Redis cluster bus 消息里每个被携带的节点称为一个 gossip entry。它包含节点 ID、IP 地址、端口、配置纪元、节点状态、最后通信时间等信息。这个 entry 是固定长度的二进制结构单个 entry 大概占用一百字节左右。一个 PING 消息会根据集群规模携带若干个 gossip entry数量不固定。小集群里一条消息包几个节点就够了大集群里如果要把状态扩散得足够快消息携带的条目会更多包长随之变大。如果你抓包看到 cluster bus 上的包长并不完全相同甚至差异很大原因就在这里常规 PING/PONG 是“带几个随机节点状态”的小包而一次 FAIL 广播或 UPDATE 可能携带更完整的状态集合包长会突然增加。2.3 cluster-node-timeout 才是心跳节奏的真正闸门Redis 官方对 Gossip 的描述里有一句话很关键节点会每隔一段时间随机挑选节点发送 PING。但“每隔一段时间”到底多长并不是某个写死的秒数而是由cluster-node-timeout间接决定的。默认情况下cluster-node-timeout是 15000 毫秒。源码里的clusterCron()大约每 100 毫秒跑一次它会扫描节点通信状态对那些很久没有收到消息的节点发送 PING。判断“很久”的重要基线就是cluster-node-timeout / 2。换句话说如果一个节点距离上次成功通信已经超过cluster-node-timeout / 2就会被纳入待 PING 列表。所以这里有一个很多运维容易弄反的结论cluster-node-timeout设置得越小故障发现越快但节点间心跳频率也会越高总线通信开销越大。反过来想靠调大cluster-node-timeout降低心跳频率是可行的但要付出故障感知变慢的代价。后面我会专门讲怎么权衡。2.4 常规心跳之外的突发流量才最容易打满带宽从抓包数据看平稳运行期的 cluster bus 流量通常不高几百个节点的集群也只有每节点几十 KB/s 到几百 KB/s 量级。真正把节点网卡打满的往往是以下几类事件节点加入或下线需要和其他节点互通 MEET/PONG状态同步量短暂上升某节点被判定 PFAIL再被多数节点确认成 FAIL集群内会广播所有节点都要更新状态网络抖动后恢复积压的 TCP 报文和重传、乱序响应会同时出现槽位迁移和主从切换过程中配置纪元变化会触发额外的 UPDATE 消息。所以我给团队的监控原则是既要测量常规心跳的底噪也要记录这些事件发生时的峰值因为集群规划时看的是峰值不是均值。带宽打满通常出现在“网络抖动 节点 failover 客户端重连”同时发生的窗口里。3. 端口级流量观测的完整链路从网卡总量到单条连接3.1 先用 sar 判断是“收”还是“发”出了问题做端口级监控的第一步不是直接抓包而是先看服务器网卡整体的收发趋势。Linux 上最简单的工具是sarsar -n DEV 1 5输出里每行会显示对应网卡的rxkB/s和txkB/s。如果你看到的是入向流量高那说明大量数据在进入这台节点可能是业务读取、主从同步或者集群总线上的 PONG 返回如果是出向流量高则要重点观察大 key 返回、全量同步或者 PING 广播。这里特别建议同时查看 Redis 自己的统计redis-cli -p 6379 INFO stats | grep -E total_net_(input|output)_bytestotal_net_input_bytes和total_net_output_bytes主要反映命令连接和数据面上的累计流量。在多数统计口径里cluster bus 不算在普通客户端连接流量中。所以你可以做一个快速的差值判断网卡总流量明显大于 Redis 客户端流量而且相差的部分又无法用 SSH、监控 agent 等其他进程解释时嫌疑就集中到 cluster bus 上。3.2 抓包前先确认 cluster bus 端口别把命令端口当成全部Redis 默认 cluster bus 是业务端口加 10000但这个规则不是绝对。有人把客户端端口改成了 7000那总线就在 17000有人在同一台机器上跑多个 Redis 实例每个实例的总线端口也各不相同。抓包之前最好确认redis-cli -p 6379 cluster nodes | head -20从cluster nodes输出里可以看到每个节点声明的ip:port那个 port 一般是客户端端口。而总线端口可以用netstat查看实际监听情况netstat -anp | grep redis-server | grep 16确认后用 tcpdump 抓 30 秒的 cluster bus 流量timeout 30 tcpdump -i eth0 -nn -s 96 port 16379 -w /tmp/cluster_bus.pcap-s 96是只抓每个包的前 96 字节够解析头部和统计长度但不会把整个业务内容抓下来。如果总线流量本身很大30 秒的 pcap 可能已经不小建议用-w落盘而不是直接打到屏幕避免 tcpdump 自己的输出变成额外负载。抓完先看总量确认抓包期间是否有明显波动tshark -r /tmp/cluster_bus.pcap -q -z io,stat,1tshark的io,stat会按秒输出 frames 和 bytes。把 30 行加起来取平均就能得到这台节点 16379 端口每秒的包数和字节数。如果服务器没有 tshark也可以用capinfos看整体字节但分秒统计还是 tshark 更方便。3.3 单节点统计不够时用会话视图找“话痨”只看单节点总量能判断“总线确实吃了很多带宽”但看不出是哪一对节点在频繁通信。这时要用 tshark 的连接会话视图tshark -r /tmp/cluster_bus.pcap -q -z conv,tcp | head -60这个命令会按 TCP 连接聚合出每个会话的收发字节数。我遇到过排障场景某次集群升级后一台新节点因为 announce ip 配置错误反复被其他节点当作未知节点发起 MEET 和 PONG结果这个节点的总线上全是同一条会话的流量。光看节点聚合流量时只看到数值高一打开会话视图马上定位到具体端口对。3.4 云上环境没有根权限时怎么观测如果 Redis 跑在云主机上而你没有宿主机抓包权限可以用下面几种替代方案VPC 流日志很多云厂商支持对弹性网卡开启流量日志能看到五元组、字节数和包数量按目的端口 16379