32 Redis 的主从同步和故障切换问题


前言

Redis 的主从同步机制不仅可以让从库服务更多的读请求,分担主库的压力,而且还能在主库发生故障时,进行主从库切换,提供高可靠服务。

不过在实际使用主从机制的时候,很容易踩到一些坑。分别是主从数据不一致、读到过期数据、配置项设置得不合理从而导致服务挂 掉。

踩到这些坑,业务应用不仅会读到错误数据,而且很可能会导致 Redis 无法正常使用,要全面地掌握这些坑的成因,提前准备一套规避方案,以及采坑后相应的解决方案。

一、主从数据不一致

主从数据不一致是指客户端从从库中读取到的值和主库中的最新值并不一致。

假设主从库之前保存的用户年龄值是 19,但是主库接收到了修改命令,已经把这个数据更新为 20 了,但是从库中的值仍然是 19。那么如果客户端从从库中读取用户年龄值,就会读到旧值。

这是因为主从库间的命令复制是异步进行的。在主从库命令传播阶段,主库收到新的写命令后会发送给从库。但是主库并不会等到从库实际执行完命令后,再把结果返回给客户端,而是主库自己在本地执行完命令后,就会向客户端返回结果了。如果从库还没有执行主库同步过来的命令,主从库间的数据就不一致了。

从库会滞后执行同步命令主要有两个原因:

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

而在主库命令被滞后处理的这段时间内, 主库本身可能又执行了新的写操作。这样主从库间的数据不一致程度就会进一步加剧。

两种应对方法:

  1. 在硬件环境配置方面,要尽量保证主从库间的网络连接状况良好。例如,要避免把主从库部署在不同的机房,或者是避免把网络通信密集的应用(例如数据分析应用)和 Redis 主从库部署在一起。
  2. 开发一个外部程序来监控主从库间的复制进度。Redis 的 INFO replication 命令可以查看主库接收写命令的进度信息
    (master_repl_offset)和从库复制写命令的进度信息(slave_repl_offset),就可以开发一个监控程序,先用 INFO replication 命令查到主、从库的进度,然后用 master_repl_offset 减去 slave_repl_offset,得到从库和主库间的复制进度差值。如果某个从库的进度差值大于预设的阈值,可以让客户端不再和这个从库连接进行数据读取,可以减少读到不一致数据的情况。不过为了避免出现客户端和所有从库都不能连接的情况,需要把复制进度差值的阈值设置得大一些。在应用 Redis 时,可以周期性地运行这个流程来监测主从库间的不一致情况。
    在这里插入图片描述
    监控程序可以一直监控着从库的复制进度,当从库的复制进度又赶上主库时,就允许客户端再次跟这些从库连接。

二、读取过期数据

在使用 Redis 主从集群时,有时会读到过期数据。例如,数据 X 的过期时间是 202010240900,但是客户端在 202010240910 时,仍然可以从从库中读到数据 X。一个数据过期后,应该是被删除的,客户端不能再读取到该数据,这是由 Redis 的过期数据删除策略引起的。

Redis 同时使用了两种策略来删除过期的数据,分别是惰性删除策略和定期删除策略:

  • 惰性删除策略: 当一个数据的过期时间到了以后,并不会立即删除数据,而是等到再有请求来读写这个数据时,对数据进行检查,如果发现数据已经过期了,再删除这个数据。好处是尽量减少删除操作对 CPU 资源的使用,对于用不到的数据,就不再浪费时间进行检查和删除了。但是会导致大量已经过期的数据留存在内存中,占用较多的内存资源。所以 Redis 在使用这个策略的同时,还使用了第二种策略:定期删除策略。
  • 定期删除策略: Redis 每隔一段时间(默认 100ms),就会随机选出一定数量的数据,检查它们是否过期,并把其中过期的数据删除,这样就可以及时释放一些内存。

定期删除策略可以释放一些内存,但是 Redis 为了避免过多删除操作对性能产生影响,每次随机检查数据的数量并不多。如果过期数据很多,并且一直没有再被访问的话,这些数据就会留存在 Redis 实例中。业务应用之所以会读到过期数据,这些留存数据就是一个重要因素。

惰性删除策略实现后,数据只有被再次访问时,才会被实际删除。如果客户端从主库上读取留存的过期数据,主库会触发删除操作,此时客户端并不会读到过期数据。但是从库本身不会执行删除操作,如果客户端在从库中访问留存的过期数据,从库并不会触发数据删除。如果你使用的是 Redis 3.2 之前的版本,从库在服务读请求时,并不会判断数据是否过期,而是会返回过期数据。在 3.2 版本后,Redis 做了改进,如果读取的数据已经过期了,从库虽然不会删除,但是会返回空值,这就避免了客户端读到过期数据。所以在应用主从集群时,尽量使用 Redis 3.2 及以上版本。

使用了 Redis 3.2 后的版本,还会读到过期数据,这跟 Redis 用于设置过期时间的命令有关系,有些命令给数据设置的过期时间在从库上可能会被延后,导致应该过期的数据又在从库上被读取到了。

