动力节点最新Redis7笔记-第五章Redis主从集群

5 Redis主从集群

为了避免Redis的单点故障问题,我们可以搭建一个Redis集群,将数据备份到集群中的其它节点上。若一个Redis节点宕机,则由集群中的其它节点顶上。

5.1 主从集群搭建

Redis的主从集群是一个“一主多从”的读写分离集群。集群中的Master节点负责处理客户端的读写请求,而Slave节点仅能处理客户端的读请求。只所以要将集群搭建为读写分离模式,主要原因是,对于数据库集群,写操作压力一般都较小,压力大多数来自于读操作请求。所以,只有一个节点负责处理写操作请求即可。

5.1.1 伪集群搭建与配置

在采用单线程IO模型时,为了提高处理器的利用率,一般会在一个主机中安装多台Redis,构建一个Redis主从伪集群。当然,搭建伪集群的另一个场景是,在学习Redis,而学习用主机内存不足以创建多个虚拟机。
下面要搭建的读写分离伪集群包含一个Master与两个Slave。它们的端口号分别是:6380、6381、6382。

5.1.1.1 复制redis.conf

在redis安装目录中mkdir一个目录,名称随意。这里命名为cluster。然后将redis.conf文件复制到cluster目录中。该文件后面会被其它配置文件包含,所以该文件中需要设置每个Redis节点相同的公共的属性。

5.1.1.2 修改redis.conf

在redis.conf中做如下几项修改:

5.1.1.2.1 masterauth


因为我们要搭建主从集群,且每个主机都有可能会是Master,所以最好不要设置密码验证属性requirepass。如果真需要设置,一定要每个主机的密码都设置为相同的。此时每个配置文件中都要设置两个完全相同的属性:requirepass与masterauth。其中requirepass用于指定当前主机的访问密码,而masterauth用于指定当前slave访问master时向master提交的访问密码,用于让master验证自己身份是否合法。

5.1.1.2.2 repl-disable-tcp-nodelay


该属性用于设置是否禁用TCP特性tcp-nodelay。设置为yes则禁用tcp-nodelay,此时master与slave间的通信会产生延迟,但使用的TCP包数量会较少,占用的网络带宽会较小。相反,如果设置为no,则网络延迟会变小,但使用的TCP包数量会较多,相应占用的网络带宽会大。

| tcp-nodelay:为了充分复用网络带宽,TCP总是希望发送尽可能大的数据块。为了达到该目的,TCP中使用了一个名为Nagle的算法。
Nagle算法的工作原理是,网络在接收到要发送的数据后,并不直接发送,而是等待着数据量足够大(由TCP网络特性决定)时再一次性发送出去。这样,网络上传输的有效数据比例就得到了大大提升,无效数据传递量极大减少,于是就节省了网络带宽,缓解了网络压力。

tcp-nodelay则是TCP协议中Nagle算法的开头。

5.1.1.3 新建redis6380.conf

新建一个redis配置文件redis6380.conf,该配置文件中的Redis端口号为6380。

5.1.1.4 再复制出两个conf文件

再使用redis6380.conf复制出两个conf文件:redis6381.conf与redis6382.conf。然后修改其中的内容。

修改redis6381.conf的内容如下:

修改redis6382.conf的内容如下:

5.1.1.5 启动三台Redis

分别使用redis6380.conf、redis6381.conf与redis6382.conf三个配置文件启动三台Redis。

5.1.1.6 设置主从关系

再打开三个会话框,分别使用客户端连接三台Redis。然后通过slaveof命令,指定6380的Redis为Master。

5.1.1.7 查看状态信息

通过info replication命令可查看当前连接的Redis的状态信息。

5.1.2 分级管理

若Redis主从集群中的Slave较多时,它们的数据同步过程会对Master形成较大的性能压力。此时可以对这些Slave进行分级管理。

设置方式很简单,只需要让低级别Slave指定其slaveof的主机为其上一级Slave即可。不过,上一级Slave的状态仍为Slave,只不过,其是更上一级的Slave。
例如,指定6382主机为6381主机的Slave,而6381主机仍为真正的Master的Slave。

此时会发现,Master的Slave只有6381一个主机。

5.1.3 容灾冷处理

在Master/Slave的Redis集群中,若Master出现宕机怎么办呢?有两种处理方式,一种是通过手工角色调整,使Slave晋升为Master的冷处理;一种是使用哨兵模式,实现Redis集群的高可用HA,即热处理。
无论Master是否宕机,Slave都可通过slaveof no one将自己由Slave晋升为Master。如果其原本就有下一级的Slave,那么,其就直接变为了这些Slave的真正的Master了。而原来的Master也会失去这个原来的Slave。

