redis集群Gossip协议占用带宽过大的优化

redis的Cluster集群去中心化,以及高可用和高弹性部署的优势,成为3节点以上redis必选的集群方案。 keydb兼容redis6.x,并提供比redis性能更好多核优化。另外keydb支持flash储存模式和热数据内存储存,也成为大容量kv数据库的一个优先考虑方案。

最近几年云厂商频繁故障,也促使大家开始只使用云厂商的基础服务(主机)而更多的开始选择跨云跨地区部署。

而redis的Gossip协议此时会占用大量金贵的公网带宽

# Gossip消息有多大会占用多大带宽

Redis 实例发送的 PING 消息的消息体是由 clusterMsgDataGossip 结构体组成的。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
typedef struct {
    char nodename[CLUSTER_NAMELEN];  //40字节
    uint32_t ping_sent; //4字节
    uint32_t pong_received; //4字节
    char ip[NET_IP_STR_LEN]; //46字节
    uint16_t port;  //2字节
    uint16_t cport;  //2字节
    uint16_t flags;  //2字节
    uint32_t notused1; //4字节
} clusterMsgDataGossip;

一个 Gossip 消息的大小了,即 104 字节。 每个实例在发送一个 Gossip 消息时,除了会传递自身的状态信息,默认还会传递集群十分之一实例的状态信息。 所以,对于一个包含了 1000 个实例的集群来说,每个实例发送一个 PING 消息时,会包含 100 个实例的状态信息,总的数据量是 10400 字节,再加上发送实例自身的信息,一个 Gossip 消息大约是 10KB。 此外,为了让 Slot 映射表能够在不同实例间传播,PING 消息中还带有一个长度为 16,384 bit 的 Bitmap,这个 Bitmap 的每一位对应了一个 Slot,如果某一位为 1,就表示这个 Slot 属于当前实例。这个 Bitmap 大小换算成字节后,是 2KB。我们把实例状态信息和 Slot 分配信息相加,就可以得到一个 PING 消息的大小了,大约是 12KB。 PONG 消息和 PING 消息的内容一样,所以,它的大小大约是 12KB。每个实例发送了 PING 消息后,还会收到返回的 PONG 消息,两个消息加起来有 24KB。 如果实例正常处理的单个请求只有几 KB 的话,那么,实例为了维护集群状态一致传输的 PING/PONG 消息,就要比单个业务请求大了。而且,每个实例都会给其它实例发送 PING/PONG 消息。随着集群规模增加,这些心跳消息的数量也会越多,会占据一部分集群的网络通信带宽,进而会降低集群服务正常客户端请求的吞吐量。

# 通讯频率

Redis Cluster 的实例启动后,默认会每秒从本地的实例列表中随机选出 5 个实例,再从这 5 个实例中找出一个最久没有通信的实例,把 PING 消息发送给该实例。这是实例周期性发送 PING 消息的基本做法。 这有可能会出现,有些实例一直没有被发送 PING 消息,导致它们维护的集群状态已经过期了。 为了避免这种情况,Redis Cluster 的实例会按照每 100ms 一次的频率,扫描本地的实例列表,如果发现有实例最近一次接收 PONG 消息的时间,已经大于配置项 cluster-node-timeout 的一半了(cluster-node-timeout/2),就会立刻给该实例发送 PING 消息,更新这个实例上的集群状态信息。 我们来总结下单实例每秒会发送的 PING 消息数量,如下所示:

PING 消息发送数量 = 1 + 10 * 实例数(最近一次接收 PONG 消息的时间超出 cluster-node-timeout/2)

假设单个实例检测发现,每 100 毫秒有 10 个实例的 PONG 消息接收超时,那么,这个实例每秒就会发送 101 个 PING 消息,约占 1.2MB/s 带宽。如果集群中有 30 个实例按照这种频率发送消息,就会占用 36MB/s 带宽,这就会挤占集群中用于服务正常请求的带宽。

# 如何降低实例间的通信开销

我们现在知道,实例间发送消息的频率有两个。

每个实例每 1 秒发送一条 PING 消息。 这个频率不算高,如果再降低该频率的话,集群中各实例的状态可能就没办法及时传播了。

每个实例每 100 毫秒会做一次检测,给 PONG 消息接收超过 cluster-node-timeout/2 的节点发送 PING 消息。

实例按照每 100 毫秒进行检测的频率,是 Redis 实例默认的周期性检查任务的统一频率,我们一般不需要修改它。

配置项 cluster-node-timeout 定义了集群实例被判断为故障的心跳超时时间,默认是 15 秒。如果 cluster-node-timeout 值比较小,那么,在大规模集群中,就会比较频繁地出现 PONG 消息接收超时的情况。 为了避免过多的心跳消息挤占集群带宽,我们可以调大 cluster-node-timeout 值,比如说调大到 20 秒或 25 秒。这样一来, PONG 消息接收超时的情况就会有所缓解,单实例也不用频繁地每秒执行 10 次心跳发送操作了。 如何验证 为了验证调整 cluster-node-timeout 值后,是否能减少心跳消息占用的集群网络带宽,我给你提个小建议:你可以在调整 cluster-node-timeout 值的前后,使用 tcpdump 命令抓取实例发送心跳信息网络包的情况。 执行下面的命令后,我们可以抓取到 192.168.10.3 机器上的实例从 16379 端口发送的心跳网络包,并把网络包的内容保存到 r1.cap 文件中: bash复制代码tcpdump host 192.168.10.3 port 16379 -i 网卡名 -w /tmp/r1.cap

通过分析网络包的数量和大小,就可以判断调整 cluster-node-timeout 值前后,心跳消息占用的带宽情况了。

# 关于Codis

Codis采取单独管理Slot的 以及作为redis的一个前端代理的方式可以不使用Gossip协议,所以没有这部带宽占用。但是codis已经很久不维护,并且引入了中间件以后会降低整体性能。 所以最好的方法 还是 1、控制集群实例的数量 2、调大cluster-node-timeout 值并做好集群故障/脑裂的自愈

Licensed under CC BY-NC-SA 4.0
comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计