主从问题

image-20230402154043999

数据不一致

主要原因:

  1. 主从库间的网络可能会有传输延迟,所以从库不能及时地收到主库发送的命令,从库上执行同步命令的时间就会被延后。
  2. 另一方面,即使从库及时收到了主库的命令,但是,也可能会因为正在处理其它复杂度高的命令(例如集合操作命令)而阻塞。此时,从库需要处理完当前的命令,才能执行主库发送的命令操作,这就会造成主从数据不一致。

两种方法:

  1. 在硬件环境配置方面,我们要尽量保证主从库间的网络连接状况良好

  2. 我们还可以开发一个外部程序来监控主从库间的复制进度

    因为 Redis 的 INFO replication 命令可以查看主库接收写命令的进度信息(master_repl_offset)和从库复制写命令的进度信息(slave_repl_offset),所以,我们就可以开发一个监控程序,先用 INFO replication 命令查到主、从库的进度,然后,我们用 master_repl_offset 减去 slave_repl_offset,这样就能得到从库和主库间的复制进度差值了。

    如果某个从库的进度差值大于我们预设的阈值,我们可以让客户端不再和这个从库连接进行数据读取,这样就可以减少读到不一致数据的情况。

  3. 将edis 中的 slave-serve-stale-data(能否处理数据读写命令) 配置项设置为 no,这样当请求来的时候会回复”正在从master同步”,除了 INFO 和 SLAVEOF 命令。

读取过期数据

主要原因:

  1. 定期删除策略:留存有没有被删除完的过期数据。
  2. 惰性删除策略:使用的是EXPIRE 和 PEXPIRE来设置过期时间,等同步到从库时产生延迟。

解决方法:

​ 使用 Redis 3.2 及以上版本;使用 EXPIREAT/PEXPIREAT 命令设置过期时间,避免从库上的数据过期时间滞后。

配置项设置得不合理从而导致服务挂掉

  1. protected-mode 配置项:

    这个配置项的作用是限定哨兵实例能否被其他服务器访问。当这个配置项设置为 yes 时,哨兵实例只能在部署的服务器本地进行访问。当设置为 no 时,其他服务器也可以访问这个哨兵实例。

    问题:如果 protected-mode 被设置为 yes,而其余哨兵实例部署在其它服务器,那么,这些哨兵实例间就无法通信。当主库故障时,哨兵无法判断主库下线,也无法进行主从切换,最终 Redis 服务不可用。

    解决:所以,我们在应用主从集群时,要注意将 protected-mode 配置项设置为 no,并且将 bind 配置项设置为其它哨兵实例的 IP 地址

  2. cluster-node-timeout 配置项

    这个配置项设置了 Redis Cluster 中实例响应心跳消息的超时时间。

    问题:如果执行主从切换的实例超过半数,而主从切换时间又过长的话,就可能有半数以上的实例心跳超时,从而可能导致整个集群挂掉。

    解决:将 cluster-node-timeout 调大些(例如 10 到 20 秒)

脑裂问题

所谓的脑裂,就是指在主从集群中,同时有两个主节点,它们都能接收写请求。而脑裂最直接的影响,就是客户端不知道应该往哪个主节点写入数据,结果就是不同的客户端会往不同的主节点上写入数据。而且,严重的话,脑裂会进一步导致数据丢失。

原因:

  • 在主从集群中发生数据丢失,最常见的原因就是主库的数据还没有同步到从库,结果主库发生了故障,等从库升级为主库后,未同步的数据就丢失了。

  • 发现是原主库假故障导致的脑裂:

    主库是由于某些原因无法处理请求,也没有响应哨兵的心跳,才被哨兵错误地判断为客观下线的。结果,在被判断下线之后,原主库又重新开始处理请求了,而此时,哨兵还没有完成主从切换,客户端仍然可以和原主库通信,客户端发送的写操作就会在原主库上写入数据了。

解决:

Redis 已经提供了两个配置项来限制主库的请求处理,分别是 min-slaves-to-write 和 min-slaves-max-lag。

  • min-slaves-to-write:这个配置项设置了主库能进行数据同步的最少从库数量;

  • min-slaves-max-lag:这个配置项设置了主从库间进行数据复制时,从库给主库发送 ACK 消息的最大延迟(以秒为单位)。

我们可以把 min-slaves-to-write 和 min-slaves-max-lag 这两个配置项搭配起来使用,分别给它们设置一定的阈值,假设为 N 和 T。这两个配置项组合后的要求是,主库连接的从库中至少有 N 个从库,和主库进行数据复制时的 ACK 消息延迟不能超过 T 秒,否则,主库就不会再接收客户端的请求了。

通讯开销问题

为了让集群中的每个实例都知道其它所有实例的状态信息,实例之间会按照一定的规则进行通信。这个规则就是 Gossip 协议

通信开销受到通信消息大小通信频率这两方面的影响

  • 通信消息大小

    包含自身状态信息、集群十分之一实例的状态信息、Slot映射表,大概有12KB。并且PING和PONG消息内容一致。一来一回就是24KB。随着集群规模增加,这些心跳消息越来越多占据部分宽带,降低正常服务的吞吐量。

  • 通信频率

    每秒会从本地实例随机挑选5个实例,并选择一个最久没有通信的实例,发送PING命令同步状态。但是由于是随机挑选的实例,很有可能会有遗漏的实例一直没有进行通信,导致集群状态信息过期。为了解决这种情况,Redis Cluster 的实例会按照每 100ms 一次的频率扫描本地的实例列表,如果发现有实例最近一次接收 PONG 消息的时间,已经大于配置项 cluster-node-timeout 的一半了(cluster-node-timeout/2),就会立刻给该实例发送 PING 消息,更新这个实例上的集群状态信息。

解决方案:

  1. 根本的解决方法就是降低集群规模,因为实例间的通信开销会随着实例规模增加而增大(理论单个实例每秒能支撑 8 万请求操作(8 万 QPS),那么,400~ 500 个实例可支持 1600 万~2000 万 QPS)

  2. 降低通信频率:

    提高cluster-node-timeout配置项(默认是15s),但是也不能调太大了,不然会遇到实例真的发生了故障没有得到及时的检测。

    可以在调整 cluster-node-timeout 值的前后,使用 tcpdump 命令抓取实例发送心跳信息网络包的情况

    例如,执行下面的命令后,我们可以抓取到 192.168.10.3 机器上的实例从 16379 端口发送的心跳网络包,并把网络包的内容保存到 r1.cap 文件中:tcpdump host 192.168.10.3 port 16379 -i 网卡名 -w /tmp/r1.cap

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