5.2 主从复制原理

5.2.1 主从复制过程

当一个Redis节点(slave节点)接收到类似slaveof 127.0.0.1 6380的指令后直至其可以从master持续复制数据,大体经历了如下几个过程:

5.2.1.1 保存master地址

当slave接收到slaveof指令后,slave会立即将新的master的地址保存下来。

5.2.1.2 建立连接

slave中维护着一个定时任务,该定时任务会尝试着与该master建立socket连接。如果连接无法建立,则其会不断定时重试,直到连接成功或接收到slaveof no one指令。

5.2.1.3 slave发送ping命令

连接建立成功后,slave会发送ping命令进行首次通信。如果slave没有收到master的回复,则slave会主动断开连接,下次的定时任务会重新尝试连接。

5.2.1.4 对slave身份验证

如果master收到了slave的ping命令,并不会立即对其进行回复,而是会先进行身份验证。如果验证失败,则会发送消息拒绝连接;如果验证成功,则向slave发送连接成功响应。

5.2.1.5 master持久化

首次通信成功后,slave会向master发送数据同步请求。当master接收到请求后,会fork出一个子进程,让子进程以异步方式立即进行持久化。

5.2.1.6 数据发送

持久化完毕后master会再fork出一个子进程,让该子进程以异步方式将数据发送给slave。slave会将接收到的数据不断写入到本地的持久化文件中。
在slave数据同步过程中,master的主进程仍在不断地接受着客户端的写操作,且不仅将新的数据写入到了master内存,同时也写入到了同步缓存。当master的持久化文件中的数据发送完毕后,master会再将同步缓存中新的数据发送给slave,由slave将其写入到本地持久化文件中。数据同步完成。

5.2.1.7 slave恢复内存数据

当slave与master的数据同步完成后,slave就会读取本地的持久化文件,将其恢复到本地内存,然后就可以对外提供读服务了。

5.2.1.8 持续增量复制

在slave对外提供服务过程中,master会持续不断的将新的数据以增量方式发送给slave,以保证主从数据的一致性。

5.2.2 数据同步演变过程

5.2.2.1 sync同步

Redis 2.8版本之前,首次通信成功后,slave会向master发送sync数据同步请求。然后master就会将其所有数据全部发送给slave,由slave保存到其本地的持久化文件中。这个过程称为全量复制。
但这里存在一个问题:在全量复制过程中可能会出现由于网络抖动而导致复制过程中断。当网络恢复后,slave与master重新连接成功,此时slave会重新发送sync请求,然后会从头开始全量复制。
由于全量复制过程非常耗时,所以期间出现网络抖动的概率很高。而中断后的从头开始不仅需要消耗大量的系统资源、网络带宽,而且可能会出现长时间无法完成全量复制的情况。

5.2.2.2 psync同步

Redis 2.8版本之后,全量复制采用了psync(Partial Sync,不完全同步)同步策略。当全量复制过程出现由于网络抖动而导致复制过程中断时,当重新连接成功后,复制过程可以“断点续传”。即从断开位置开始继续复制,而不用从头再来。这就大大提升了性能。
为了实现psync,整个系统做了三个大的变化:

5.2.2.2.1 复制偏移量

系统为每个要传送数据进行了编号,该编号从0开始,每个字节一个编号。该编号称为复制偏移量。参与复制的主从节点都会维护该复制偏移量。

master每发送过一个字节数据后就会进行累计。统计信息通过info replication的master_repl_offset可查看到。同时,slave会定时向master上报其自身已完成的复制偏移量给master,所以master也会保存slave的复制偏移量offset。

slave在接收到master的数据后,也会累计接收到的偏移量。统计信息通过info replication的slave_repl_offset可查看到。

5.2.2.2.2 主节点复制ID

当master启动后就会动态生成一个长度为40位的16进制字符串作为当前master的复制ID,该ID是在进行数据同步时slave识别master使用的。通过info replication的master_replid属性可查看到该ID。

5.2.2.2.3 复制积压缓冲区

当master有连接的slave时,在master中就会创建并维护一个队列backlog,默认大小为1MB,该队列称为复制积压缓冲区。master接收到了写操作数据不仅会写入到master主存,写入到master中为每个slave配置的发送缓存,而且还会写入到复制积压缓冲区。其作用就是用于保存最近操作的数据,以备“断点续传”时做数据补偿,防止数据丢失。

5.2.2.2.4 psync同步过程


