主从数据不一致

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

其实这是因为主从库间的命令复制是异步进行的

延迟两个原因

  • 主从库间的网络可能会有传输延迟,所以从库不能及时地收到主库发送的命令,从库上执行同步命令的时间就会被延后。
  • 即使从库及时收到了主库的命令,但是,也可能会因为正在处理其它复杂度高的命令(例如集合操作命令)而阻塞。此时,从库需要处理完当前的命令,才能执行主库发送的命令操作,这就会造成主从数据不一致。而在主库命令被滞后处理的这段时间内,主库本身可能又执行了新的写操作。这样一来,主从库间的数据不一致程度就会进一步加剧。

解决方案

  • 在硬件环境配置方面,我们要尽量保证主从库间的网络连接状况良好。例如,我们要避免把主从库部署在不同的机房,或者是避免把网络通信密集的应用(例如数据分析应用)和 Redis 主从库部署在一起。

  • 开发一个外部程序来监控主从库间的复制进度。

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

    如果某个从库的进度差值大于我们预设的阈值,我们可以让客户端不再和这个从库连接进行数据读取,这样就可以减少读到不一致数据的情况。不过,为了避免出现客户端和所有从库都不能连接的情况,我们需要把复制进度差值的阈值设置得大一些。

总结

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

读到过期数据

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

惰性删除策略

当一个数据的过期时间到了以后,并不会立即删除数据,而是等到再有请求来读写这个数据时,对数据进行检查,如果发现数据已经过期了,再删除这个数据。

优点

尽量减少删除操作对 CPU 资源的使用,对于用不到的数据,就不再浪费时间进行检查和删除了

缺点

这个策略会导致大量已经过期的数据留存在内存中,占用较多的内存资源。

定期删除策略

Redis 每隔一段时间(默认 100ms),就会随机选出一定数量的数据,检查它们是否过期,并把其中过期的数据删除,这样就可以及时释放一些内存。

优点

可以释放一些内存

缺点

Redis 为了避免过多删除操作对性能产生影响,每次随机检查数据的数量并不多。如果过期数据很多,并且一直没有再被访问的话,这些数据就会留存在 Redis 实例中。

从库本身不会执行删除操作,如果客户端在从库中访问留存的过期数据,从库并不会触发数据删除。

那么,从库会给客户端返回过期数据吗?

这就和你使用的 Redis 版本有关了。如果你使用的是 Redis 3.2 之前的版本,那么,从库在服务读请求时,并不会判断数据是否过期,而是会返回过期数据。在 3.2 版本后,Redis 做了改进,如果读取的数据已经过期了,从库虽然不会删除,但是会返回空值,这就避免了客户端读到过期数据。所以,在应用主从集群时,尽量使用 Redis 3.2 及以上版本。

Redis 设置过期时间的命令,命令给数据设置的过期时间在从库上可能会被延后,导致应该过期的数据又在从库上被读取到了。

设置数据过期时间的命令一共有 4 个,我们可以把它们分成两类:

  • EXPIRE 和 PEXPIRE:它们给数据设置的是从命令执行时开始计算的存活时间;
  • EXPIREAT 和 PEXPIREAT:它们会直接把数据的过期时间设置为具体的一个时间点。

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

总结

对于读到过期数据,这是可以提前规避的,一个方法是,使用 Redis 3.2 及以上版本;另外,你也可以使用 EXPIREAT/PEXPIREAT 命令设置过期时间,避免从库上的数据过期时间滞后。不过,这里有个地方需要注意下,因为 EXPIREAT/PEXPIREAT 设置的是时间点,所以,主从节点上的时钟要保持一致,具体的做法是,让主从节点和相同的 NTP 服务器(时间服务器)进行时钟同步。

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

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

protected-mode 配置项

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

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

我们在应用主从集群时,要注意将 protected-mode 配置项设置为 no,并且将 bind 配置项设置为其它哨兵实例的 IP 地址。这样一来,只有在 bind 中设置了 IP 地址的哨兵,才可以访问当前实例,既保证了实例间能够通信进行主从切换,也保证了哨兵的安全性。

cluster-node-timeout 配置项

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

建议你将 cluster-node-timeout 调大些(例如 10 到 20 秒)

建议

Redis 中的 slave-serve-stale-data 配置项设置了从库能否处理数据读写命令,你可以把它设置为 no。这样一来,从库只能服务 INFO、SLAVEOF 命令,这就可以避免在从库中读到不一致的数据了。

slave-serve-stale-data和slave-read-only区别

slave-read-only 是设置从库能否处理写命令,slave-read-only 设置为 yes 时,从库只能处理读请求,无法处理写请求,你可不要搞混了。