数据库系列文章:
关系型数据库:
- MySQL —— 基础语法大全
- MySQL —— 进阶
非关系型数据库:
- Redis 的安装与配置
- Redis 基本命令(上)
- Redis 基本命令(下)
- Redis 持久化
为了避免 Redis 的 单点故障 问题, 我们可以搭建一个 Redis 集群,将数据备份到集群中的其它节点上。若一个 Redis 节点宕机,则由集群中的其它节点顶上。
Redis的 主从集群 是一个“ 一主多从 ”的 读写分离 集群。 集群中 的 Master
节点负责处理客户端的读写请求,而 Slave
节点仅能处理客户端的读请求。只所以要将集群搭建为 读写分离 模式,主要原因是,对于数据库集群,写操作压力一般都较小,压力大多数来自于读操作请求。所以,只有一个节点负责处理写操作请求即可。
在采用 单线程 IO 模型 时,为了提高处理器的利用率,一般会在一个主机中安装多台 Redis,构建一个 Redis 主从伪集群 。当然,搭建伪集群的另一个场景是,在学习 Redis ,而学习用主机内存不足以创建多个虚拟机。
下面要搭建的读写分离伪集群包含一个 Master
与两个 Slave
。 它们的端口号分别是: 6380
、6381
、 6382
。
⭐️(1)复制 redis.conf
在redis 安装目录中 mkdir
一个目录,名称随意。这里命名为 cluster
。然后将 redis.conf
文件复制到 cluster
目录中。该文件后面会被其它配置文件包含,所以该文件中需要设置每个 Redis 节点相同的公共的属性。
⭐️(2)修改 redis.conf
在 redis.conf
中做如下几项修改:
A、 masterauth
因为我们要搭建 主从集群,且每个主机都有可能会是 Master
,所以最好 不要设置密码验证 属性 requirepass
。如果真需要设置,一定要每个主机的密码都 设置为相同 的。此时每个配置文件中都要设置两个完全相同的属性: requirepass
与 masterauth
。
requirepass
用于指定当前主机的访问密码;masterauth
用于指定当前 slave
访问 master
时向 master
提交的访问密码,用于让 master
验证自己身份是否合法。B、 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
算法的开头。
⭐️(3)新建 redis6380.conf
新建一个redis 配置文件 redis6380.conf
,该配置文件中的 Redis 端口号为 6380
。
⭐️(4)再复制出两个 conf 文件
再使用 redis6380.conf
复制出两个 conf
文件: redis6381.conf
与 redis6382.conf
。然后修改其中的内容。
修改 redis6382.conf
的内容如下:
⭐️(5)启动三台 redis
分别使用 redis6380.conf
、 redis6381.conf
与 redis6382.conf
三个配置文件启动三台 Redis。
⭐️(6)设置主从关系
再打开三个会话框,分别使用客户端连接三台
Redis 。 然后通过 slaveof
命令,指定 6380
的 Redis 为 Master
。
⭐️(7)查看状态信息
通过 info replication
命令可查看当前连接的 Redis 的状态信息。
若 Redis 主从集群 中的 Slave
较多时,它们的数据同步过程会对 Master
形成较大的性能压力。此时可以对这些 Slave
进行分级管理。
设置方式很简单,只需要让低级别 Slave
指定其 slaveof
的主机为其上一级 Slave
即可。不过,上一级 Slave
的状态仍为 Slave
,只不过,其是更上一级的 Slave
。
例如,指定 6382
主机为 6381
主机的 Slave
,而 6381
主机仍为真正的 Master
的 Slave
。
Master
的 Slave
只有 6381
一个主机。 在 Master/Slave
的 Redis 集群中,若 Master
出现宕机怎么办呢?有两种处理方式:
Slave
晋升为 Master
的 冷处理;HA
,即热处理。 无论 Master
是否宕机, Slave
都可通过 slaveof no one
将自己由 Slave
晋升为 Master
。如果其原本就有下一级的 Slave
,那么,其就直接变为了这些 Slave
的真正的 Master
了。而原来的 Master
也会失去这个原来的 Slave
。
当一个 Redis 节点 (slave
节点) 接收到 类似 slaveof 127.0.0.1 6380
的指令后直至其可以从 master
持续复制数据,大体经历了如下几个过程:
当 slave
接收到 slaveof
指令后,slave
会立即将新的 master
的地址保存下来。
⭐️(2) 建立连接
slave
中维护着一个定时任务,该定时任务会尝试着与该 master
建立 socket
连接。如果连接无法建立,则其会不断定时重试,直到 连接成功 或 接收到 slaveof no one
指令。
⭐️(3) slave 发送 ping 命令
连接建立成功后,slave
会发送 ping
命令进行首次通信。如果 slave
没有收到 master
的回复,则 slave
会主动断开连接,下次的定时任务会重新尝试连接。
⭐️(4) 对 slave 身份验证
如果 master
收到了 slave
的 ping
命令,并不会立即对其进行回复,而是会先进行身份验证。如果验证失败,则会发送消息拒绝连接;如果验证成功,则向 slave
发送连接成功响应。
⭐️(5) master 持久化
首次通信成功后,slave
会向 master
发送数据同步请求。当 master
接收到请求后,会 fork
出一个子进程,让子进程以 异步方式 立即进行持久化。
⭐️(6) 数据发送
持久化完毕后 master
会再 fork
出一个子进程,让该子进程以 异步方式 将数据发送给 slave
。slave
会将接收到的数据 不断写入到本地的持久化文件中。
在 slave
数据同步过程中,master
的主进程仍在不断地接受着客户端的写操作,且不仅将新的数据写入到了master
内存,同时也写入到了 同步缓存 。当 master
的持久化文件中的数据发送完毕后,master
会再将 同步缓存中新的数据 发送给 slave
,由 slave
将其写入到本地持久化文件中。数据同步完成。
⭐️(7) slave 恢复内存数据
当 slave
与 master
的数据同步完成后, slave
就会读取本地的持久化文件,将其恢复到本地内存,然后就可以对外提供读服务了。
⭐️(8) 持续增量复制
在 slave
对外提供服务过程中, master
会持续不断的将新的数据以 增量方式 发送给 slave
以保证主从数据的一致性。
⭐️(1) sync 同步
Redis 2.8
版本之前,首次通信成功后, slave
会向 master
发送 sync
数据同步请求。然后 master
就会将其所有数据全部发送给 slave
,由 slave
保存到其本地的持久化文件中。这个过程称为 全量复制。
但这里 存在一个问题:在 全量复制 过程中可能会出现由于网络抖动而导致复制过程中断。当网络恢复后,
slave
与master
重新连接成功,此时slave
会重新发送sync
请求,然后会 从头开始 全量复制。
由于全量复制过程非常耗时,所以期间出现网络抖动的概率很高。而中断后的从头开始不仅需要消耗大量的系统资源、网络带宽,而且可能会出现长时间无法完成全量复制的情况。
⭐️(2) psync 同步
Redis 2.8
版本之后,全量复制 采用了 psync
(Partial Sync
,不完全同步
) 同步策略。 当全量复制过程出现由于网络抖动而导致复制过程中断时,当重新连接成功后,复制过程可以 “断点续传" 。即从断开位置开始继续复制 ,而不用从头再来。这就大大提升了性能。
为了实现 psync
,整个系统做了 三个大的变化:
A、 复制偏移量
系统为每个要传送数据进行了编号,该编号从 0
开始,每个字节一个编号。该编号称为复制偏移量。参与复制的主从节点都会维护该复制偏移量。
master
每发送过一个字节数据后就会进行累计。统计信息通过 info replicatio
n 的 master_repl_offset
可查看到。同时, slave
会定时向 master
上报其自身已完成的 复制偏移量 给 master
,所以 master
也会保存 slave
的复制偏移量 offset
。
slave
在接收到 master
的数据后,也会累计接收到的偏移量。统计信息通过 info replication
的 slave_repl_offset
可查看到。
B、 主节点复制 ID
当 master
启动后就会动态生成一个长度为 40 位的 16 进制字符串作为当前 master
的 复制 ID ,该 ID 是在进行数据同步时 slave
识别 master
使用的。通过 info replication
的master_replid
属性可查看到该 ID 。
C、 复制积压缓冲区
当 master
有连接的 slave
时,在 master
中就会创建并维护一个队列 backlog
,默认大小为 1MB
,该队列称为 复制积压缓冲区 。 master
接收到了 写操作数据 不仅会写入到 master
主存,写入到 master
中为每个 slave
配置的发送缓存,而且还会写入到 复制积压缓冲区。其作用就是用于保存最近操作的数据,以备“ 断点续传 ”时做 数据补偿,防止数据丢失。
psync
是一个由 slave
提交的命令,其格式为 psync
,表示当前 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
:告知 slave
当前 master
的动态 ID 及可以开始全量复制了,这里的 repl_offset
一般为 0
CONTINUE
:告知 slave
可以按照你提交的 repl_offset
后面位置开始“续传”了ERR
:告知 slave
,当前 master
的版本低于 Redis 2.8 ,不支持 psyn
,你可以开始全量复制了E、 psync 存在的问题
psync
数据同步过程中,若 slave
重启,在 slave
内存中保存的 master
的 动态 ID
与续传 offset
都会消失,“断点续传” 将无法进行,从而只能进行全量复制,导致资源浪费。psync
数据同步过程中, master
宕机 后 slave
会发生“易主”,从而导致 slave
需要从新 master
进行全量复制,形成资源浪费。⭐️(3) psync 同步的改进
Redis 4.0 对 psync
进行了改进,提出了“同源增量同步”策略。
A、 解决 slave 重启问题
针对“ slave
重启 时 master
动态 ID 丢失问题”,改进后的 psync
将 master
的 动态 ID
直接写入到了 slave
的持久化文件中。
slave重启后直接从本地持久化文件中读取 master
的 动态 ID
,然后向 master
提交 获取复制偏移量的请求。 master
会根据提交请求的 slave
地址,查找到保存在 master
中的复制偏移量,然后向 slave
回复 FULLRE SYNC
,以告知 slave
其马上要开始发送的位置。然后 master
开始“断点续传”。
B、 解决 slave 易主问题
slave
易主后需要和新 master
进行全量复制,本质原因是 新 master
不认识 slave
提交的 psync
请求中“原 master
的 动态 ID
”。如果 slave
发送 PSYNC
< 原 master_replid>
命令,新 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 。
⭐️(4) 无盘操作
Redis 6.0 对同步过程又进行了改进,提出了“无盘全量同步”与“无盘加载”策略,避免了耗时的 IO 操作。
master
的主进程 fork
出的子进程直接将内存中的数据发送给 slave
,无需经过磁盘。slave
在接收到 master
发送来的数据后不需要将其写入到磁盘文件,而是直接写入到内存,这样 slave
就可快速完成数据恢复。⭐️(5) 共享复制积压缓冲区
Redis 7.0 版本对 复制积压缓冲区 进行了改进,让各个 slave
的发送缓冲区 共享复制积压缓冲区。这使得复制积压缓冲区的作用,除了可以保障数据的安全性外,还作为所有 slave
的发送缓冲区,充分利用了复制积压缓冲区。
对于 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 。 在“不差钱”的情况下,可以让 Sentinel
占用独立的主机,即在 Redis 主机上只启动 Redis 进程,在 Sentinel
主机上只启动 Sentinel 进程
。下面要搭建一个“ 一主二从三哨兵 ”的高可用伪集群,即这些角色全部安装运行在一台主机上。“一主二从”使用前面的主从集群,下面仅搭建一个 Sentinel
伪集群。
⭐️(1) 复制 sentinel.conf
将 Redis 安装目录中的 sentinel.conf
文件复制到 cluster
目录中。该配置文件中用于存放一些 sentinel
集群中的一些公共配置。
⭐️(2) 修改 sentinel.conf
修改 cluster/sentinel.conf
配置文件。
A、 sentinel monitor
该配置用于指定 Sentinel 要监控的 master
是谁
,并为 master
起了一个名字
。该名字在后面很多配置中都会使用。同时指定 Sentinel 集群中决定该 master
“客观下线状态” 判断的法定 sentinel
数量
。
的另一个用途与 sentinel
的 Leader 选举有关。要求中至少要有 max(quorum, sentinelNum/2+1)
个 sentinel
参与,选举才能进行。(面试会问)这里将该配置注释掉,因为要在后面的其它配置文件中设置,如 果不注释就会出现配置冲突。
B、 sentinel auth-pass
如果 Redis 主从集群中的主机设置了访问密码,那么该属性就需要指定 master
的主机名与访问密码。以方便 sentinel
监控 master
。
⭐️(3) 新建 sentinel26380.conf
在Redis 安装目录下的 cluster
目录中新建 sentinel26380.conf
文件作为 Sentinel 的配置文件,并在其中键入如下内容:
include sentinel.conf
pidfile /var/run/sentinel_26380.pid
port 26380
# 使用自己主机的ip
sentinel monitor mymaster 192.168.216.128 6380 2
# logfile access26380.log
sentinel monitor
属性用于指定当前监控的 master
的 IP
与 Port
,同时为集群中 master
指定一个名称 mymaster
,以方便其它属性使用。
最后的
2
是参数quorum
的值,quorum
有两个用途。
- 一个是只有当
quorum
个sentinel
都认为当前master
宕机了才能开启故障转移。- 另一个用途与
sentinel
的 Leader 选举有关。要求中至少要有max(quorum, sentinelNum/2+1)
个sentinel
参与,选举才能进行。
⭐️(4) 再复制两个 conf 文件
再使用 sentinel26380.conf
复制出两个 conf
文件:sentinel26381.conf
与 sentinel26382.conf
。然后修改其中的内容。
修改 sentinel26381.conf
。
修改 sentinel26382.conf
。
⭐️(1) 启动并关联 Redis 集群
首先要启动三台Redis ,然后再通过 slaveof
关联它们。
⭐️(2) 启动 Sentinel 集群
A、 启动命令
在 /usr/local/bin
目录 下有一个命令 redis-sentinel
用于启动 Sentinel 。不过,我们发现一个奇怪的现象: /usr/local/bin
目录中的 redis-sentinel
命令是 redis-server
命令的软链接,这是为什么呢?
查看 Redis 安装目录中的 src
目录中的 redis-server
与redis-sentinel
命令,我们发现这两个命令的大小一模一样。其实,这两个命令本质上是同一个命令。
只所以可以启动不同的进程,主要是因为在启动时所加载的配置文件的不同。所以在启动 Sentinel 时,需要指定 sentinel.conf
配置文件。
B、 两种启动方式
由于 redis-server
与 redis-sentinel
命令本质上是同一个命令,所以使用这两个命令均可启动 Sentinel
。
redis-sentinel
命令: redis-sentinel sentinel26380.conf
redis-server
命令: redis-server sentinel26380.conf --sentinel
C、 启动三台 Sentinel
⭐️(3) 查看 Sentinel 信息
运行中的 Sentinel 就是一个 特殊 Redis ,其也可以通过客户端连接,然后通过 info sentinel
来查看当前连接的 Sentinel 的信息。
⭐️(4) 查看 sentinel 配置文件
打开任意 sentinel
的配置文件,发现其配置内容中新增加了很多配置。
在公共的 sentinel.conf
文件 中,还可以通过修改一些其它属性的值来达到对 Sentinel
的配置优化。
⭐️(1) sentinel down-after-milliseconds
每个 Sentinel 会通过定期发送 ping
命令来判断 master
、 slave
及其它 Sentinel 是否存活。如果 Sentinel 在该属性指定的时间内没有收到它们的响应,那么该 Sentinel 就会 主观认为 该主机宕机。默认为 30 秒
。
⭐️(2) sentinel parallel-syncs
该属性用于指定,在故障转移期间,即老的 master
出现问题,新的 master
刚晋升后,允许多少个 slave
同时从新 master
进行数据同步。默认值为 1
表示所有 slave
逐个从新 master
进行数据同步。
注意:为
1
时,同步时间较长,可能造成在数据不一致(可读);但过大会造成新master
同步压力过大,且redis集群不对外提供任何服务(读写都不行)!
⭐️(3) sentinel failover-timeout
指定故障转移的超时时间,默认时间为 3 分钟
。该超时时间的用途很多:
master
上进行第二次故障转移尝试的时间为该 failover-timeout
的两倍。master
晋升完毕, slave
从老 master
强制转到新 master
进行数据同步的 时间阈值。master
晋升完毕,所有 replicas
的配置文件更新为新 master
的 时间阈值。⭐️(4) sentinel deny-scripts-reconfig
指定是否可以通过命令 sentinel set
动态修改 notification-script
与 client-reconfig-script
两个脚本。默认是不能的。这两个脚本如果允许动态修改,可能会引发安全问题。
⭐️(5) 动态修改配置
通过 redis-cli
连接上 Sentinel
后,通过 sentinel set
命令可动态修改配置信息。 例如,下面的命令动态修改了 sentinel monitor
中的 quorum
的值。
下表是 sentinel set
命令支持的参数:
Sentinel 维护着三个定时任务 以监测 Redis
节点及其它 Sentinel
节点的状态。
⭐️(1) info 任务
每个 Sentinel
节点每 10 秒
就会向 Redis
集群中的每个节点发送 info
命令,以获得最新的 Redis
拓扑结构。
⭐️(2) 心跳任务
每个 Sentinel
节点每 1 秒
就会向所有 Redis
节点及其它 Sentinel
节点发送一条 ping
命令,以检测这些节点的存活状态。该任务是判断节点在线状态的重要依据。
⭐️(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
节点客观下线的判断依据。 对于每个 Redis 节点在线状态的监控是由 Sentinel
完成的。
⭐️(1) 主观下线
每个 Sentinel
节点每秒就会向每个 Redis 节点发送 ping
心跳检测,如果 Sentinel
在 down-after-milliseconds
时间内没有收到某 Redis 节点的回复,则 Sentinel
节点就会对该 Redis 节点做出“下线状态”的判断。这个判断仅仅是当前 Sentinel
节点的“ 一家之言 ”,所以称为主观下线。
⭐️(2) 客观下线
当 Sentinel
主观下线的节点是 master
时,该 Sentinel
节点( 主动 )会向每个其它 Sentinel
节点发送 sentinel is-master-down-by-addr
命令,以询问其对 master
在线状态的判断结果。这些 Sentinel
节点在收到命令后会向这个发问 Sentinel
节点响应 0
(在线)或 1
(下线)。当 Sentinel
收到 超过 quorum
个下线判断后,就会对 master
做出客观下线判断。
当 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 不再存在。 在进行故障转移时, Sentinel Leader
需要从所有 Redis
的 Slave
节点中选择出新的 Master
。其选择算法为:
Sentinel
的replica-priority
值为 0
Redis
节点Redis
节点中选择出 replica-priority
最小的的节点列表。如果只有一个节点,则直接返回,否则,继续 Sentinel Leader
负责整个故障转移过程,经历了如上步骤:
Sentinel Leader
根据 master
选择算法选择出一个 slave
节点作为新的 master
Sentinel Leader
向新 master
节点发送 slaveof no one
指令,使其晋升为 master
Sentinel Leader
向新 master
发送 info replication
指令,获取到 master
的动态 IDSentinel Leader
向其余 Redis 节点发送消息,以告知它们新 master
的动态 ID (广播)Sentinel Leader
向其余 Redis 节点发送 slaveof
指令,使它们成为新 master
的 slave
Sentinel Leader
从所有 slave
节点中每次选择出 parallel-syncs
个 slave
从新 master
同步数据,直至所有 slave
全部同步完毕不同的节点类型,其上线的方式也是不同的。
⭐️(1) 原 Redis 节点上线
无论是原下线的 master
节点还是原下线的 slave
节点,只要是原 Redis
集群中的节点上线,只需启动 Redis
即可。因为每个 Sentinel
中都保存有原来其监控的所有 Redis
节点列表,Sentinel
会定时查看这些 Redis
节点 是否恢复。如果查看到其已经恢复,则会命其从当前 master
进行数据同步。
不过,如果是原 master
上线,在新 master
晋升后 Sentinel Leader
会立即先将原 master
节点更新为 slave
,然后才会定时查看其是否恢复。
⭐️(2) 新 Redis 节点上线
如果需要在 Redis
集群中添加一个新的节点,其未曾出现在 Redis
集群中,则上线操作 只能手工完成。即添加者在添加之前必须知道当前 master
是谁,然后在新节点启动后运行 slaveof
命令加入集群。
⭐️(3) Sentinel 节点上线
如果要添加的是 Sentinel
节点,无论其是否曾经出现在 Sentinel
集群中,都需要手工完成。即添加者在添加之前必须知道当前 master
是谁,然后在配置文件中修改 sentinel monitor
属性,指定要监控的 master
。然后启动 Sentinel
即可。
CAP 定理 指的是在一个分布式系统中,一致性 Consistency
、可用性 Availability
、分区容错性 Partition tolerance
,三者不可兼得 。
C
):分布式系统中多个主机之间是否能够保持数据一致的特性。即,当系统数据发生更新操作后,各个主机中的数据仍然处于一致的状态。A
):系统提供的服务必须一直处于可用的状态,即对于用户的每一个请求,系统总是可以在 有限的时间内 对用户 做出响应。P
):分布式系统在遇到任何网络分区故障时,仍能够保证对外提供满足一致性和可用性的服务。 CAP定理的内容是:对于分布式系统,网络环境相对是不可控的,出现网络分区是不可避免的,因此系统 必须具备分区容错性。但系统 不能同时保证 一致性 与 可用性。即要么 CP
要么 AP
。
BASE 是 Basically Available
(基本可用)、 Soft state
(软状态) 和 Eventually consistent
(最终一致性)三个短语的简写, BASE 是对 CAP 中 一致性 和 可用性 权衡的结果,其来源于对大规模互联网系统分布式实践的结论,是基于 CAP 定理逐步演化而来的。
BASE 理论的核心思想是:即使无法做到 强一致性,但每个系 都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性。
(1) 基本可用
基本可用是指分布式系统在出现不可预知故障的时候,允许损失部分可用性。
(2) 软状态
软状态,是指允许系统数据存在的中间状态,并认为该中间状态的存在不会影响系统的整体可用性,即允许系统主机 间进行数据同步的过程存在一定延时。软状态,其实就是一种灰度状态,过渡状态。
(3) 最终一致性
最终一致性强调的是系统中所有的数据副本,在经过一段时间的同步后,最终能够达到一个一致的状态。因此,最终一致性的本质是需要系统保证最终数据能够达到一致,而 不需要 保证系统数据的实时一致性。
实时一致性:要求实际内容一旦发生更新,客户端立刻可以访问到最新的数据。所以,在集群环境下该特性是无法实现的,只存在于单机环境中。
最终一致性:数据内容发生变更后,经过一小段时间后,客户端可以访问到最新的数据。
实时一致性与最终一致性两个概念是从客户端访问到一致性内容的 时间角度 来说的;单从客户端访问到的 内容角度 来说(不说时间问题),还有两个概念:
强一致性:也称为严格一致性。要求客户端访问到的一定是更新过的新数据。
弱一致性:允许客户端从集群不同节点访问到的数据是不一致的。
下面将生产中常见到的一些中间件与服务器集群的 CAP
特性进行分析。
(1)Zookeeper 与 CAP
Zookeeper
遵循的是 CP
模式 ,即 保证了 一致性,但牺牲了可用性。
当 Leader 节点中的数据发生了变化后,在 Follower 还没有同步完成之前,整个 Zookeeper 集群是不对外提供服务的。如果此时有客户端来访问数据,则客户端会因访问超时而发生重试。(不过,由于 Leader 的选举非常快,所以这种重试对于用户来说几乎是感知不到的)。所以说, Zookeeper 保证了一致性,但牺牲了可用性。
(2) Consul 与 CAP
Consul 遵循的是 CP
模式 ,即 保证了 一致性,但牺牲了可用性。
(3) Redis 与 CAP
Redis 遵循的是 AP
模式 ,即 保证了 可用性,但牺牲了一致性。
(4) Eureka 与 CAP
Eureka 遵循的是 AP
模式 ,即 保证了 可用性,但牺牲了一致性。
(5) Nacos 与 CAP
Nacos 在做注册中心时,默认是 AP
的。但其也支持 CP
模式,但需要用户提交请求进行转换。
Raft 算法是一种通过对 日志复制管理 来达到集群节点 一致性 的算法。 这个日志复制管理发生在集群节点中的 Leader
与 Followers
之间。 Raft 通过选举出的 Leader
节点负责管理日志复制过程,以实现各个节点间数据的一致性。
论文:https://www.usenix.org/system/files/conference/atc14/atc14-paper-ongaro.pdf
在 Raft 中,节点有三种角色:
Leader
唯一负责处理客户端 写请求 的节点;也可以处理客户端 读请求 ;同时负责日志复制工作 (读写分离)Candidate Leader
选举的候选人,其可能会成为 Leader
。是一个选举中的过程角色Follower
:可以处理客户端 读请求 ;负责同步来自于 Leader
的日志;当接收到其它 Cadidate
的投票请求后可以进行投票;当发现 Leader
挂了,其会转变为 Candidate
发起 Leader
选举。 通过 Raft
算法首先要实现集群中 Leader
的选举。
(1) 我要选举
若 follower
在心跳超时范围内没有接收到来自于 leader
的心跳,则认为 leader
挂了。此时其首先会使其本地 term
增一。然后 follower
会完成以下步骤:
candidate
的投票请求,则会将选票投给这个 candidate
follower
转变为 candidate
(2) 我要投票
follower
在接收到投票请求后,其会根据以下情况来判断是否投票:
candidate
的 term
不能小于 我的 term
term
内,我的选票还没有投出去candidate
的请求, 我将采取 first-come-first-served
方式投票(3) 等待响应
当一个 Candidate
发出投票请求后会等待其它节点的响应结果。这个响应结果可能有三种情况:
leader
。然后会将消息广播给所有其它节点,以告诉大家我是新的 Leader
了candidate
发来的新 leader
通知,比较了新 leader
的 term
并不比自己的 term
小,则自己转变为 follower
leader
通知,则重新发出选举(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
。
在 Leader
选举出来的情况下,通过 日志复制管理 实现集群中各节点数据的同步。
(1) 状态机
Raft 算法一致性的实现,是基于日志复制状态机的。 状态机的最大特征是,不同 Server
中的状态机若当前状态相同,然后接受了相同的输入,则一定会得到相同的输出。
(2) 处理流程
当 leader
接收到 client
的写操作请求后,大体会经历以下流程:
leader
在接收到 client
的写操作请求后, leader
会将数据与 term
封装为一个 box
,并随着下一次心跳发送给所有 followers
,以征求大家对该 box
的意见。同时在本地将数据封装为日志follower
在接收到来自 leader
的 box
后首先会比较该 box
的 term 与本地记录的曾接受过的 box
的最大 term
,只要不比自己的小就接受该 box
,并向 leader
回复同意。同时会将该 box
中的数据封装为日志。leader
接收到过半同意响应后,会将日志 commit
到自己的状态机,状态机会输出一个结果,同时日志状态变为了 committedleader
还会通知所有 follower
将日志 commit
到它们本地的状态机,日志状态变为了 committed
commit
通知发出的同时, leader
也会向 client
发出成功处理的响应(3) AP 支持
Log 由 term index
、 log index
及 command
构成。为了保证可用性,各个节点中的日志可以不完全相同,但 leader
会不断给 follower
发送 box
,以使各个节点的 log
最终达到相同。即 raft
算法不是强一致性的,而是 最终一致 的。
Raft集群存在 脑裂问题。在多机房部署中,由于网络连接问题,很容易形成多个分区。而多分区的形成,很容易产生脑裂,从而导致数据不一致。
由于三机房部署的 容灾能力最强 ,所以生产环境下,三机房部署是最为常见的。下面以三机房部署为例进行分析,根据机房断网情况,可以分为五种情况:
(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
则会自动下课,所以 不会形成脑裂。
(2) 情况二 —— 形成脑裂
这种情况与情况一基本是一样的。不同的是,一定会 形成脑裂,无论新 Leader
在 B 还是 C 机房。
(3) 情况三 —— 无脑裂
A 、 C 可以正常对外提供服务,但 B 无法选举出新的 Leader
。由于 B 中的主机全部变为了 选举状态,所以无法提供任何服务,没有形成脑裂。
(4) 情况四 —— 无脑裂
A 、 B 、 C 均可以对外提供服务,不受影响。
(5) 情况五 —— 无脑裂
A 机房无法处理写操作请求,但可以对外提供读服务。
B 、 C 机房由于失去了 Leader
,均会发起选举,但由于均无法获取过半支持,所以均无法选举出新的 Leader
。
(1) 请求到达前 Leader 挂了
client
发送 写操作请求 到达 Leader
之前 Leader
就挂了,因为 请求 还没有到达集群,所以这个请求对于集群来说就没有存在过, 对集群 数据的一致性 没有任何影响 。 Leader
挂了之后,会选举产生新的 Leader
。
由于 Stale Leader
并未向 client
发送成功处理响应,所以 client
会**重新发送该写操作请求**。
(2) 未开始同步数据前 Leader 挂了
client
发送 写操作请求 给 Leader
请求 到达 Leader
后, Leader
还没有开始向 Followers
发出数据 Leader
就挂了 。 这时集群会选举产生新的 Leader
。 Stale Leader
重启后会作为 Follower
重新 加入集群,并同步新 Leader
中的数据以保证数据一致性。 之前接收到 client
的数据 被丢弃。
由于 Stale Leader
并未向 client
发送成功处理响应,所以 client
会 重新发送该写操作请求。
(3) 同步完部分后 Leader 挂了
client
发送 写操作请求 给 Leader
, Leader
接收完数据后向所有 Follower
发送数据。 在部分 Follower
接收到数据后 Leader
挂了 。由于 Leader
挂了,就会发起新的 Leader
选举。
Leader
产生于 已完成数据接收 的 Follower
,其会继续将前面接收到的写操作请求转换为日志,并写入到 本地状态机,并向所有 Flollower
发出询问。在获取过半同意响应后会向所有 Followers
发送 commit
指令,同时向 client
进行响应。Leader
产生于尚未完成数据接收的 Follower
,那么原来已完成接收的 Follower
则会放弃曾接收到的数据。由于 client
没有接收到响应,所以 client
会重新发送该写操作请求。(4) commit 通知发出后 Leader 挂了
client
发送写操作请求 给 Leader
, Leader
也成功向所有 Followers
发出的 commit
指令,并向 client
发出响应后, Leader
挂了。
由于 Stale Leader
已经向 client
发送成功接收响应,且 commit
通知已经发出,说明这个写操作请求已经被 server
成功处理。
在网络上有一个关于 Raft 算法 的动画,其 非常清晰全面 地演示了 Raft 算法的工作原理。该动画的地址为:http://thesecretlivesofdata.com/raft/
Raft 算法动画演示包括以下内容:
- What is Distributed Consensus? (什么是分布式一致性?)
- Protocol Overview (协议概述)
- Leader Election (Leader 选举)
- Log Replication(日志复制)
- Other Resources(参考文献)
注:仅供学习参考,如有不足欢迎指正!