psync是一个由slave提交的命令,其格式为psync <master_replid> <repl_offset>,表示当前slave要从指定的master中的repl_offset+1处开始复制。repl_offset表示当前slave已经完成复制的数据的offset。该命令保证了“断点续传”的实现。
在第一次开始复制时,slave并不知道master的动态ID,并且一定是从头开始复制,所以其提交的psync命令为 PSYNC ? -1。即master_replid为问号(?),repl_offset为-1。
如果复制过程中断后slave与master成功连接,则slave再次提交psyn命令。此时的psyn命令的repl_offset参数为其前面已经完成复制的数据的偏移量。

其实,并不是slave提交了psyn命令后就可以立即从master处开始复制,而是需要master给出响应结果后,根据响应结果来执行。master根据slave提交的请求及master自身情况会给出不同的响应结果。响应结果有三种可能:
  • FULLRESYNC <master_replid> <repl_offset>:告知slave当前master的动态ID及可以开始全量复制了,这里的repl_offset一般为0
  • CONTINUE:告知slave可以按照你提交的repl_offset后面位置开始“续传”了
  • ERR:告知slave,当前master的版本低于Redis 2.8,不支持psyn,你可以开始全量复制了
5.2.2.2.5 psync存在的问题
  • 在psync数据同步过程中,若slave重启,在slave内存中保存的master的动态ID与续传offset都会消失,“断点续传”将无法进行,从而只能进行全量复制,导致资源浪费。
  • 在psync数据同步过程中,master宕机后slave会发生“易主”,从而导致slave需要从新master进行全量复制,形成资源浪费。

5.2.2.3 psync同步的改进

Redis 4.0对psync进行了改进,提出了“同源增量同步”策略。

5.2.2.3.1 解决slave重启问题
针对“slave重启时master动态ID丢失问题”,改进后的psync将master的动态ID直接写入到了slave的持久化文件中。

slave重启后直接从本地持久化文件中读取master的动态ID,然后向master提交获取复制偏移量的请求。master会根据提交请求的slave地址,查找到保存在master中的复制偏移量,然后向slave回复FULLRESYNC <master_replid> <repl_offset>,以告知slave其马上要开始发送的位置。然后master开始“断点续传”。

5.2.2.3.2 解决slave易主问题
slave易主后需要和新master进行全量复制,本质原因是新master不认识slave提交的psync请求中“原master的动态ID”。如果slave发送 PSYNC <原master_replid> <repl_offset>命令,新master能够识别出该slave要从原master复制数据,而自己的数据也都是从该master复制来的。那么新master就会明白,其与该slave“师出同门”,应该接收其“断点续传”同步请求。

而新master中恰好保存的有“原master的动态ID”。由于改进后的psync中每个slave都在本地保存了当前master的动态ID,所以当slave晋升为新的master后,其本地仍保存有之前master的动态ID。而这一点也恰恰为解决“slave易主”问题提供了条件。通过master的info replicaton中的master_replid2可查看到。如果尚未发生过易主,则该值为40个0。

5.2.2.4 无盘操作

Redis 6.0对同步过程又进行了改进,提出了“无盘全量同步”与“无盘加载”策略,避免了耗时的IO操作。
  • 无盘全量同步:master的主进程fork出的子进程直接将内存中的数据发送给slave,无需经过磁盘。
  • 无盘加载:slave在接收到master发送来的数据后不需要将其写入到磁盘文件,而是直接写入到内存,这样slave就可快速完成数据恢复。

5.2.2.5 共享复制积压缓冲区

Redis 7.0版本对复制积压缓冲区进行了改进,让各个slave的发送缓冲区共享复制积压缓冲区。这使得复制积压缓冲区的作用,除了可以保障数据的安全性外,还作为所有slave的发送缓冲区,充分利用了复制积压缓冲区。

5.3 哨兵机制实现

5.3.1 简介

对于Master宕机后的冷处理方式是无法实现高可用的。Redis从2.6版本开始提供了高可用的解决方案—— Sentinel哨兵机制。在集群中再引入一个节点,该节点充当Sentinel哨兵,用于监视Master的运行状态,并在Master宕机后自动指定一个Slave作为新的Master。整个过程无需人工参与,完全由哨兵自动完成。
不过,此时的Sentinel哨兵又成为了一个单点故障点:若哨兵发生宕机,整个集群将瘫痪。所以为了解决Sentinel的单点问题,又要为Sentinel创建一个集群,即Sentinel哨兵集群。一个哨兵的宕机,将不会影响到Redis集群的运行。
那么这些Sentinel哨兵是如何工作的呢?Sentinel是如何知道其监视的Master状态的呢?每个Sentinel都会定时会向Master发送心跳,如果Master在有效时间内向它们都进行了响应,则说明Master是“活着的”。如果Sentinel中有quorum个哨兵没有收到响应,那么就认为Master已经宕机,然后会有一个Sentinel做Failover故障转移。即将原来的某一个Slave晋升为Master。

