5 Redis 集群
2.8版本之前, Redis采用主从集群模式. 实现了数据备份和读写分离
2.8版本之后, Redis采用Sentinel哨兵集群模式 , 实现了集群的高可用
5.1 主从集群搭建
首先, 基本所有系统 , “读” 的压力都大于 “写” 的压力
Redis 的主从集群是一个“一主多从”的读写分离集群(运用哨兵机制后会升级为3主多从)。
集群中的 Master 节点负责处理客户端的读写请求,而 Slave 节点仅能处理客户端的读请求。
一个主节点可以有多个从节点(或没有从节点),但一个从节点只能有一个主节点。
5.1.0 集群节点数总是建议为奇数
首先,集群必须有过半的机器是正常工作的,整个集群才可对外服务。
那么我们列举一些情况,来看看在这些情况下集群的容错性。
如果有2个节点,那么只要挂掉1个节点,集群就不可用了。此时,集群对的容忍度为0;
如果有3个节点,那么挂掉1个节点,还有剩下2个正常节点,超过半数,可以重新选举,正常服务。此时,集群的容忍度为1;
如果有4个节点,那么挂掉1个节点,剩下3个,超过半数,可以重新选举。但如果再挂掉1个,只剩下2个,就无法正常选举和服务了。此时,集群的容忍度为1;
依次类推,5个节点,容忍度为2;6个节点容忍度同样为2;
也就是2n和2n-1的容忍度是一样的,都是n-1。那么,为了节省资源,集群当然选择奇数节点。
5.1.1 伪集群搭建与配置
在采用单线程 IO 模型时,为了提高处理器的利用率,一般会在一个主机中安装多台 Redis,构建一个 Redis 主从伪集群。当然,搭建伪集群的另一个场景是,在学习 Redis,而学习用主机内存不足以创建多个虚拟机。
下面要搭建的读写分离伪集群包含一个 Master 与两个 Slave。它们的端口号分别是:6380、 6381、6382。
5.1.1.1 在集群文件夹中创建公共redis.conf文件
-
在 redis 安装目录中 mkdir 一个目录,名称随意。这里命名为 cluster。
-
然后将 redis.conf文件复制到 cluster 目录中。称为公共conf
尽管一主两从主机会有自己的conf文件, 且配置各不相同. 但他们三者之间配置相同的地方, 就放到这个公共conf文件中.
稍后各台结点使用include命令将公共conf文件包含进自己特有的conf文件中
5.1.1.2 修改 redis.conf文件
-
masterauth
-
repl-disable-tcp-nodelay
5.1.1.3 新建每个节点自己的conf文件并启动
- 在cluster下新建三个 redis 配置文件, 分别为 redis6380.conf, redis6381.conf , redis6382.conf. 其内容如下
#将公共conf文件包含进来
include redis.conf
#分配不同的pid与端口
pidfile /var/run/redis_6380.pid
port 6380
#指定RDB和AOF持久化文件的文件名
dbfilename dump6380.rdb
appendfilename appendonly6380.aof
#若master挂了 , 这个salve被选举为master的优先级
#默认是100 , 越小越高.
#例外是0. 若设为0, 指定该主机永远不能当master
replica-priority 90# logfile access6380.log
include redis.conf
pidfile /var/run/redis_6381.pid
port 6381
dbfilename dump6381.rdb
appendfilename appendonly6381.aof
replica-priority 80
# logfile access6381.log
include redis.conf
pidfile /var/run/redis_6382.pid
port 6382
dbfilename dump6382.rdb
appendfilename appendonly6382.aof
replica-priority 70
# logfile access6382.log
- 使用三个配置文件分别启动三台redis
5.1.1.4 设置主从关系
再打开三个会话框,分别使用客户端连接三台 Redis。
- 通过redis-cli -p 指定端口号, 进而指定进入哪个redis 的命令行
- 在三台节点的命令行分别输入 info replication命令 , 可发现三台节点的 role 都是 master
- 通过 slaveof 命令,指定 6380的 Redis 为 Master。 其中127.0.0.1是本地环回地址, 意为三台节点同属一个ip地址
- 在三台节点的命令行分别输入 info replication命令 ,
可发现81和82的 role 已经变为 slave, 且带有master的信息
80端口会显示所有从节点的信息
5.1.1.5 主从集群的性质
- 在80端口set几个键值对, 在81和82端口上可以正常查询到
- 在82/83端口set键值对时, 会报错. 因为从节点不允许写
- 尽管从节点关机不会影响整个集群的正常的工作, 但每次从节点的重新启动后 , 都要重新用slaveof命令将从节点纳入集群.
5.1.2 分级管理
若 Redis 主从集群中的 Slave 较多时,它们的数据同步过程会对 Master 形成较大的性能压力。此时可以对这些 Slave 进行分级管理。
设置方式很简单,只需要让低级别 Slave 指定其 slaveof 的主机为其上一级 Slave 即可。不过,上一级 Slave 的状态仍为 Slave,只不过,其是更上一级的 Slave。
5.1.3 容灾冷处理
在 Master/Slave 的 Redis 集群中,若 Master 出现宕机怎么办呢?
有两种处理方式,一种是通过手工角色调整,使 Slave 晋升为 Master 的冷处理;一种是使用哨兵模式,即热处理 , 来实现 Redis集群的高可用 HA(High Availability)
但无论 Master 是否宕机,Slave 都可通过 slaveof no one 将自己由 Slave 晋升为 Master。如果其原本就有下一级的 Slave,那么,其就直接变为了这些 Slave 的真正的 Master 了。而原来的 Master 也会失去这个原来的 Slave。
5.2 主从复制
主从复制,是指将master节点的数据,复制到slave节点中, 发生在slaveof命令之后。
数据的复制是单向的,只能由主节点到从节点。
中小项目未必会做Redis 的集群,但是至少都做了主从()
集群和主从的概念区别?
集群是多台机器做同一件事情(高性能HPC) 或者任意一台机器处理一个事情(负载均衡LBC), 例如负载均衡、热备
主从属于高可用的范畴: 主机挂了 备用马上接替. 平时只使用主机(高可用HA)
5.2.1 主从复制过程
当一个 slave 节点接收到类似 slaveof 127.0.0.1 6380 的指令后直至其可以从 master 持续复制数据,大体经历了如下几个过程:
- 保存 master 地址.
当 slave 接收到 slaveof 指令后,slave 会立即将新的 master 的地址保存下来。 - 建立连接
slave 中维护着一个定时任务,该定时任务会尝试着与该 master 建立 socket 连接。如果连接无法建立,则其会不断定时重试,直到连接成功或接收到 slaveof no one 指令。 - slave 发送 ping 命令
连接建立成功后,slave 会发送 ping 命令进行首次通信。如果 slave 没有收到 master 的回复,则 slave 会主动断开连接,下次的定时任务会重新尝试连接。 - 对 slave 身份验证
如果 master 收到了 slave 的 ping 命令,并不会立即对其进行回复,而是会先进行身份验证。如果验证失败,则会发送消息拒绝连接;如果验证成功,则向 slave 发送连接成功响应。 - master 持久化
首次通信成功后,slave 会向 master 发送数据同步请求。当 master 接收到请求后,会 fork出一个子进程,让子进程以异步方式立即进行持久化。 - 数据发送
持久化完毕后 master 会再 fork 出一个子进程,让该子进程以异步方式将数据发送给slave。slave 会将接收到的数据不断写入到本地的持久化文件中。
在 slave 数据同步过程中,master 的主进程仍在不断地接受着客户端的写操作,且不仅将新的数据写入到了 master 内存,同时也写入到了同步缓存。当 master 的持久化文件中的数据发送完毕后,master 会再将同步缓存中新的数据发送给 slave,由 slave 将其写入到本地持久化文件中。数据同步完成。 - slave 恢复内存数据
当 slave 与 master 的数据同步完成后,slave 就会读取本地的持久化文件,将其恢复到本地内存,然后就可以对外提供读服务了 - 持续增量复制
在 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,不完全同步)同步策略。
当全量复制过程出现由于网络抖动而导致复制过程中断,并重新连接成功后,复制过程可以 “断点续传”。
即从断开位置开始继续复制,而不用从头再来。这就大大提升了性能
5.2.2.2.1 系统为psync断点续传做出的改变
为了实现 psync,整个系统做了三个大的变化:
- 新增复制偏移量offset
系统为每个要传送数据进行了编号,该编号从 0 开始,每个字节一个编号。该编号称为复制偏移量。参与复制的主从节点都会维护该复制偏移量。
可以看到 , 两个从节点都同步到了2111, 主节点也发送到了2111
- 主节点复制 ID
当 master 启动后就会动态生成一个长度为 40 位的 16 进制字符串作为当前 master 的复制 ID,该 ID 是在进行数据同步时 slave 识别 master 使用的。通过 info replication 的master_replid 属性可查看到该 ID。 - 复制积压缓冲区
当 master 有连接的 slave 时,在 master 中就会创建并维护一个队列 backlog,默认大小为 1MB,该队列称为复制积压缓冲区。master 接收到了写操作数据不仅会写入到 master 主存,写入到 master 中为每个 slave 配置的发送缓存,而且还会写入到复制积压缓冲区。其作用就是用于保存最近操作的数据,以备“断点续传”时做数据补偿,防止数据丢失。
5.2.2.2.2 psync过程
5.2.2.2.3 psync存在的问题
- 在 psync 数据同步过程中,若 slave 重启,在 slave 内存中保存的 master 的动态 ID 与续传 offset 都会消失,“断点续传”将无法进行,从而只能进行全量复制,导致资源浪费。
- 在 psync 数据同步过程中,master 宕机后 slave 会发生“易主”,从而导致 slave 需要从新 master 进行全量复制,形成资源浪费。
5.2.2.3 进一步改进psync
Redis 4.0 对 psync 进行了改进,提出了“同源增量同步”策略。
- 解决slave重启后master动态id丢失问题
改进后的 psync 将 master 的动态 ID 直接写入到了 slave 的持久化文件中。
slave 重启后直接从本地持久化文件中读取 master 的动态 ID,然后向 master 提交获取复制偏移量的请求。master 会根据提交请求的 slave 地址,查找到保存在 master 中的复制偏移量,然后向 slave 回复 FULLRESYNC <master_replid> <repl_offset>,以告知 slave 其马上要开始发送的位置。然后 master 开始“断点续传”。 - 解决 slave 易主问题
slave 易主后需要和新 master 进行全量复制,本质原因是新 master 不认识 slave 提交的psync 请求中“原 master 的动态 ID”。
如果有一种办法, 可以令新master认识到一件事: 尽管自己不是slave所请求的旧master, 但自己的数据也是从旧master复制来的, 因此可以用自己的数据断点续传.
而新 master 中恰好保存的有“原 master 的动态 ID”。因为改进后的psync将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 Sentinel哨兵集群
前面提到可以通过冷处理/热处理解决主节点宕机问题.
但冷处理方式无法实现高可用。
Redis 从 2.8 版本开始提供了高可用的解决方案—— 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
需要注意的是, Sentinel哨兵实际就是一个特殊的节点.
哨兵节点是特殊的redis服务,不提供读写服务,主要用来监控redis实例节点。 哨
兵架构下client端第一次从哨兵找出redis的主节点,后续就直接访问redis的主节点,不会每次都通过sentinel代理访问redis的主节点,当redis的主节点挂掉时,哨兵会第一时间感知到,并且在slave节点中重新选出来一个新的master,然后将新的master信息通知给client端,从而实现高可用。
这里面redis的client端一般都实现了订阅功能,订阅sentinel发布的节点变动消息。
5.3.1 Sentinel哨兵高可用集群搭建
在“不差钱”的情况下,可以让 Sentinel 占用独立的主机,即在 Redis 主机上只启动 Redis进程,在 Sentinel 主机上只启动 Sentinel 进程。
下面要搭建一个“一主二从三哨兵”的高可用伪集群,即这些角色全部安装运行在一台主机上。“一主二从”使用前面的主从集群,下面仅搭建一个 Sentinel 伪集群。
5.3.1.1 在cluster文件夹中复制一份公共sentinel.conf文件
将 Redis 安装目录中的 sentinel.conf 文件复制到 cluster 目录中。
该配置文件中用于存放一些 sentinel 集群中的一些公共配置。
5.3.1.2 修改公共 sentinel.conf
- sentinel monitor
该配置通过来指定 Sentinel 要监控的 master 是谁,并为 master 起了一个
名字。
该名字在后面很多配置中都会使用。同时指定 Sentinel 集群中决定该master“客观下线状态”判断的法定 sentinel 数量。的另一个用途与sentinel 的 Leader 选举有关。要求中至少要有 max(quorum, sentinelNum/2+1)个 sentinel 参与,选举才能进行。
这里将该配置注释掉,因为要在后面的其它配置文件中设置,如果不注释就会出现配置冲突。
- sentinel auth-pass
如果 Redis 主从集群中的主机设置了访问密码,那么该属性就需要指定 master 的主机名与访问密码。以方便 sentinel 监控 master。
5.3.1.3 新建三个Sentinel各自的Sentinel.conf文件
三个文件都放置于cluster目录下. 内容分别为:
#将公共sentinel.conf文件包含进来
include sentinel.conf
pidfile /var/run/sentinel_26380.pid
port 26380
#指定当前监控的 master 的 IP 与 Port,同时为集群中 master 指定一个名称 mymaster,以方便其它属性使用。
#最后的 2 是参数 quorum 的值,quorum 有两个用途。一个是只有当 quorum 个 sentinel都认为当前 master 宕机了才能开启故障转移。另一个用途与 sentinel 的 Leader 选举有关。要求中至少要有 max(quorum, sentinelNum/2+1)个 sentinel 参与,选举才能进行。
sentinel monitor mymaster 192.168.192.102 6380 2# logfile access26380.log
include sentinel.conf
pidfile /var/run/sentinel_26381.pid
port 26381
sentinel monitor mymaster 192.168.192.102 6380 2# logfile access26381.log
include sentinel.conf
pidfile /var/run/sentinel_26382.pid
port 26382
sentinel monitor mymaster 192.168.192.102 6380 2# logfile access26382.log
5.3.2 启动Redis 高可用集群
5.3.2.1 启动三台主从节点
首先要启动三台 Redis,然后再通过 slaveof 关联它们。
5.3.2.2 启动 Sentinel 集群
启动sentinel有一条特殊的命令, 但其本质和redis-server一致, 因此启动sentinel有两种命令
分别启动三台sentinel
redis-sentinel sentinel26380.conf
redis-server sentinel26380.conf --sentinel
5.3.2.3 查看sentinel信息
运行中的 Sentinel 就是一个特殊 Redis,其也可以通过客户端连接,然后通过 info sentinel来查看当前连接的 Sentinel 的信息。
当前监控的master只有1台
5.3.2.4 查看sentinel各自的配置文件
例如打开26380的
5.3.3 优化公共sentinel.conf文件配置
5.3.3.1 sentinel down-after-milliseconds
每个 Sentinel 会通过定期发送 ping 命令来判断 master、slave 及其它 Sentinel 是否存活。如果 Sentinel 在该属性指定的时间内没有收到它们的响应,那么该 Sentinel 就会主观认为该主机宕机。默认为 30 秒。
5.3.3.2 sentinel parallel-syncs
该属性用于指定,在故障转移期间,即老的 master 出现问题,新的 master 刚晋升后,允许多少个 slave 同时从新 master 进行数据同步。默认值为 1 表示所有 slave 逐个从新 master进行数据同步。
5.3.3.3 sentinel failover-timeout
指定故障转移的超时时间,默认时间为 3 分钟。该超时时间的用途很多:
- 由于第一次故障转移失败,在同一个 master 上进行第二次故障转移尝试的时间为该failover-timeout 的两倍
- 新 master 晋升完毕,slave 从老 master 强制转到新 master 进行数据同步的时间阈值。
- 取消正在进行的故障转换所需的时间阈值。
- 新 master 晋升完毕,所有 replicas 的配置文件更新为新 master 的时间阈值。
5.3.4 哨兵机制原理
5.3.4.1 三个任务
Sentinel 维护着三个定时任务以监测 Redis 节点及其它 Sentinel 节点的状态。
- info 任务
每个 Sentinel 节点每 10 秒就会向 Redis 集群中的每个节点发送 info 命令,以获得最新的Redis 拓扑结构。 - 心跳任务
每个Sentinel 节点每1秒就会向所有 Redis 节点及其它Sentinel 节点发送一条 ping 命令,以检测这些节点的存活状态。该任务是判断节点在线状态的重要依据。 - 发布/订阅任务
每个 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.3.4.2 下线Redis节点
5.3.4.2.1 主观下线
每个 Sentinel 节点每秒就会向每个 Redis 节点发送 ping 心跳检测,如果 Sentinel 在down-after-milliseconds 时间内没有收到某 Redis 节点的回复,则 Sentinel 节点就会对该 Redis节点做出“下线状态”的判断。这个判断仅仅是当前 Sentinel 节点的“一家之言”,所以称为主观下线。
5.3.4.2.2 客观下线
当 Sentinel 主观下线的节点是 master 时,该 Sentinel 节点会向每个其它 Sentinel 节点发送 sentinel is-master-down-by-addr 命令,以询问其对 master 在线状态的判断结果。这些Sentinel 节点在收到命令后会向这个发问 Sentinel 节点响应 0(在线)或 1(下线)。当该Sentinel收到超过 quorum 个下线判断后,就会对 master 做出客观下线判断。
5.3.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.3.4.4 故障转移过程
- Sentinel Leader 根据 master 选择算法选择出一个 slave 节点作为新的 master
- Sentinel Leader 向新 master 节点发送 slaveof no one 指令,使其晋升为 master
- Sentinel Leader 向新 master 发送 info replication 指令,获取到 master 的动态 ID
- Sentinel Leader 向其余 Redis 节点发送消息,以告知它们新 master 的动态 ID
- Sentinel Leader 向其余 Redis 节点发送 slaveof 指令,使它们成为新master 的 slave
- Sentinel Leader 从所有 slave 节点中每次选择出 parallel-syncs 个 slave 从新 master 同步数据,直至所有 slave 全部同步完毕
- 故障转移完毕
5.3.4.4.1 master选择算法
在进行故障转移时,Sentinel Leader 需要从所有 Redis 的 Slave 节点中选择出新的 Master。其选择算法为:
- 过滤掉所有主观下线的,或心跳没有响应 Sentinel 的,或 replica-priority 值为 0 的 Redis节点
- 在剩余 Redis 节点中选择出 replica-priority 最小的的节点列表。如果只有一个节点,则直接返回,否则,继续
- 从优先级相同的节点列表中选择复制偏移量最大的节点。如果只有一个节点,则直接返回,否则,继续
- 从复制偏移值量相同的节点列表中选择动态 ID 最小的节点返回
5.3.4.6上线Redis节点
不同的节点类型,其上线的方式也是不同的。
5.3.4.6.1 原 Redis 节点上线
无论是原下线的 master 节点还是原下线的 slave 节点,只要是原 Redis 集群中的节点上线,只需启动 Redis 即可。因为每个 Sentinel 中都保存有原来其监控的所有 Redis 节点列表, Sentinel 会定时查看这些 Redis 节点是否恢复。如果查看到其已经恢复,则会命其从当前master 进行数据同步。
不过,如果是原 master 上线,在新 master 晋升后 Sentinel Leader 会立即先将原 master节点更新为 slave,然后才会定时查看其是否恢复。
5.3.4.6.2 新 Redis 节点上线
如果需要在 Redis 集群中添加一个新的节点,其未曾出现在 Redis 集群中,则上线操作只能手工完成。
即添加者在添加之前必须知道当前 master 是谁,然后在新节点启动后运行slaveof 命令加入集群。
5.3.4.6.3 Sentinel 节点上线
如果要添加的是 Sentinel 节点,无论其是否曾经出现在 Sentinel 集群中,都需要手工完成。
即添加者在添加之前必须知道当前 master 是谁,然后在配置文件中修改 sentinel monitor属性,指定要监控的 master。然后启动 Sentinel 即可。
5.4 CAP定理
CAP 定理指的是在一个分布式系统中,一致性 Consistency、可用性 Availability、分区容错性 Partition tolerance,三者不可兼得。
- 一致性(C):分布式系统中多个主机之间是否能够保持数据一致的特性。即,当系统数据发生更新操作后,各个主机中的数据仍然处于一致的状态。
- 可用性(A):系统提供的服务必须一直处于可用的状态,即对于用户的每一个请求,系统总是可以在有限的时间内对用户做出响应。
- 分区容错性(P):分布式系统在遇到任何网络分区故障时,仍能够保证对外提供满足一致性和可用性的服务。
对于分布式系统,网络环境相对是不可控的,出现网络分区是不可避免的,因此系统必须具备分区容错性。但系统不能同时保证一致性与可用性。
即要么 CP,要么 AP。
5.4.1 BASE理论
BASE 是 Basically Available(基本可用)、Soft state(软状态)和 Eventually consistent(最终一致性)三个短语的简写,BASE 是对 CAP 中一致性和可用性权衡的结果,其来源于对大规模互联网系统分布式实践的结论,是基于 CAP 定理逐步演化而来的。
BASE 理论的核心思想是:即使无法做到强一致性,但每个系统都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性。
- 基本可用
基本可用是指分布式系统在出现不可预知故障的时候,允许损失部分可用性。 - 软状态
硬状态, 相对于一致性,要求多个节点的数据副本都是一致的.
软状态,是指允许系统数据存在的中间状态,并认为该中间状态的存在不会影响系统的整体可用性,即允许系统主机间进行数据同步的过程存在一定延时。软状态,其实就是一种灰度状态,过渡状态。 - 最终一致性
最终一致性强调的是系统中所有的数据副本,在经过一段时间的同步后,最终能够达到一个一致的状态。因此,最终一致性的本质是需要系统保证最终数据能够达到一致,而不需要保证系统数据的实时一致性。
可以说软状态和最终一致性是一体的 , 实际是对CAP中一致性的降级.
5.4.2 常见的CAP场景
-
Zookeeper
ZooKeeper遵循的是 CP 模式,即保证了一致性,但牺牲了可用性。
当 Leader 节点中的数据发生了变化后,在 Follower 还没有同步完成之前,整个 Zookeeper集群是不对外提供服务的。如果此时有客户端来访问数据,则客户端会因访问超时而发生重试。不过,由于 Leader 的选举非常快,所以这种重试对于用户来说几乎是感知不到的。所以说,Zookeeper 保证了一致性,但牺牲了可用性。 -
Redis 遵循的是 AP 模式,即保证了可用性,但牺牲了一致性。
-
Nacos 在做注册中心时,默认是 AP 的。但其也支持 CP 模式,但需要用户提交请求进行转换。
5.5 Raft算法
Raft 算法是一种通过对日志复制管理来达到集群节点一致性的算法。
这个日志复制管理发生在集群节点中的 Leader 与 Followers 之间。
Raft 通过选举出的 Leader 节点负责管理日志复制过程,以实现各个节点间数据的一致性。
5.5.1 角色、任期及角色转变
在 Raft 中,节点有三种角色:
- Leader:唯一负责处理客户端写请求的节点;也可以处理客户端读请求;同时负责日志复制工作
- Candidate:Leader 选举的候选人,其可能会成为 Leader。是一个选举中的过程角色
- Follower:可以处理客户端读请求;负责同步来自于 Leader 的日志;当接收到其它Cadidate 的投票请求后可以进行投票;当发现 Leader 挂了,其会转变为 Candidate 发起Leader 选举
5.5.2 细说Leader选举
5.5.2.0 选举时机
在很多时候,当 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.5.2.1 选举中的两个超时设置
第一个是 选举超时 设置, 也即上述randomized election timeouts
第二个是 任期term , followe转变为candidate后 , term会自增1. follower收到candidate投票请求时, 如果candidate的term大于follower, 则follower将term更新为与candidate一致
5.5.2.2 选举过程(算法流程)
- 我要选举
若 follower 在心跳超时范围内没有接收到来自于 leader 的心跳,则认为 leader 挂了。此时其首先会使其本地 term 增一。然后 follower 会完成以下步骤:
- 先检查是否接收到了其它 candidate 的投票请求,若有则投
- 由 follower 转变为 candidate
- 若之前尚未投票,则向自己投一票
- 向其它节点发出投票请求,然后等待响应
- 我要投票
follower 在接收到投票请求后,仅当同时满足下列条件时, 才会投票:
a. 本follower尚未转变为candidate(因为只要转变为candidate, 就会立即投票给自己)
b. 发来投票请求的candidate的term大于(>)本follower的term, 以保证日志最新
上述两个条件满足时, 谁先来投给谁.
- 等待响应
当一个 Candidate 发出投票请求后会等待其它节点的响应结果。这个响应结果可能有三种情况:
- 收到过半选票,成为新的 leader。然后会将消息广播给所有其它节点,以告诉大家我是新的 Leader 了
- 接收到别的 candidate 发来的新 leader 通知,比较了新 leader 的 term 并不比自己的 term小,则自己转变为 follower
- 经过一段时间后,没有收到过半选票,也没有收到新 leader 通知,则随机等待一段时间重新发出选举
5.5.3 同步数据
在拥有 Leader 之后,我们要将leader的所有改变同步到所有节点, 这个同步过程通过日志复制管理实现.
5.5.3.1 日志复制状态机
Raft 算法一致性的实现,是基于日志复制状态机的。状态机的最大特征是,不同 Server中的状态机若当前状态相同,然后接受了相同的输入,则一定会得到相同的输出。
5.5.3.2 同步处理过程
为了保证可用性,各个节点中的日志可以不完全相同,但 leader 会不断给 follower 发送 box,以使各个节点的 log 最终达到相同。即 raft 算法不是强一致性的,而是最终一致的。
- 每次有改变时, 该改变会以条目(entry)的形式写入leader节点日志中 , 但不会立即commit生效
- 为了commit该改变, 会首先将改变发送到所有follower, 所有follower收到后比对自己的term和改变的term, 只要不比自己小就立即执行该改变
- leader节点在确认多数follower节点已经写入该改变后, 正式commit该改变, 将该改变写入leader节点自身, 并通知所有follower该改变已经commit.
- 此时集群达到一致性
5.5.4 脑裂
Raft 集群存在脑裂问题。
在哨兵架构中,redis的集群脑裂是某个master所在机器突然脱离了正常的网络,导致redis master节点跟 , redis slave节点和sentinel集群处于不同的网络分区,此时因为sentinel集群无法感知到master的存在,哨兵可能就会认为master宕机了,然后开启选举,将其他slave切换成了master,这个时候集群里就会有两个master,也就是所谓的脑裂。
由于三机房(一般为奇数)部署的容灾能力最强,所以生产环境下,三机房部署是最为常见的。下面以三机房部署为例进行分析,根据机房断网情况,可以分为五种情况:
5.5.4.1 一定会脑裂的情况
5.5.5 Sentinel Leader宕机
5.5.5.1 请求到达前 Leader 挂了
client 发送写操作请求到达 Leader 之前 Leader 就挂了,因为请求还没有到达集群,所以这个请求对于集群来说就没有存在过,对集群数据的一致性没有任何影响。Leader 挂了之后,会选举产生新的 Leader。
5.6 Raft动画演示
https://thesecretlivesofdata.com/