AOF日志和RDB内存快照两块内容只是保证了Redis数据的可靠性。但还是存在数据丢失的风险,若Redis宕机的期间,有操作命令到了客户端,此时这些数据并没有记录到日志或者在rdb快照中。
Redis的高可靠性
主要有两点:数据少丢失、连接少断开
AOF日志和RDB内存快照是确保了数据的准确性,主从库模式则是确保了连接的稳定性。
而主从库顾名思义就是有主库和从库,需要将从库与主库进行连接,连接后从库就会从主库中复制数据,主库若有新增数据也会增量的同步到从库中。
方法1:有两个redis实例,redis1(168.172.3.4)和redis2(168.172.3.5),那么可以设置redis2是redis1的从库。在redis2实例中输入命令replicate(在Redis 5.0之前使用的是slaveof):
replicaof 168.172.3.4 6379
这样就形成了主从关系,Redis2实例是redis1实例的从库。
方法2:
1.将redis所在的文件夹复制多份,修改文件夹名称。
2.修改文件夹的redis.windows.conf文件内的port端口,不与主库一致就行。并修改slaveof进行主从连接。
3.将新增的redis实例设置新的服务。
以下为图片:
以6380为例:
然后在cmd中输入redis-server --service-install redis.windows.conf --loglevel verbose --service-name redis6380
命令生成redis服务
启动主库服务,再启动从库服务即可自动连接主库。
而客户端可以选择要连接的服务端redis-cli -h 127.0.0.1 -p 6380
即可连接6380服务端。
主从库之间,是怎样同步数据的?
流程图如下:
第一阶段:从库与主库创建连接,并通知主库,等待确认后,向主库发送命令psync,带有参数主库runid以及offset。runid:是主库随意生成的唯一实例id,由于是第一次连接,还不知道主库的runid所以为"?",offset: 为-1指的是第一次同步。然后主库中返回FULLRESYNC[RUNID][OFFSET]命令,表示第一次为全量同步,同时从库记录runid以及offset。
第二阶段:由主库bgsave生成一个rdb文件,传输给从库,从库接收到rdb文件后,将从库的数据都删除(防止数据错误),再执行rdb。
第三阶段:再将传输rdb文件过程中新到的命令传输给从库。具体就是在rdb传输后,将replication buffer里的修改数据再次传给从库。
这样一来,主从库的同步就实现了。
主从库同步主要有两个地方有开销:
- 主库生成rdb文件(需要fock子进程,会影响主线程。)
- 传输rdb文件(会占用主库的带宽)
如若从库的数量过多的话,那么每个从库都会请求与主库进行数据交互,就势必会造成主库不断的fock子进程生成RDB,我们知道fock的时候会是主库的线程阻塞,影响主库的使用。同时传输rdb文件的时候也会增加主库的网络带宽,这就影响了主库应用程序的响应速度。
那有什么方法能分担主库的压力呢?
有,那就是 “主-从-从”模式
主从级联模式分担主库压力
使用“主-从-从”模式让从库帮主库分担生成RDB文件和传输RDB文件的压力。
当部署主从集群的时候,我们可以为某个从库手动选择主库:
replicaof 从库ip 6379
这样从库就知道不需要去和主库进行数据的交互了,帮主库分担了压力。一旦主从库形成了链接,就会一直保持,主库就会通过这个链接将后续新的数据传递到从库中,这就是基于长链接的命令传播。
下面画一个主-从-从模式的图:
要是主从间的网络链接断了怎么办?
在redis2.8之前,网络连接突然闪断后,从库就会与主库进行一次全量同步,若数据量庞大,那开销就会很大。
在redis2.8之后,网络断开后,会使用增量复制的方法继续同步,将断连期间新增的数据同步到从库中。
具体操作是,在断连期间,若有新的数据进入主库,会将数据保存到replication buffer,并把数据同步到缓冲区repl_backlog_buffer内。
repl_backlog_buffer是一个扇形缓冲区,主库记录的是自己写到的位置,从库记录的是自己读到的位置。 若主库在断连期间有N条数据进来,那么就有偏移量master_repl_offset,在主从链接上后,从库就将自己读到的位置slave_repl_offset发送给主库,主库就自行判断取得master_repl_offset——slave_repl_offset之间的数据传输给从库。
一开始主库与从库都在一个起始位置,当主库有数据读入的时候,master_repl_offset偏移量就产生,主从连接上后,slave_repl_offset偏移量也跟着产生,正常来说master_repl_offset与slave_repl_offset是一致的。
这里要注意的是,由于repl_backlog_buffer是扇形缓冲区,那么如果主从迟迟没有连接上,导致从库的偏移量始终追不上主库,那么主库新的数据就会将扇形缓冲区的数据覆盖,就会导致数据丢失。
解决这个方法可以配置repl_backlog_size的大小,来将缓冲区设置的较大,不让新的数据覆盖掉还未同步的数据。
如果主库挂了,如何不间断服务?
如果主库挂了,这时就需要有 “哨兵机制” 来对主从库进行:监听、换主、通知 这三个过程。
动作 | 作用 |
---|---|
监听 | 监听主库、从库是否与客户端断连。 |
换主 | 若主库客观下线则,从从库中选择一个成为主库 |
通知 | 更换主库后,需要通知从库新主库的ip,使从库与新主库连接,并通知客户端。 |
监听:监听主库是否下线
哨兵会使用ping与主库和从库进行交互,若ping不通,则判断主库主观下线。但是这可能只是哨兵觉得主库下线了,但是实际上并没有下线。可能是因为主库与哨兵之间网络连接不好,或者此时主库较为拥塞。若这时就判定主库下线,那么就可能会形成不必要的开销。
若要处理这点,就需要配置3个哨兵,由3个哨兵去监听主从库,若有3/2+1以上的哨兵设置主库主观下线,那么就可以判定主库客观下线,从而进行主从库切换。
换主:主库下线后,从从库中选择一个成为新主库
换主的过程有三个筛选过程:
- 筛选掉已经下线的从库
- 从剩下的从库中选择网络连接较好的从库
- 首先根据优先级筛选(可以通过配置slave_priority),若没有优先结果;再次根据从库与旧主库之间数据是否接近(使用master_repl_offset与slave_repl_offset是否接近),若都接近;最后根据从库的ID,ID最小,就选定该从库为新主库。
通知:通知从库新主库ip,通知redis新主库
这其实涉及到了哨兵集群,哨兵集群就是有多个哨兵共同来监控。
要怎么建立集群?
可以通过redis的PUB\SUB订阅机制,每一个哨兵将自己的ip和端口号发布到同一个频道中,然后都订阅这个频道,此时每个哨兵都知道其他哨兵的ip与端口号,就可以建立起网络连接,此时就形成了哨兵集群。
leader哨兵切换完新主库后,也是通过PUB发布新主库ip,然后其他从库订阅频道,获取新主库ip进行主从库配置。
那要怎么通知到redis呢?
其实哨兵就是一个运行在特定条件下的redis实例,同样它们有自己的PUB\SUB订阅机制,也有自己的一些事件。redis客户端只需要订阅哨兵发布的事件,就可以通知到redis了。
哨兵的订阅有很多事件,主要的时间有:主库下线判断、新主库选定、从库重新配置。
这样我们就可以在客户端中订阅哨兵的消息了
订阅“所有实例客观下线的事件状态”
SUBSCRIBE +odown
也可以订阅所有事件
SUBSCRIBE *
最后我们来看看如何添加哨兵?
sentinel monitor [mater_name] [ip] [redis_port] [quorum]
可以通过上述命令语句添加哨兵,对应参数有:
master_name : 主库名
ip : 哨兵实例ip
redis_port : 6379
quorum : 通过quorum判断客观下线以及竞选leader
如果哨兵挂掉了,还能进行主从库切换吗?
若有一个哨兵挂掉了,还有其他的哨兵实例存在,只要剩余哨兵实例的个数大于配置哨兵时设置的quorum值,那就可以进行主从库切换。若有四个哨兵,挂了一个,那若需要选举一个leader来进行主从库切换就必须有3个以上哨兵同意,但是此时只有三个哨兵,3/2+1=2所以此时并不能进行主从库切换。
那就多配置几个哨兵,每个哨兵之间建立网络连接,形成哨兵集群,并且每个哨兵都与主库从库以及redis客户端都保持通信。
哨兵要怎么获取到从库的信息?
哨兵通过向主库发送INFO命令,主库响应后,返回从库列表,然后哨兵就根据从库列表的连接信息来与从库进行连接。
那要如何进行主从切换?
首先就要判断主库为客观下线,这个过程就是每个实例自身判断主库为主观下线,都向其他哨兵发送is-master-down-by-addr标识,返回N或Y。
一个哨兵获得仲裁赞成票超过quorum值后,就将主库标记为客观下线。此时该哨兵自己想要当leader,就提起leader仲裁。仲裁赞成票超过quorum值,就竞选成为leader,进行主从库切换。
时刻 | S1 | S2 | S3 |
---|---|---|---|
T1 | S1想要成为leader,给自己投一票赞成,将请求发送给其他哨兵 | ||
T2 | S3收到S1的请求,但是自己也想要成为leader,所以给S1投反对票(N),给自己投了赞成票 | ||
T3 | S2先收到了S1的请求,给S1投了赞成票,此时S1有两票 | ||
T4 | 这时S2才收到了S3的请求,但是已经给S1投了赞成票,所以只能给S3投反对票 |
此时的结果就是,哨兵1获得2票,哨兵3获得1票,3/2+1=2,所以哨兵1就成为了leader,进行主从切换的操作。
为什么S2会先接受到S1的请求?这个就可能是应为S3和S2之间的网络连接正在拥塞,和S1的网络连接较好,先接收到了S1的请求。
此时,主从库切换的过程就完成了。
如何通知从库以及redis客户端新主库的信息?
这就是使用redis客户端的PUB\SUB发布\订阅机制以及哨兵本身的PUB\SUB订阅机制。
前一种与哨兵之间数据交互;redis客户端从哨兵的发布\订阅机制获取新主库信息。