5.3.2 Redis高可用集群搭建

在“不差钱”的情况下,可以让Sentinel占用独立的主机,即在Redis主机上只启动Redis进程,在Sentinel主机上只启动Sentinel进程。下面要搭建一个“一主二从三哨兵”的高可用伪集群,即这些角色全部安装运行在一台主机上。“一主二从”使用前面的主从集群,下面仅搭建一个Sentinel伪集群。

角色 端口号 角色 端口号
master 6380 sentinel 26380
slave 6381 sentinel 26381
slave 6382 sentinel 26382

5.3.2.1 复制sentinel.conf

将Redis安装目录中的sentinel.conf文件复制到cluster目录中。该配置文件中用于存放一些sentinel集群中的一些公共配置。

5.3.2.2 修改sentinel.conf

修改cluster/sentinel.conf配置文件。

5.3.2.2.1 sentinel monitor


该配置用于指定Sentinel要监控的master是谁,并为master起了一个名字。该名字在后面很多配置中都会使用。同时指定Sentinel集群中决定该master“客观下线状态”判断的法定sentinel数量。的另一个用途与sentinel的Leader选举有关。要求中至少要有max(quorum, sentinelNum/2+1)个sentinel参与,选举才能进行。
这里将该配置注释掉,因为要在后面的其它配置文件中设置,如果不注释就会出现配置冲突。

5.3.2.2.2 sentinel auth-pass


如果Redis主从集群中的主机设置了访问密码,那么该属性就需要指定master的主机名与访问密码。以方便sentinel监控master。

5.3.2.3 新建sentinel26380.conf

在Redis安装目录下的cluster目录中新建sentinel26380.conf文件作为Sentinel的配置文件,并在其中键入如下内容:

sentinel monitor属性用于指定当前监控的master的IP与Port,同时为集群中master指定一个名称mymaster,以方便其它属性使用。
最后的2是参数quorum的值,quorum有两个用途。一个是只有当quorum个sentinel都认为当前master宕机了才能开启故障转移。另一个用途与sentinel的Leader选举有关。要求中至少要有max(quorum, sentinelNum/2+1)个sentinel参与,选举才能进行。

5.3.2.4 再复制两个conf文件

再使用sentinel26380.conf复制出两个conf文件:sentinel26381.conf与sentinel26382.conf。然后修改其中的内容。
修改sentinel26381.conf。

修改sentinel26382.conf。

5.3.3 Redis高可用集群的启动

5.3.3.1 启动并关联Redis集群

首先要启动三台Redis,然后再通过slaveof关联它们。

5.3.3.2 启动Sentinel集群

5.3.3.2.1 启动命令

在/usr/local/bin目录下有一个命令redis-sentinel用于启动Sentinel。不过,我们发现一个奇怪的现象:/usr/local/bin目录中的redis-sentinel命令是redis-server命令的软链接,这是为什么呢?

查看Redis安装目录中的src目录中的redis-server与redis-sentinel命令,我们发现这两个命令的大小一模一样。其实,这两个命令本质上是同一个命令。

只所以可以启动不同的进程,主要是因为在启动时所加载的配置文件的不同。所以在启动Sentinel时,需要指定sentinel.conf配置文件。

5.3.3.2.2 两种启动方式

由于redis-server与redis-sentinel命令本质上是同一个命令,所以使用这两个命令均可启动Sentinel。

  • 方式一,使用redis-sentinel命令:redis-sentinel sentinel26380.conf
  • 方式二,使用redis-server命令:redis-server sentinel26380.conf --sentinel
5.3.3.2.3 启动三台Sentinel



5.3.3.3 查看Sentinel信息

运行中的Sentinel就是一个特殊Redis,其也可以通过客户端连接,然后通过info sentinel来查看当前连接的Sentinel的信息。

5.3.3.4 查看sentinel配置文件

打开任意sentinel的配置文件,发现其配置内容中新增加了很多配置。

5.3.4 Sentinel优化配置

在公共的sentinel.conf文件中,还可以通过修改一些其它属性的值来达到对Sentinel的配置优化。

5.3.4.1 sentinel down-after-milliseconds


每个Sentinel会通过定期发送ping命令来判断master、slave及其它Sentinel是否存活。如果Sentinel在该属性指定的时间内没有收到它们的响应,那么该Sentinel就会主观认为该主机宕机。默认为30秒。

5.3.4.2 sentinel parallel-syncs


该属性用于指定,在故障转移期间,即老的master出现问题,新的master刚晋升后,允许多少个slave同时从新master进行数据同步。默认值为1表示所有slave逐个从新master进行数据同步。

5.3.4.3 sentinel failover-timeout