设置数据过期时间的命令一共有 4 个,分成两类:

  1. EXPIRE 和 PEXPIRE:是从命令执行时开始计算的存活时间;
  2. EXPIREAT 和 PEXPIREAT:把数据的过期时间设置为具体的一个时间点。

这 4 个命令的参数和含义如下表所示:
在这里插入图片描述
例一,使用 EXPIRE 命令,执行下面的命令把 testkey 的过期时间设置为 60s 后。

EXPIRE testkey 60

例二,使用 EXPIREAT 命令,执行下面的命令让 testkey 在 2020 年 10 月 24 日上午 9 点过期,命令中的 1603501200 就是以秒数时间戳表示的 10 月 24 日上午 9 点。

EXPIREAT testkey 1603501200

这些命令导致读到过期数据的原因:

当主从库全量同步时,如果主库接收到了一条 EXPIRE 命令,那么主库会直接执行这条命令。这条命令会在全量同步完成后,发给从库执行。而从库在执行时,就会在当前时间的基础上加上数据的存活时间,这样从库上数据的过期时间就会比主库上延后了。

假设当前时间是 2020 年 10 月 24 日上午 9 点,主从库正在同步,主库收到了一条命令: EXPIRE testkey 60,表示 testkey 的过期时间就是 24 日上午 9 点 1 分,主库直接执行了这条命令。 但是主从库全量同步花费了 2 分钟才完成。等从库开始执行这条命令时,时间已经是 9 点 2 分了。而 EXPIRE 命令是把 testkey 的过期时间设置为当前时间的 60s 后,也就是 9 点 3 分。如果客户端在 9 点 2 分 30 秒时在从库上读取 testkey,仍然可以读到 testkey 的值。但是 testkey 实际上已经过期了。

为了避免这种情况,建议在业务应用中使用 EXPIREAT/PEXPIREAT 命令, 把数据的过期时间设置为具体的时间点,避免读到过期数据。

小结:

主从数据不一致,Redis 采用的是异步复制,所以无法实现强一致性保证(主从数据时时刻刻保持一致),数据不一致是难以避免的。应对方法:保证良好网络环境,以及使用程序监控从库复制进度,一旦从库复制进度超过阈值,不让客户端连接从库。

过期数据可以提前规避的,使用 Redis 3.2 及以上版本;使用 EXPIREAT/PEXPIREAT 命令设置过期时间,避免从库上的数据过期时间滞后。需要注意下,因为 EXPIREAT/PEXPIREAT 设置的是时间点,所以主从节点上的时钟要保持一致,让主从节点和相同的 NTP 服务器(时间服务器)进行时钟同步。

三、不合理配置项导致的服务挂掉

涉及到的配置项有两个,分别是 protected-mode 和 cluster-node-timeout :

  1. protected-mode 配置项:限定哨兵实例能否被其他服务器访问。设置为 yes 时, 哨兵实例只能在部署的服务器本地进行访问。设置为 no 时,其他服务器也可以访问这个哨兵实例。如果 protected-mode 被设置为 yes,而其余哨兵实例部署在其它服务器, 这些哨兵实例间就无法通信。当主库故障时,哨兵无法判断主库下线,也无法进行主从切换,最终 Redis 服务不可用。所以在应用主从集群时,要注意将 protected-mode 配置项设置为 no,并且将 bind 配置项设置为其它哨兵实例的 IP 地址。只有在 bind 中设置了 IP 地址的哨兵,才可以访问当前实例,既保证了实例间能够通信进行主从切换,也保证了哨兵的安全性。
    设置了下面的配置项,部署在 192.168.10.3/4/5 这三台服务器上的哨兵实例就可以相互通信,执行主从切换。
protected-mode no
bind 192.168.10.3 192.168.10.4 192.168.10.5
  1. cluster-node-timeout 配置项:设置了 Redis Cluster 中实例响应心跳消息的超时时间。在 Redis Cluster 集群中为每个实例配置了“一主一从”模式时,如果主实例发生故障,从实例会切换为主实例,受网络延迟和切换操作执行的影响,切换时间可能较长,就会导致实例的心跳超时(超出 cluster-node-timeout)。实例超时后,就会被 Redis Cluster 判断为异常。而 Redis Cluster 正常运行的条件就是,有半数以上的实例都能正常运行。所以如果执行主从切换的实例超过半数,而主从切换时间又过长的话,就可能有半数以上的实例心跳超时,从而可能导致整个集群挂掉。建议将 cluster-node- timeout 调大些(例如 10 到 20 秒)。

总结

Redis 主从库同步时可能出现的 3 个坑,分别是主从数据不一致、读取到过期数据、不合理配置项导致服务挂掉。
在这里插入图片描述
主从库数据不一致的问题建议:Redis 中的 slave- serve-stale-data 配置项设置了从库能否处理数据读写命令,把它设置为 no。从库只能服务 INFO、SLAVEOF 命令,避免在从库中读到不一致的数据了。不过要注意下这个配置项和 slave-read-only 的区别,slave-read-only 是设置从库能否处理写命令,slave-read-only 设置为 yes 时,从库只能处理读请求,无法处理写请求,可不要搞混了。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

海陆云

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值