Redis集群带宽排查:Gossip协议如何悄悄吃满节点网卡——从原理到端口级监控与扩容评估
2026/9/9 6:14:52 网站建设 项目流程

前一阵帮一个业务团队排查网络告警,规模已经到三百多个节点的 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 条,再加上其他节点连过来的 inbound,netstat一拉就是大几百上千条。这个数量级在内存和文件描述符上还没到压垮服务器的程度,但它意味着每一条连接上都会有状态维护成本,而网络报文也要在这些连接之间持续流动。

我不建议靠“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 上最简单的工具是sar

sar -n DEV 1 5

输出里每行会显示对应网卡的rxkB/stxkB/s。如果你看到的是入向流量高,那说明大量数据在进入这台节点,可能是业务读取、主从同步或者集群总线上的 PONG 返回;如果是出向流量高,则要重点观察大 key 返回、全量同步或者 PING 广播。

这里特别建议同时查看 Redis 自己的统计:

redis-cli -p 6379 INFO stats | grep -E "total_net_(input|output)_bytes"

total_net_input_bytestotal_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,1

tsharkio,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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询