指定故障转移的超时时间,默认时间为3分钟。该超时时间的用途很多:

  • 由于第一次故障转移失败,在同一个master上进行第二次故障转移尝试的时间为该failover-timeout的两倍
  • 新master晋升完毕,slave从老master强制转到新master进行数据同步的时间阈值。
  • 取消正在进行的故障转换所需的时间阈值。
  • 新master晋升完毕,所有replicas的配置文件更新为新master的时间阈值。

5.3.4.4 sentinel deny-scripts-reconfig


指定是否可以通过命令sentinel set动态修改notification-script与client-reconfig-script两个脚本。默认是不能的。这两个脚本如果允许动态修改,可能会引发安全问题。

5.3.4.5 动态修改配置


通过redis-cli连接上Sentinel后,通过sentinel set命令可动态修改配置信息。例如,下面的命令动态修改了sentinel monitor中的quorum的值。
下表是sentinel set命令支持的参数:

参数 示例
quorum sentinel set mymaster quorum 2
down-after-milliseconds sentinel set mymaster down-after-milliseconds 50000
failover-timeout sentinel set mymaster failover-timeout 300000
parallel-syncs sentinel set mymaster parallel-syncs 3
notification-script sentinel set mymaster notification-script /var/redis/notify.sh
client-reconfig-script sentinel set mymaster client-reconfig-script /var/redis/reconfig.sh
auth-pass sentinel set mymaster auth-pass 111

5.4 哨兵机制原理

5.4.1 三个定时任务

Sentinel维护着三个定时任务以监测Redis节点及其它Sentinel节点的状态。

5.4.1.1 info任务

每个Sentinel节点每10秒就会向Redis集群中的每个节点发送info命令,以获得最新的Redis拓扑结构。

5.4.1.2 心跳任务

每个Sentinel节点每1秒就会向所有Redis节点及其它Sentinel节点发送一条ping命令,以检测这些节点的存活状态。该任务是判断节点在线状态的重要依据。

5.4.1.3 发布/订阅任务

每个Sentinel节点在启动时都会向所有Redis节点订阅_ sentinel :hello主题的信息,当Redis节点中该主题的信息发生了变化,就会立即通知到所有订阅者。
启动后,每个Sentinel节点每2秒就会向每个Redis节点发布一条
sentinel :hello主题的信息,该信息是当前Sentinel对每个Redis节点在线状态的判断结果及当前Sentinel节点信息。
当Sentinel节点接收到
sentinel _:hello主题信息后,就会读取并解析这些信息,然后主要完成以下三项工作:

  • 如果发现有新的Sentinel节点加入,则记录下新加入Sentinel节点信息,并与其建立连接。
  • 如果发现有Sentinel Leader选举的选票信息,则执行Leader选举过程。
  • 汇总其它Sentinel节点对当前Redis节点在线状态的判断结果,作为Redis节点客观下线的判断依据。

5.4.2 Redis节点下线判断

对于每个Redis节点在线状态的监控是由Sentinel完成的。

5.4.2.1 主观下线

每个Sentinel节点每秒就会向每个Redis节点发送ping心跳检测,如果Sentinel在down-after-milliseconds时间内没有收到某Redis节点的回复,则Sentinel节点就会对该Redis节点做出“下线状态”的判断。这个判断仅仅是当前Sentinel节点的“一家之言”,所以称为主观下线。

5.4.2.2 客观下线

当Sentinel主观下线的节点是master时,该Sentinel节点会向每个其它Sentinel节点发送sentinel is-master-down-by-addr命令,以询问其对master在线状态的判断结果。这些Sentinel节点在收到命令后会向这个发问Sentinel节点响应0(在线)或1(下线)。当Sentinel收到超过quorum个下线判断后,就会对master做出客观下线判断。

5.4.3 Sentinel Leader选举

当Sentinel节点对master做出客观下线判断后会由Sentinel Leader来完成后续的故障转移,即Sentinel集群中的节点也并非是对等节点,是存在Leader与Follower的。
Sentinel集群的Leader选举是通过Raft算法实现的。Raft算法比较复杂,后面会详细学习。这里仅简单介绍一下大致思路。
每个选举参与者都具有当选Leader的资格,当其完成了“客观下线”判断后,就会立即“毛遂自荐”推选自己做Leader,然后将自己的提案发送给所有参与者。其它参与者在收到提案后,只要自己手中的选票没有投出去,其就会立即通过该提案并将同意结果反馈给提案者,后续再过来的提案会由于该参与者没有了选票而被拒绝。当提案者收到了同意反馈数量大于等于max(quorum,sentinelNum/2+1)时,该提案者当选Leader。
说明:

  • 在网络没有问题的前提下,基本就是谁先做出了“客观下线”判断,谁就会首先发起Sentinel Leader的选举,谁就会得到大多数参与者的支持,谁就会当选Leader。
  • Sentinel Leader选举会在次故障转移发生之前进行。
  • 故障转移结束后Sentinel不再维护这种Leader-Follower关系,即Leader不再存在。

