Redis集群的三种模式?
- 主从集群
- 哨兵集群
- 切片集群
如何提高Redis高可靠性?
- 数据尽量少丢失。AOF和RDB保证。
- 服务尽量少中断。增加副本冗余量(将一份数据同时保存在多个实例上)。
Redis主从模式的为何采用读写分离操作?(主从模式)
如上图所示,如果不做读写分离,如果客户端对一个值做了三次写操作,则会造成数据不一致。如果需要在不做读写分离的情况下做数据一致,就涉及到加锁、实例间协商是否完成修改等一系列操作(实例间相互通信,这一操作是否完成),这回带来巨额的开销。
如果采用读写分离,所有的写操作就只会在主库上进行,不需要协商是否完成。主库有了最新的数据后,会同步给从库。
主从库同步的三种模式?
- 全量复制
- 基于长连接的命令传播
- 增量复制
主从库间如何进行第一次同步?(全量复制)
使用replicaof(Redis5.0之前使用slaveof)命令形成主从关系,之后会按照下图所示完成数据同步。
1、从库和主库建立连接,并告诉主库即将进行同步,主库确认回复后,主从库间可以开始同步。
2、从库给主库发送psync命令,表示要进行数据同步,主库根据这个命令的参数来启动复制。psync命令包含了主库的runID和主库复制进度offset两个参数。主库收到psync命令后,会用FULLRESYNC响应命令带上两个参数:主库的runID和主库的复制进度offset。FULLRESYNC响应表示第一次复制采用的全量复制,也就是主库会把当前所有的数据都复制给从库。
- runID:是每个Redis实例启动时都会自动生成的一个随机ID,用来唯一标记这个实例。当从库和主库第一次复制时,因为不知道主库的runID,所以runID为“?”。
- offest:此时设为-1,标识第一次复制。
3、从库收到数据后,在本地完成数据加载,这过程依赖于内存快照生成的RDB文件
主库执行bgsave命令,生产rdb文件,接着讲文件发送给从库,从库接收到RDB文件后,会先清空当前数据库,然后加载RDB文件,因为从库和主库建立连接前,可能保存了其他数据,为了避免之前的数据影响,所以从库需先清空当前数据库。
在主库将数据同步给从库过程中,主库不会阻塞,仍然可以接受请求,为了保证主从库的数据一致性,主库会再内存中用专门的replication buffer,记录RDB文件生成后收到的所有写操作。
4、主库会把第二阶段执行过程中新收到的写命令,再发送给从库。当主库完成RDB文件发送后,就会把此时replication buffer中的修改操作发送给从库,从库再重新执行这些操作。
为什么会有replication buffer?
Redis和客户端、从库通信,Redis都需要分配一个内存buffer进行数据交互,所有数据交互都是通过这个buffer进行的。Redis先把数据写到这个buffer中,然后再把buffer中的数据发到client socker中再通过网络发送出去,这样就完成了数据交互。而这个buffer通常叫做replication buffer。
如果主从传播命令时,因为某些原因从库处理得非常慢,那么主库上的这个buffer就会持续增长,消耗大量的内存资源,甚至OOM,所以redis提供了client-output-buffer-limit参数限制这个buffer大小。如果超过限制,主库会强制断开这个client连接。也就是说从库处理慢导致主库内存buffer的积压达到限制后,主库会强制断开从库的连接。此时主从复制会中断,中断后如果从库再次发起复制请求,那么此时可能会导致恶性循环,引发复制风暴。
在一次全量复制中,对主库来说,生成RDB文件和传输RDB文件是非常耗时的。所以在此期间需要replication buffer这个缓冲区去将这期间的命令保存起来。
长连接的命令传播?
一旦主从库完成全量复制,它们之间就会维护一个网络连接,主库会通过这个连接将后续陆续收到的命令操作再同步给从库。这个过程也称为基于长连接的命令传播,可以避免频繁简历连接的开销。
但这个过程存在风险点,最常见的是网络断连或阻塞。
主从库间网络断了怎么办?
在Redis2.8之前,如果主从库之间网络闪断,从库会和主库重新进行一次全量复制。在Redis2.8开始,主从库会采用增量复制的方式同步。
增量复制是通过repl_backlog_buffer这个缓冲区。repl_backlog_buffer是一个环形缓冲区,主库会记录自己写到的位置,从库会记录自己已经读到的位置。主库在缓冲区写的位置会逐步偏离起始位置,对主库来说,对应的偏移量叫做master_repl_offset。而从库再复制完写操作命令后,它在缓冲区中的读的位置也开始逐步偏移,此时,从库已复制的偏移量叫做slave_repl_offset。这两个偏移量基本相等,也就是在同一位置。在主从网络断连后连接恢复,从库会给主库发送psync命令,并把自己当前的slave_repl_offset发给主库,主库会判断自己的master_repl_offset和slave_repl_offset之间的差距。此时,主库会把master_repl_offset和slave_repl_offset之间的命令操作同步给从库。
master_repl_offset和slave_repl_offset值是单调递增的,值可以大于repl_backlog_size。而redis会用一个名为repl_backlog_idx的值记录在环形缓冲区中最新写入位置。当repl_backlog_idx等于repl_backlog_size时,repl_backlog_idx重置为0。
如果从库的读取速度比较慢,就有可能导致从库还未读取的操作被主库新写的操作覆盖了,这会导致主从库间的数据不一致。如果从库和主库断连时间过长,造成repl_backlog_buffer上的slave_repl_offset位置上的数据被覆盖掉了,此时,主从库间将进行全量复制。
为了避免repl_backlog_buffer上的slave_repl_offset位置上的数据被覆盖,可以调整repl_backlog_size这个参数,这个参数和缓冲空间大小有关。缓冲空间的计算公式是:主库写入命令速度 * 操作大小 - 主从库间网络传输命令速度 * 操作大小。而repl_backlog_size这个最终值会等于:缓冲空间大小 * 2。
例如:主库每秒写入2000个操作,每个操作2KB,网络每秒传输1000个操作,那么有1000个操作需要缓冲起来,那么repl_backlog_size的大小至少需要2MB,为了应对突发压力,repl_backlog_size需要设置为4MB。
主从全量同步为什么使用RDB不使用AOF?
1、RDB文件内容是经过压缩的二进制数据(不同数据类型数据做了针对性优化),而AOF恩建记录的是每次写操作的命令。RDB文件比AOF文件小。
2、从库直接按照RDB协议解析还原数据快。而AOF需要一次重放每个写命令。
主从切换,哨兵机制的基本流程?(哨兵模式)
哨兵是一个运行在特殊模式下的Redis进程,主从库实例运行的同时,它也在运行。哨兵主要负责三个任务:监控、选主、通知。
- 监控:在哨兵运行时,周期性地给所有的主从库大宋PING命令,检测它们是否仍然在线运行。如果从库没有在规定时间内响应哨兵的PING命令,哨兵就会把它标记为“下线状态”,同样,如果主库也没有再规定时间内响应哨兵的PING命令,哨兵会判定主库下线,然后开始自动切换主库。
- 选主:主库挂了之后,哨兵就需要从多个从库中,按照一定的规则选择一个实例,把它作为新的主库。
- 通知:在选主之后,哨兵需要把新主库的连接信息发给其他从库,让他们执行replicaof命令,和新主库建立连接,并进行数据复制。同事,哨兵会把新主库的连接信息通知给客户端,让他们把请求操作发到新主库上。
哨兵需作出的两个决策?
- 在监控中,哨兵需要判断主库是否处于下线状态。
- 在选主中,哨兵需要决定哪个从库作为主库。
哨兵如何判断下线?(监控)
主观下线:哨兵进程会使用PING命令检测它自己和主、从库的网络连接情况,用来判断实例的状态。如果哨兵发现主库或从库对PING命令的响应超时,那哨兵会先把它标记为“主观下线”。
如果检测的是从库,哨兵就简单把它标记为“主观下线”。如果检测的是主库,哨兵就不能简单标记“主观下线”,开启主从切换。因为存在哨兵误判的可能性,一旦启动了主从切换,后续的选主和通知操作都会带来额外的计算和通信开销。
为了减少哨兵误判,它通常会采用多实例组成的集群模式进行部署,也被成为哨兵集群。
客观下线:只有大多数的哨兵实例,都判断主库已经“主观下线”,主库才会标记为“客观下线”。这个叫法也是表名主库下线成为了一个客观事实了。
如何选定新主库?(选主)
“筛选+打分”:在多个从库中,先按照一定的筛选条件,把不符合条件的从库去掉,然后再按照一定的规则,给剩下的从库逐个打分,得分最高者选为主库。
筛选规则:除了要检查从库的当前在线状态,还要判断它之前的网络连接状态。如果从库总是和主库断连,而且断连好过一定的阈值,那就判断这个从库网络状况不是太好,将其筛掉。具体使用配置项down-after-milliseconds*10。其中down-after-milliseconds是我们认定主从库断连的最大连接超时时间。如果在down-after-milliseconds毫秒内,主从节点都没有通过网络联系上,我们就可以认为主从节点断连了,如果断连次数超过了10次,说明这个从库网络状况不好,将被筛掉。
打分规则:1、从库优先级 2、从库复制进度 3、从库ID号
- 从库优先级:用户可以通过slave-priority配置项,给不同的从库设置不同优先级。如果有一个从库优先级最高,那它就是新主库。如果从库的优先级一样,则开始第二轮打分
- 从库复制进度 :选择和旧主库同步最接近的那个从库作为主库。比较各个从库的slave_repl_offset,值最大的从库当选主库。
- 从库ID号:ID号最小的从库当选主库
哨兵在操作主从切换的过程中,客户端能否正常请求?
如果客户端使用了读写分离,那么读请求可以在从库正常执行,不受影响。但是由于主库挂了,所以这期间写请求会失败。失败持续时间 = 哨兵切换主从时间 + 客户端感知到新主库时间。
哨兵检测主库多久没有响应就提升从库为新的主库,这个时间是可以配置的(down-after-milliseconds)。配置时间越短,哨兵越敏感。
哨兵提升一个从库作为新主库后,会把新主库的地址写入自己实例的pubsub(switch-master)中。客户端需要订阅这个pubsub,当这个pubsub有数据时,客户端就能感知到主库发生变更,同时拿到最新的主库地址。这种机制属于哨兵主动通知客户端。如果客户端因为某些原因错过了哨兵通知或者哨兵通知后客户端处理失败了。安全起见,客户端也需要支持主动去获取最新主从的地址进行访问。
所以客户端需要访问主从库时,不能直接写死主从库的地址,而是需要从哨兵集群中获取最新地址(sentinel get-master-addr-by-name命令)。