一、关于主从的基本思路:
首先由于数据都存储在一个服务器上,如果该服务器发生某些意外,此会引出无法服务新的请求和数据丢失问题,这样就需要多个服务器。但是这多个服务器中的数据要保持一致且要有规则(比如几个服务器执行写操作),于是可以引出主从复制模式,这个模式保证多台服务器的数据一致且主从服务器直接读写分离方式,这样客户端发来写或读操作命令后,见图如下:
此外呢,如果某一时刻主服务器宕机,那么需要选该主服务器之下的从服务器之一为新主服务器,这个过程是哨兵发挥作用,就如其名,含义为一直盯着主服务器,观其挂没。
以上就是主从的基本思路,中间有许多细节并没体现(比如主服务器怎么与从服务器进行写操作同步?主服务器挂了之后,哨兵该怎么做呢?等等问题)。接下来开始认真看吧:
二、主从复制(主从怎么连接?主从怎么复制?)
- 执行此命令之后,服务器B就会变成服务器 A 的从服务器
# 服务器 B 执行这条命令
replicaof <服务器 A 的 IP 地址> <服务器 A 的 Redis 端口号>
- 从服务器开始与主服务器进行第一次同步,步骤如下:
第一部, 建立链接、协商同步。从服务器就会给主服务器发送 psync 命令, 主服务器收到 psync 命令(此命令含两个参数,分别是主服务器的 runID 和复制进度 offset)后,会用 FULLRESYNC 作为响应命令返回给对方,此响应代表主服务器会把所有的数据都同步给从服务器,即全量复制
第二部,主服务器同步数据给从服务器。主服务器执行bgsave(不影响主进程的命令,会通过fork函数生成一个子进程,也就是不影响服务客户端新的请求)生成RDB文件,并发送给从服务器,从服务器先清空数据然后加载RDB文件。但是主服务器仍不能把所有的写操作命令都给从服务器。主服务器在下面这三个期间中将收到的写操作命令,写入到 replication buffer 缓冲区里:
(1)主服务器生成 RDB 文件期间
(2)主服务器发送 RDB 文件给从服务器期间
(3)从服务器加载RDB文件期间
第三部,主服务器发送新写操作命令给从服务器。主服务器将 replication buffer 缓冲区里所记录的写操作命令发送给从服务器,从服务器执行来自主服务器 replication buffer 缓冲区里发来的命令,这时主从服务器的数据就一致了。 - 从服务器与主服务器之间会建立tcp连接。以后通过此连接主服务器可以直接发送给从服务器,以此维护主从数据一致。此过程为基于长连接的命令传播
对于以上步骤中的再多些细节因素就可以得到如下注意事项以及如何解决?如下:
- 如果主服务器中数据量很大时候,那么主服务器的进程就会一直忙于通过fork函数创建子进程,也就是阻塞了主服务器的进程,会导致客户端的新请求会迟迟得不到响应
- 传输 RDB 文件会占用主服务器的网络带宽,会对主服务器响应命令请求产生影响。
以上这两个问题,可使从服务器来充当主服务器的一个小弟(经理)这个角色进行解决。通过这种方式,主服务器生成 RDB 和传输 RDB 的压力可以分摊到充当经理角色的从服务器
- 对于我们说过的主从之间已经建立好tcp连接。如果tcp断开了呢,这时从服务器的数据就没办法和主服务器保持一致了,客户端就可能从从服务器读到旧的数据。进而也可以想到,如果在网络断开期间,客户端发新的写操作给主服务器,在某一时刻网络连接恢复了,又怎把这些写操作发给从服务器呢?
对于以上这个问题,采用增量复制方法进行解决,解决思路如下:repl_backlog_buffe 是一个环形缓冲区里面有两个偏移量(也可以联想到算法的双指针),主从服务器都有各自的偏移量,主服务器使用 master_repl_offset 来记录自己写到的位置,从服务器使用 slave_repl_offset 来记录自己读到的位置。见图如下:
此图详解:在主服务器进行命令传播时,不仅会将写命令发送给从服务器,还会将写命令写入到 repl_backlog_buffer 缓冲区里,因此这个缓冲区里一直会保存着最近传播的写命令。当从服务器断开有连接上主服务器时,从服务器会通过 psync 命令将自己的复制偏移量 slave_repl_offset 发送给主服务器,主服务器根据自己的 master_repl_offset 和 slave_repl_offset 之间的差值来执行增量同步或者全量复制操作。
呵,还有细节:因为repl_backlog_buffer 缓冲区是环形的,那么就可能在某一时刻出现主服务器写的数据会把从服务器想读的数据覆盖。那么此时,主服务器就会采用全量同步,这个方式比增量同步的性能损耗要大很多。所以,我们应该调整下 repl_backlog_buffer 缓冲区大小,尽可能的大一些。大小可以通过这个式子进行计算:
以上这两个变量含义为:
- second 为从服务器断线后重新连接上主服务器所需的平均 时间(以秒计算)。
- write_size_per_second 则是主服务器平均每秒产生的写命令数据量大小。
三、 哨兵机制
- 哨兵引出:主服务器挂了,需要继续服务客户端的请求和主从机制,来使redis能够正常合理工作。
- 哨兵简介:哨兵其实是一个运行在特殊模式下的 Redis 进程,所以它也是一个节点。从“哨兵”这个名字也可以看得出来,它相当于是“观察者节点”,观察的对象是主从节点。
以下为哨兵机制的工作原理:
哨兵会每隔 1 秒给所有主从节点发送 PING 命令,当主从节点收到 PING 命令后,会发送一个响应命令给哨兵,这样就可以判断它们是否在正常运行。见图如下:
如果主节点或者从节点没有在规定的时间内响应哨兵的 PING 命令,哨兵就会将它们标记为主观下线。此外主节点还有客观下线,当主节点也被判断为客观下线之后,那么它就是真的挂了。(因为主观下线会因为主节点的系统压力比较大等原因误判)。那么客观下线是怎么判断的呢?是这样的,哨兵在部署的时候不会只部署一个节点,而是用多个节点部署成哨兵集群(最少需要三台机器来部署哨兵集群),通过多个哨兵节点一起判断,就可以就可以避免单个哨兵因为自身网络状况不好,而误判主节点下线的情况。流程如下:
(1) 当一个哨兵判断主节点为主观下线后,就会向其他哨兵发起命令,其他哨兵收到这个命令后,就会根据自身和主节点的网络状况,做出赞成投票或者拒绝投票的响应。
(2)当这个哨兵的赞同票数达到哨兵配置文件中的 quorum 配置项设定的值后,这时主节点就会被该哨兵标记为客观下线。(quorum 的值一般设置为哨兵个数的二分之一加1)
(3)此时就可判断该主节点是否真正下线
此后,哨兵判断完主节点客观下线后,哨兵就要开始在多个从节点中,选出一个从节点来做新主节点。现在就要选出谁来主持这个主从切换呢?
(1)候选者(判断主节点为客观下线的哨兵节点)会向其他哨兵发送命令,表明希望成为主持人来执行主从切换,并让所有其他哨兵对它进行投票。每个哨兵只有一次投票机会,如果用完后就不能参与投票了,可以投给自己或投给别人,但是只有候选者才能把票投给自己。
(2)若此候选者满足 拿到半数以上的赞成票 且 拿到的票数同时还需要大于等于哨兵配置文件中的 quorum 值。
(3)若条件满足,那么候选者就成为了主持人
- 到此,已经知道了 主节点已下线 和 完成主从切换的主持人,那么接下来要做的就是开始进行主从切换:
(1)选出新主节点(层层过滤):首先过滤掉网络不好的从节点,然后由主持人进行三轮考察。第一轮,优先级最高的从节点胜出。Redis 有个叫 slave-priority 配置项,可以给从节点设置优先级。每一台从节点的服务器配置不一定是相同的,我们可以根据服务器性能配置来设置从节点的优先级(性能好的从节点优先级较高)。第二轮,复制进度最靠前的从节点胜出,这个是比较从节点在repl_backlog_buffer缓冲区的复制偏移量大小,看哪个的复制偏移量最接近主节点的写入偏移量,那么哪个从节点的数据就是最新的。第三轮,ID号最小的胜出,每个从节点都有一个编号,这个编号就是 ID 号,是用来唯一标识从节点的。这样就选出了新主节点。
以上流程见图如下:
(2) 将从节点指向新主节点。让已下线主节点属下的所有从节点指向新主节点,这一动作可以通过向从节点发送 SLAVEOF 命令来实现。
(3)通知客户端主节点已更换。这主要通过 Redis 的发布者/订阅者机制来实现的。每个哨兵节点提供发布者/订阅者机制,客户端可以从哨兵订阅消息。客户端和哨兵建立连接后,客户端会订阅哨兵提供的频道。主从切换完成后,哨兵就会向 +switch-master 频道发布新主节点的 IP 地址和端口的消息,这个时候客户端就可以收到这条信息,然后用这里面的新主节点的 IP 地址和端口进行通信了。此外,客户端还可以Redis 的发布者/订阅者机制知道主从切换的进度。
(4)将旧主节点变为从节点。故障转移操作最后要做的是,继续监视旧主节点,当旧主节点重新上线时,哨兵集群就会向它发送 SLAVEOF 命令,让它成为新主节点的从节点。
- 至此,哨兵机制的实现到此结束。
四、通过Redis的发布者/订阅者机制实现哨兵集群
思路如下:在主从集群中,主节点上有一个名为__sentinel__:hello的频道,不同哨兵就是通过它来相互发现,实现互相通信的。
在下图中,哨兵 A 把自己的 IP 地址和端口的信息发布到__sentinel__:hello 频道上,哨兵 B 和 C 订阅了该频道。那么此时,哨兵 B 和 C 就可以从这个频道直接获取哨兵 A 的 IP 地址和端口号。然后,哨兵 B、C 可以和哨兵 A 建立网络连接。