5.4.4 master选择算法

在进行故障转移时,Sentinel Leader需要从所有Redis的Slave节点中选择出新的Master。其选择算法为:

  1. 过滤掉所有主观下线的,或心跳没有响应Sentinel的,或replica-priority值为0的Redis节点
  2. 在剩余Redis节点中选择出replica-priority最小的的节点列表。如果只有一个节点,则直接返回,否则,继续
  3. 从优先级相同的节点列表中选择复制偏移量最大的节点。如果只有一个节点,则直接返回,否则,继续
  4. 从复制偏移值量相同的节点列表中选择动态ID最小的节点返回

5.4.5 故障转移过程

Sentinel Leader负责整个故障转移过程,经历了如上步骤:

  1. Sentinel Leader根据master选择算法选择出一个slave节点作为新的master
  2. Sentinel Leader向新master节点发送slaveof no one指令,使其晋升为master
  3. Sentinel Leader向新master发送info replication指令,获取到master的动态ID
  4. Sentinel Leader向其余Redis节点发送消息,以告知它们新master的动态ID
  5. Sentinel Leader向其余Redis节点发送slaveof 指令,使它们成为新master的slave
  6. Sentinel Leader从所有slave节点中每次选择出parallel-syncs个slave从新master同步数据,直至所有slave全部同步完毕
  7. 故障转移完毕

5.4.6 节点上线

不同的节点类型,其上线的方式也是不同的。

5.4.6.1 原Redis节点上线

无论是原下线的master节点还是原下线的slave节点,只要是原Redis集群中的节点上线,只需启动Redis即可。因为每个Sentinel中都保存有原来其监控的所有Redis节点列表,Sentinel会定时查看这些Redis节点是否恢复。如果查看到其已经恢复,则会命其从当前master进行数据同步。
不过,如果是原master上线,在新master晋升后Sentinel Leader会立即先将原master节点更新为slave,然后才会定时查看其是否恢复。

5.4.6.2 新Redis节点上线

如果需要在Redis集群中添加一个新的节点,其未曾出现在Redis集群中,则上线操作只能手工完成。即添加者在添加之前必须知道当前master是谁,然后在新节点启动后运行slaveof命令加入集群。

5.4.6.3 Sentinel节点上线

如果要添加的是Sentinel节点,无论其是否曾经出现在Sentinel集群中,都需要手工完成。即添加者在添加之前必须知道当前master是谁,然后在配置文件中修改sentinel monitor属性,指定要监控的master。然后启动Sentinel即可。

5.5 CAP定理

5.5.1 概念

CAP定理指的是在一个分布式系统中,一致性Consistency、可用性Availability、分区容错性Partition tolerance,三者不可兼得。

  • 一致性(C):分布式系统中多个主机之间是否能够保持数据一致的特性。即,当系统数据发生更新操作后,各个主机中的数据仍然处于一致的状态。
  • 可用性(A):系统提供的服务必须一直处于可用的状态,即对于用户的每一个请求,系统总是可以在有限的时间内对用户做出响应。
  • 分区容错性(P):分布式系统在遇到任何网络分区故障时,仍能够保证对外提供满足一致性和可用性的服务。

5.5.2 定理

CAP定理的内容是:对于分布式系统,网络环境相对是不可控的,出现网络分区是不可避免的,因此系统必须具备分区容错性。但系统不能同时保证一致性与可用性。即要么CP,要么AP。

5.5.3 BASE理论

BASE是Basically Available(基本可用)、Soft state(软状态)和Eventually consistent(最终一致性)三个短语的简写,BASE是对CAP中一致性和可用性权衡的结果,其来源于对大规模互联网系统分布式实践的结论,是基于CAP定理逐步演化而来的。
BASE理论的核心思想是:即使无法做到强一致性,但每个系统都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性。

5.5.3.1 基本可用

基本可用是指分布式系统在出现不可预知故障的时候,允许损失部分可用性。

5.5.3.2 软状态

软状态,是指允许系统数据存在的中间状态,并认为该中间状态的存在不会影响系统的整体可用性,即允许系统主机间进行数据同步的过程存在一定延时。软状态,其实就是一种灰度状态,过渡状态。

5.5.3.3 最终一致性

最终一致性强调的是系统中所有的数据副本,在经过一段时间的同步后,最终能够达到一个一致的状态。因此,最终一致性的本质是需要系统保证最终数据能够达到一致,而不需要保证系统数据的实时一致性。

