在实际开发中Redis的节点部署大多数不是单机,基本采用集群的形式,而目前常见集群模式有三种主从模式、主从哨兵模式、分片集群模式,其中我们较为熟悉的应该是主从哨兵模式,采用一个主节点多个从节点,同时采用哨兵集群监控主从集群,从而使Redis高可用,实现高可用也带来了一些弊端如Redis在主从节点读取的数据不一致、从节点还能读取过期数据等等,下面分析一波。
主从数据怎么不一致呢?举例说明
当主节点的数据X=20,主节点接收到修改命令将主节点的数据X改为15,而从节点还是X=20这就造成了主从节点不一致的情况。
首先思考的是为什么主从数据会不一致呢?
我们知道在主从节点进行全量同步后,后续主从节点增量同步都是异步的方式,也就是说主节点接收到写命令后,会将命令记录到缓冲区replication buffer中,后续将缓冲区中的数据同步到从节点,同步示例图如下。
具体分析就是在主从库命令同步阶段,主节点收到新的写命令后,会发送给从节点,但是主节点并不会等待从节点执行完毕后再给客户端应答,而是主节点在自己的本地将命令执行完毕后,直接向客户端返回,如果从节点没有立即执行主节点同步过来的命令,那么这就造成了主从数据不一致的情况,那么什么原因会导致从节点没有马上执行主节点同步过来的命令呢?有如下原因
如果出现了上述的两种情况我们应该如何避免呢?
这个思考点其实稍微有点奇怪,为什么说过期数据还能读取呢?首先我们思考我们设置一个有过期时间的键值,那么键值过期后是怎么删除的呢?目前是两种方式惰性删除和定期删除
我们在了解两种键值删除方式后,对客户端为什么读取过期数据这个问题应该也就有思路了,首先我们注意到定期删除方式时,每次是选取一定数量的键值,那么也就是说如果过期的键值数据量巨大,那么Redis不可能一次性将所有过期的键值淘汰,必须分为多次,这时去访问已经过期的键值是有可能访问到已过期数据的。
其次惰性删除,必须要访问键值后才能检查是否过期,过期将被删除,但如果客户端访问的是从节点呢?从节点默认不具备删除功能,那么客户端在从节点访问过期数据是有可能访问到过期数据的,从节点只有接收到主节点的删除命令才会删除键值。
那么从节点会返回过期数据吗?这是根据Redis版本来决定的,如果Redis的版本是3.2以下的,那么从节点在进行读请求时,是不会校验键值是否过期的,有可能返回过期数据,如果是3.2之后的版本那么读请求会检查过期时间,注意这里和主节点的处理方式不同如果读取的键值是过期的从节点会返回空值,但不会删除该键值,只有接收到主节点的删除命令才会删除。
所以在使用主从时最好采用3.2之后的版本,那么3.2之后就不会读取到过期数据了吗?那必然不是的,还有一种场景
Redis给我们提供了两种设置过期时间的方式
具体是什么意思呢?命令演示如下
### 设置2022-06-13 20:26:50过期
127.0.0.1:6379> EXPIREAT name 1655123210
(integer) 1
### 设置10s后过期
127.0.0.1:6379> EXPIRE name 10
当主从节点开始进行全量同步时,主节点接收到命令EXPIRE name 10给一个键值设置过期时间,这时主节点会等主从节点全量同步完毕后,才向从节点发送全量同步期间接收到的命令EXPIRE name 10。
如果全量同步时间长,假设主节点执行命令时间为2022/06/13 23:16:00那么主节点name的过期时间应该为2022/06/13 23:16:10,而如果主从节点全量同步完毕需要两分钟那么从节点执行命令的时间为2022/06/13 23:18:00,从节点name过期时间就为2022/06/13 23:18:10,这时就造成了主从节点数据不一致的情况。
这种情况一般建议使用EXPIREAT命令,明确指出键值的具体过期时间点,但也要注意需要保证主从节点的时钟基本一致,不然时钟跳跃一样会导致数据不一致的情况。
这个配置和slave-read-only很像一定要注意区分。
一定一定注意,上面两种情况,从节点都不会主动删除主节点同步过来带有过期时间的key值,也就是说主节点同步过来的key值由主节点自己维护,从节点并不会介入(除非主节点同步删除命令到从节点)。
==就算从节点可以修改键值,也并不建议从节点修改==,这样可能造成数据不一致的情况,假设主从节点有键值a:stock,这时客户端1想将键值a:stock改为13给主节点发送了set命令,客户端2想将键值a:stock改为15给从节点发送了set命令,这时就造成了主从节点数据不一致的情况。
留言与评论(共有 0 条评论) “” |