5.5.4 CAP的应用

下面将生产中常见到的一些中间件与服务器集群的CAP特性进行分析。

5.5.4.1 Zookeeper与CAP

Zookeeper遵循的是CP模式,即保证了一致性,但牺牲了可用性。
当Leader节点中的数据发生了变化后,在Follower还没有同步完成之前,整个Zookeeper集群是不对外提供服务的。如果此时有客户端来访问数据,则客户端会因访问超时而发生重试。不过,由于Leader的选举非常快,所以这种重试对于用户来说几乎是感知不到的。所以说,Zookeeper保证了一致性,但牺牲了可用性。

5.5.4.2 Consul与CAP

Consul遵循的是CP模式,即保证了一致性,但牺牲了可用性。

5.5.4.3 Redis与CAP

Redis遵循的是AP模式,即保证了可用性,但牺牲了一致性。

5.5.4.4 Eureka与CAP

Eureka遵循的是AP模式,即保证了可用性,但牺牲了一致性。

5.5.4.5 Nacos与CAP

Nacos在做注册中心时,默认是AP的。但其也支持CP模式,但需要用户提交请求进行转换。

5.6 Raft算法

5.6.1 基础

Raft算法是一种通过对日志复制管理来达到集群节点一致性的算法。这个日志复制管理发生在集群节点中的Leader与Followers之间。Raft通过选举出的Leader节点负责管理日志复制过程,以实现各个节点间数据的一致性。

5.6.2 角色、任期及角色转变


在Raft中,节点有三种角色:

  • Leader:唯一负责处理客户端写请求的节点;也可以处理客户端读请求;同时负责日志复制工作
  • Candidate:Leader选举的候选人,其可能会成为Leader。是一个选举中的过程角色
  • Follower:可以处理客户端读请求;负责同步来自于Leader的日志;当接收到其它Cadidate的投票请求后可以进行投票;当发现Leader挂了,其会转变为Candidate发起Leader选举

5.6.3 leader选举

通过Raft算法首先要实现集群中Leader的选举。

5.6.3.1 我要选举

若follower在心跳超时范围内没有接收到来自于leader的心跳,则认为leader挂了。此时其首先会使其本地term增一。然后follower会完成以下步骤:

  • 此时若接收到了其它candidate的投票请求,则会将选票投给这个candidate
  • 由follower转变为candidate
  • 若之前尚未投票,则向自己投一票
  • 向其它节点发出投票请求,然后等待响应

5.6.3.2 我要投票

follower在接收到投票请求后,其会根据以下情况来判断是否投票:

  • 发来投票请求的candidate的term不能小于我的term
  • 在我当前term内,我的选票还没有投出去
  • 若接收到多个candidate的请求,我将采取first-come-first-served方式投票

5.6.3.3 等待响应

当一个Candidate发出投票请求后会等待其它节点的响应结果。这个响应结果可能有三种情况:

  • 收到过半选票,成为新的leader。然后会将消息广播给所有其它节点,以告诉大家我是新的Leader了
  • 接收到别的candidate发来的新leader通知,比较了新leader的term并不比自己的term小,则自己转变为follower
  • 经过一段时间后,没有收到过半选票,也没有收到新leader通知,则重新发出选举

5.6.3.4 选举时机

在很多时候,当Leader真的挂了,Follower几乎同时会感知到,所以它们几乎同时会变为candidate发起新的选举。此时就可能会出现较多candidate票数相同的情况,即无法选举出Leader。
为了防止这种情况的发生,Raft算法其采用了randomized election timeouts策略来解决这个问题。其会为这些Follower随机分配一个选举发起时间election timeout,这个timeout在150-300ms范围内。只有到达了election timeout时间的Follower才能转变为candidate,否则等待。那么election timeout较小的Follower则会转变为candidate然后先发起选举,一般情况下其会优先获取到过半选票成为新的leader。

5.6.4 数据同步

在Leader选举出来的情况下,通过日志复制管理实现集群中各节点数据的同步。

5.6.4.1 状态机

Raft算法一致性的实现,是基于日志复制状态机的。状态机的最大特征是,不同Server中的状态机若当前状态相同,然后接受了相同的输入,则一定会得到相同的输出。

5.6.4.2 处理流程


当leader接收到client的写操作请求后,大体会经历以下流程:

  • leader在接收到client的写操作请求后,leader会将数据与term封装为一个box,并随着下一次心跳发送给所有followers,以征求大家对该box的意见。同时在本地将数据封装为日志
  • follower在接收到来自leader的box后首先会比较该box的term与本地记录的曾接受过的box的最大term,只要不比自己的小就接受该box,并向leader回复同意。同时会将该box中的数据封装为日志。
  • 当leader接收到过半同意响应后,会将日志commit到自己的状态机,状态机会输出一个结果,同时日志状态变为了committed
  • 同时leader还会通知所有follower将日志commit到它们本地的状态机,日志状态变为了committed
  • 在commit通知发出的同时,leader也会向client发出成功处理的响应

5.6.4.3 AP支持


Log由term index、log index及command构成。为了保证可用性,各个节点中的日志可以不完全相同,但leader会不断给follower发送box,以使各个节点的log最终达到相同。即raft算法不是强一致性的,而是最终一致的。

5.6.5 脑裂

Raft集群存在脑裂问题。在多机房部署中,由于网络连接问题,很容易形成多个分区。而多分区的形成,很容易产生脑裂,从而导致数据不一致。
由于三机房部署的容灾能力最强,所以生产环境下,三机房部署是最为常见的。下面以三机房部署为例进行分析,根据机房断网情况,可以分为五种情况:

5.6.5.1 情况一–不确定


这种情况下,B机房中的主机是感知不到Leader的存在的,所以B机房中的主机会发起新一轮的Leader选举。由于B机房与C机房是相连的,虽然C机房中的Follower能够感知到A机房中的Leader,但由于其接收到了更大term的投票请求,所以C机房的Follower也就放弃了A机房中的Leader,参与了新Leader的选举。
若新Leader出现在B机房,A机房是感知不到新Leader的诞生的,其不会自动下课,所以会形成脑裂。但由于A机房Leader处理的写操作请求无法获取到过半响应,所以无法完成写操作。但B机房Leader的写操作处理是可以获取到过半响应的,所以可以完成写操作。故,A机房与B、C机房中出现脑裂,且形成了数据的不一致。
若新Leader出现在C机房,A机房中的Leader则会自动下课,所以不会形成脑裂。

5.6.5.2 情况二–形成脑裂


这种情况与情况一基本是一样的。不同的是,一定会形成脑裂,无论新Leader在B还是C机房。

5.6.5.3 情况三–无脑裂


A、C可以正常对外提供服务,但B无法选举出新的Leader。由于B中的主机全部变为了选举状态,所以无法提供任何服务,没有形成脑裂。

5.6.5.4 情况四–无脑裂


A、B、C均可以对外提供服务,不受影响。

5.6.5.5 情况五–无脑裂


A机房无法处理写操作请求,但可以对外提供读服务。
B、C机房由于失去了Leader,均会发起选举,但由于均无法获取过半支持,所以均无法选举出新的Leader。

5.6.6 Leader宕机处理

5.6.6.1 请求到达前Leader挂了

client发送写操作请求到达Leader之前Leader就挂了,因为请求还没有到达集群,所以这个请求对于集群来说就没有存在过,对集群数据的一致性没有任何影响。Leader 挂了之后,会选举产生新的Leader。
由于Stale Leader并未向client发送成功处理响应,所以client会重新发送该写操作请求。

5.6.6.2 未开始同步数据前Leader挂了

client发送写操作请求给Leader,请求到达Leader后,Leader还没有开始向Followers发出数据Leader就挂了。这时集群会选举产生新的Leader。Stale Leader重启后会作为Follower重新加入集群,并同步新Leader中的数据以保证数据一致性。之前接收到client的数据被丢弃。
由于Stale Leader并未向client发送成功处理响应,所以client会重新发送该写操作请求。

5.6.6.3 同步完部分后Leader挂了

client发送写操作请求给Leader,Leader接收完数据后向所有Follower发送数据。在部分Follower接收到数据后Leader挂了。由于Leader挂了,就会发起新的Leader选举。

  • 若Leader产生于已完成数据接收的Follower,其会继续将前面接收到的写操作请求转换为日志,并写入到本地状态机,并向所有Flollower发出询问。在获取过半同意响应后会向所有Followers发送commit指令,同时向client进行响应。
  • 若Leader产生于尚未完成数据接收的Follower,那么原来已完成接收的Follower则会放弃曾接收到的数据。由于client没有接收到响应,所以client会重新发送该写操作请求。

5.6.6.4 commit通知发出后Leader挂了

client发送写操作请求给Leader,Leader也成功向所有Followers发出的commit指令,并向client发出响应后,Leader挂了。
由于Stale Leader已经向client发送成功接收响应,且commit通知已经发出,说明这个写操作请求已经被server成功处理。

5.6.7 Raft算法动画演示

在网络上有一个关于Raft算法的动画,其非常清晰全面地演示了Raft算法的工作原理。

猜你喜欢

转载自blog.csdn.net/Javanewspaper/article/details/130949646