主从集群
主从集群搭建
redis的主从集群是一个“一主多从”的读写分离集群。集群中的master节点负责处理客户端的读写请求,而slave节点仅能处理客户端的读请求。在数据库集群中,写的操作压力一般较小,压力大多来自读操作请求。
伪集群搭建与配置
在采用单线程IO模型时,为了提高处理器的利用率,一般会在一个主机中安装多台redis,构建一个redis主从伪集群。
下面是搭建一主二从伪集群
- 复制redis.conf
- 将伪集群公有的配置放到同一个配置文件,然后再分别为各个redis服务器进行个性化的配置
- 对共有配置进行修改
- 若设置了requirepass,为了能够在主机宕机时能够连接上其他副本服务器,那么也要为masterauth设置相同的值
- 将repl-disable-tcp-nodelay属性设置为no,以实现大的tcp包传输,减少网络开销(该操作会使得数据积累到一定量后才进行传输,若系统的流量很大,或者是副本服务器跳数很多则置为yes)
- 定义个性化配置
- 在redis.conf同目录下创建3个配置文件来对应1台主服务器2台从服务器
redis6380.conf
# 导入公共配置
include redis.conf
# 指定pidfile
# 当启动redis服务器时,会生成一个唯一的进程id,并会将该pid写入到指定文件中
pidfile /var/run/redis_6380.pid
post 6380
# 配置rdb持久化文件名
dbfilename dump6380.rdb
# 配置aof持久化文件名
appendfilename appendonly6380.aof
# 配置该服务器竞争主服务器优先级,数字越小越高
replica-priority 90
redis6381.conf
# 导入公共配置
include redis.conf
# 指定pidfile
# 当启动redis服务器时,会生成一个唯一的进程id,并会将该pid写入到指定文件中
pidfile /var/run/redis_6381.pid
post 6380
# 配置rdb持久化文件名
dbfilename dump6381.rdb
# 配置aof持久化文件名
appendfilename appendonly6381.aof
# 配置该服务器竞争主服务器优先级,数字越小越高
replica-priority 80
redis6382.conf
# 导入公共配置
include redis.conf
# 指定pidfile
# 当启动redis服务器时,会生成一个唯一的进程id,并会将该pid写入到指定文件中
pidfile /var/run/redis_6381.pid
post 6380
# 配置rdb持久化文件名
dbfilename dump6381.rdb
# 配置aof持久化文件名
appendfilename appendonly6381.aof
# 配置该服务器竞争主服务器优先级,数字越小越高
replica-priority 70
- 设置主从关系
- 分别启动三台redis服务器,注意启动时指定的配置文件以及端口号
- 在从服务器执行
slaveof ip 端口号
来指定主服务器
分级管理
若redis主从集群中的slave较多时,它们的数据同步过程会对master形成较大的性能压力。此时可以对这些slave进行分级管理。
我们只需要让低级别的slave指定其slaveof的主机为上一级slave即可,以形成一个树状结构。
容灾冷处理
在Master/Slave的Redis集群中,若Master出现宕机有两种处理方式,一组是通过手工角色调整,使Slave晋升为Master的冷处理;另一种是使用哨兵模式,实现Redis集群的高可用HA即热处理。
无论master是否宕机,slave都可以通过slaveof no one 将自己由slave晋升为master。如果其原本就有下一级的slave,那么,其就直接变成这些slave的真正master。而原来的master也会失去这个原来的slave
主从复制原理
主从复制过程
- 保存master地址
- 当slave接收到slaveof指令后,slave会立即将新的master的地址保存下来
- 建立连接
- slave中维护着一个定时任务,该定时任务会尝试着与该master建立socket连接。
- 如果连接成功slave会发送ping命令进行首次通信
- 如果连接失败则会重新发送连接请求
- 如果此时接收到slaveof no one命令则会停止建立链接(自己成为master)
- 身份认证
- master在接收到slave的ping命令后对slave进行身份验证
- 若通过master向slave发送连接成功响应
- 若未通过则发送拒绝连接
- 请求同步数据
- slave向master发送全量数据同步请求
- 数据持久化
- master在接收到数据同步请求后fork出一个子进程进行数据持久化
- 发送同步数据
- 当数据持久化完毕后master再fork出一个子进程将master的持久化文件发送给slave
- 接收并写入同步数据
- slave接收到master的数据并写入到本地吃鸡化文件
- 若此时master主进程又发生了写操作则master会将数据写入到本地内存的同时还会将其写入到同步缓存
- 若此时同步数据已经发送完毕则master将同步缓存中的数据发送给slave
- 恢复内存数据
- 数据同步完毕后slave读取本地持久化文件,恢复内存数据,对外服务
- 增量接收同步数据
- 对外服务中master持续接收到写操作,会以增量的方式发送给slave
数据同步演变过程
sync同步
redis2.8以前,首次通信成功后,slave会向master发送sync数据同步请求。然后master就会将其所有数据全部发送给slave,由slave保存到其本地的持久化文件中。这个过程称为全量复制
但这里存在一个问题:在全量复制的过程中可能会出现由于网络抖动而导致复制过程中断。当网络恢复后,slave与master重新连接成功,此时slave会重新发送sync请求,然后会从开头开始全量复制。
由于全量复制非常耗时,所以期间出现网络抖动的概率很高。而中断后的从头开始不仅需要小号大量的系统资源、网络带宽,而且可能会出现长时间无法完成全量复制的情况。
psync同步
redis2.8以后全量复制采用了psync同步策略。当全量复制过程出现由于网络抖动而导致复制过程中断时,当重新连接成功之后,复制过程可以“断点续传”
为实现断点续传,系统做出了三大变化
- 复制偏移量
- 系统为每个要传送数据进行编号,该编号从0开始,每个字节一个编号。该编号称为复制偏移量。参与复制的主从节点都会维护该复制偏移量。
- 主节点复制ID
- 当master启动后会动态生成一个长度为40位的16进制字符串作为当前master的复制id,该id是在进行数据同步时slave识别master使用的。通过info replication的master_replid属性可查看该id
- 复制积压缓冲区
- 当master有连接的slave时,在master中就会创建并维护一个队列backlog,默认大小为1MB,该队列称为复制积压缓冲区。master接收到了写操作数据不仅会写入到master主存,写入到master中为每个slave配置的发送缓存,而且还会写入到复制积压缓冲区。其作用就是用于保存最近操作的数据,以备“断点续传”时做数据补偿,防止数据丢失
psync同步过程
- slave向master首次提交psync同步命令
PSYNC ? -1
(这里的?表示动态id未知)- 若当前master不支持PSYNC则master向slave进行ERR响应,告知将进行全量复制
- master向slave发送全量数据,开启全量复制
- 结束
- master向slave进行响应
FULLRESYNC <master_replid> <repl_offset>
,告知其可以进行全量复制 - master向slave发送全量数据,开启全量复制
- 若全量复制中slave与master中断且重连成功
- slave向master再次提交psync同步命令
PSYNC <master_replid><repl_offset>
- master向slave进行响应,告知其可以“断点续传”CONTINUE
- master从
<repl_offset + 1>
开始向slave进行“断点续传”
psync存在问题
在psync数据同步过程中,若slave重启,在slave内存中保存的master的动态id与续传offset都会消失,“断点续传”将无法进行,从而只能进行全量复制,导致资源浪费
psync同步改进
redis4.0对psync进行了改进,提出了“同源增量同步”策略
- 解决slave重启问题
- 直接将master的动态id写到slave的持久化文件中,使得重启后仍然能从持久化文件中得到master的动态id
- 解决slave易主问题
- 由于改进后的psync中每个slave都在本地保存了当前master的动态id,故当slave晋升成新的master后,其本地仍然存在之前master的动态id
redis6.0对同步过程提出了“无盘全量同步”与“无盘加载”,避免了耗时的io操作
- 无盘全量同步
- master的主进程fork出的子进程直接将内存中的数据发送给slave,无需经过磁盘
- 无盘加载
- slave在接收到master发来的数据后不需要将其写入到磁盘文件,而是直接写入到内存,这样slave就可以快速完成数据恢复
redis7.0对复制积压缓冲区进行了改进,让各个slave的发送缓冲区共享复制积压缓冲区。这使得复制积压缓冲区的作用,除了可以保障数据的安全性外,还作为所有slave的发送缓冲区,充分利用了复制积压缓冲区
哨兵机制实现
简介
对于Master宕机后的冷处理方案无法实现高可用性(需要手动指定master)。在redis2.6版本开始提供了高可用的解决方案:哨兵机制。在集群中再引入一个节点,该节点充当Sentinel哨兵,用于监视Master的运行状态,并在Master宕机后自动指定一个Slave作为新的Master。
对于单哨兵也存在宕机可能,故还要为哨兵也创建一个集群。
哨兵在监视master状态时,会定时向master发送心跳。当哨兵集群中有一定数量的哨兵没有接收到响应就说明master已经宕机。此时会有一个sentinel来做failover故障转移,将原理的某一个Slave晋升为master
搭建redis高可用集群
下面是搭建一主二从三哨兵伪集群,一主二从使用上小节搭建即可
搭建思路与主从服务器类似
- 复制sentinel.conf
- 修改sentinel.conf得到公共哨兵配置
- 注释掉
sentinel monitor mymaster 127.0.0.1 6379 2
- 该行配置用于指定监听的master的ip,端口号以及确定master宕机的最少哨兵数
- 由于选举新master仍然需要大多数哨兵支持,故该选项值应该大于哨兵个数的一半,以防止出现主观确认master宕机,但是不能选举出新master的情况
- 在
sentinel auth-pass
项处设置master的密码- 所有的slave和master如果有密码必须相同
- 有密码和无密码可以混合使用
- 注释掉
- 编写个性化哨兵配置
# sentienl26380.conf
# 包含公共配置文件
include sentinel.conf
# 指定动态id存放文件
pidfile /var/run/sentinel_26380.pid
# 指定端口号
port 26380
# 指定主机ip、端口号、主观master宕机最少票数
sentinel monitor mymaster 192.168.92.1 6380 2
# sentienl26381.conf
# 包含公共配置文件
include sentinel.conf
# 指定动态id存放文件
pidfile /var/run/sentinel_26381.pid
# 指定端口号
port 26381
# 指定主机ip、端口号、主观master宕机最少票数
sentinel monitor mymaster 192.168.92.1 6380 2
# sentienl26382.conf
# 包含公共配置文件
include sentinel.conf
# 指定动态id存放文件
pidfile /var/run/sentinel_26382.pid
# 指定端口号
port 26382
# 指定主机ip、端口号、主观master宕机最少票数
sentinel monitor mymaster 192.168.92.1 6380 2
- 启动master和slave,并用slaveof命令来为slave指定master(这里master是192.168.92.1 6380)
- 启动所有sentinel
redis-sentinel 配置文件名
sentinel优化配置
sentinel down-after-milliseconds
- 指定master,slave,sentinel未返回心跳响应的最小毫秒数
- 对于哨兵sentinel,它不只是给master发送心跳,它还和能连接上的所有服务器发送心跳
sentinel parallel-syncs
- 设置同时进行同步的slave个数
- 进行同步的slave对外不能提供读写服务
- 这表明着redis在同步时可以牺牲一致性来保证可用性
sentinel failover-timeout
- 故障转移的超时时间
- 若超时则以两倍该时间为新master的故障转移超时时间
- 其余slave从旧的已宕机的master转移到新master的超时时间
- 哨兵集群取消对slave晋升为master的后悔时间
- 其余slave从新的master同步数据所花费的最大时间,倘若超过了这个时间还未完成同步,则会以更大的同时进行同步slave个数进行同步
sentinel deny-scripts-reconfig
- 不能自动修改配置文件以保证程序继续进行
哨兵机制原理
三个定时任务
sentinel维护着三个定时任务以监测redis节点及其它sentinel节点状态
- info任务
- 每个sentinel节点以10秒每次的频率向redis集群中的每个节点发送info命令,以获取最新的redis拓扑结构
- 心跳任务
- 每个sentinel节点每秒都会向所有redis节点及其他sentinel节点发送一条ping命令,以检测这些节点的存活状态。该任务是判断节点在线状态的重要依据
- 发布/订阅任务
- 每个sentinel节点在启动时都会向所有redis节点订阅
__sentinel__:hello
主题的信息,当redis节点中该主题的信息发生了编号,就会立即通知到所有订阅者。 - 启动后,每个sentinel节点以每两秒一次的频率向每个redis节点发布一条
__sentinel__:hello
主题的信息,该信息是当前sentinel对每个redis节点在线状态的判断结果及当前sentinel节点信息 - 当sentinel节点接收到
__sentinel__:hello
主题信息后,就会读取并解析这些信息,然后主题完成以下三项工作- 如果发现有新的sentinel节点加入,则记录下新加入sentinel节点信息,并与其建立连接
- 如果发现有sentinel leader选举的选票信息,则执行leader选举过程
- 汇总其他sentinel节点对当前redis节点在线状态的判断结果,作为redis节点客观下线的判断依据
- 每个sentinel节点在启动时都会向所有redis节点订阅
redis节点下线判断
- 主观下线
- 每个sentinel节点每秒会向每个redis节点发送ping心跳检测,如果sentinel在down-after-milliseconds时间内没有接收到某redis节点的回复,则sentinel节点就会对该redis节点做出“下线状态”的判断。这个判断仅仅是当前sentinel节点的意见。
- 客观下线
- 当sentinel主管下线的节点是master是,该sentinel系欸但会向每个其他sentinel节点发送sentinel is-master-down-by-addr命令,以询问其对master在线状态的判断结果。这些sentinel节点在接收到命令后回想这个发问sentinel节点响应0(在线)或1(下线)。当sentinel收到超过quorum个下线判断后,就会对master做出客观下线判断
sentinel leader选举
- 当sentinel节点对master做出客观下线判断后会由sentinel leader来完成后续的故障转移。
- sentinel集群的leader选举是通过raft算法实现的(该算法后面会详细说)
- 在网络没有问题的前提下,基本上谁做出了客观下线的判断,谁就会首先发起sentinel leader的选举,谁就会当选leader
- sentinel leader选举会在故障转移发生之前进行,故障转移后leader关系会被取消
master选择算法
在进行故障转移时,sentinel leader需要从所有redis的slave节点中选择处新的master
选择master的算法为
- 过滤所有主观下线,或心跳没有响应sentinel或replica-priority值为0的redis节点
- 在剩余redis节点中选择处replica-priority(优先级数)最小的节点列表。若只有一个节点则直接返回
- 从优先级相同的节点列表中选择复制偏移量最大(同步次数最多)的节点。若只有一个节点则返回
- 从复制偏移量相同的节点列表中选择动态id最小的节点返回
故障转移的过程
- sentinel leader根据master选择算法选出一个slave节点作为新的master
- sentinel leader向新maser节点发送
slaveof no one
指令,使其晋升为master - sentinel leader向新master发送
info replication
指令,获取master动态id - sentinel leader向其余redis节点发送消息,告知它们新master的动态id
- sentinel leader向其余redis节点发送
slaveof <mastip> <masterPort>
指令,使它们成为新master的slave - sentinel leader从所有slave节点中每次选择处parallel-syncs个slave从新master同步数据,直至所有slave全部同步完毕
- 故障转移完毕
节点上线方法
- 原redis节点上线
- 直接启动即可
- 新redis节点上线
- 启动服务器
- 使用slaveof命令加入集群
- sentinel节点上线
- 需要在配置文件中修改sentinel monitor属性,指定监控的master
- (如果是之前存在的sentinel节点也需要指定,因为master可能改变过)
- 启动哨兵
CAP定理
概念
- C(一致性)
- 分布式系统中多个主机之间是否能够保持数据一致的特性。即当系统数据发生更新操作后,各个主机中的数据仍然处于一致的状态
- A(可用性)
- 系统提供的服务必须一直处于可用的状态,即对于用户的每一个请求,系统总是可以在有限的时间内对用户做出响应
- P(分区容错性)
- 分布式系统在遇到任何网络分区故障时,仍然能够保证对外提供满足一致性和可用性的服务
定理
CAP定理的内容是:对于分布式系统,网络环境相对是不可控的,出现网络分区是不可避免的,因此系统必须具备分区容错性。但系统不能同时保证一致性与可用性,即要么CP要么AP
BASE理论
BASE是基本可用、软状态、最终一致性的简写。其核心思想是:即使无法做到强一致性,但每个系统哦都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性
- 基本可用
- 指分布式系统在出现不可预知故障的时候,允许损失部分可用性
- 软状态
- 指允许系统数据存在的中间状态,并认为该中间状态的存在不会影响系统整体可用性,即允许系统主机键进行数据同步的过程存在一定延时。软状态,其实就是一种灰度状态,过渡状态
- 最终一致性
- 指经过一段时间的同步后,最终能够达到一致性。不需要实时一致性
Raft算法
概念
Raft算法是一种通过对日志复制管理来达到集群节点一致性的算法。这个日志复制管理发生在集群节点中的leader与followers之间。raft通过选举处的leader节点负责管理日志复制过程,以实现各个节点间数据的一致性
- leader
- 唯一负责处理客户端写请求的节点;也可以处理客户端读请求;同时负责日志复制工作
- candidate
- leader选举的候选人,其可能会成为leader,是一个选举中的过程角色
- follower
- 可以处理客户端读请求;负责同步来自于leader的日志;当接收到其它cadidate的投票请求后可以进行投票;当发现leader挂了,其会转变为candidate发起leader选举
leader选举
-
leader的选择的目的是要选出有大的term值和日志信息的节点
-
当一个节点给另一个节点投票过后,投票的节点会将当前投票请求继续传递给邻接节点
-
参与选举
- 当follower在心跳超时范围内没有接收到来自leader的心跳,则认为leader宕机。此时会使得本地term加1
- 若此时接收到了其它candidate的投票请求,则会将选票投给这个candidate
- 若没有接收到投票请求则自己从follower变为candidate
- 若之前尚未投票,则向自己投票
- 向其它节点发出投票请求,然后等待响应
-
参与投票
- follower在接收到投票请求后进行投票判断(以下是满足条件后投给请求方)
- 若发出投票请求的candidate的term大于等于自己的term
- 若在自己当前term内,选票还未投递
- 接收到多个candidate,采用first-come-first-served方式投票
-
等待响应
- 当一个candidate发出投票请求后会等待其它节点的响应结果。
- 收到过半选票
- 成为新的leader
- 将该消息广播给所有节点
- 接收到其它candidate发送的leader通知
- 比较新leader的term大于等于自己的term,则自己转换为follower
- 选票未过半,同时无新leader通知
- 重新发起选举
-
选举时间
- 在大多数情况下,当leader宕机后,follower几乎是同时感知到。为了避免较多candidate票数相同的情况,Raft算法采用了随机选举超时策略。
- 其会为follower随机分配一个选举发起时间(150~300ms)只有达到了这个时间的follower才能转变为candidate。
数据同步
在leader选举出来的情况下,leader通过日志复制管理实现集群中各节点数据的同步
- 状态机
- 不同server中的状态机若当前状态相同,然后接受了相同输入,则一定会得到相同输出
- 处理流程
- leader在接收到client的写操作请求后,会将数据与自己的term封装为一个box,并随着下一次心跳发送给所有followers,以获得大家对该box的意见。同时在本地将数据封装为日志
- follower在接收到来自leader的box后首先会比较该box的term与本地term,只要不必自己的term小就接受该box,并向leader回复同意。同时会将该box中的数据封装为日志。
- 当leader接收到过半同意响应后,会将日志commit到自己的状态机,状态机会输出一个结果,同时日志状态会变为committed
- 同时leader还会通知所有follower将日志commit到它们本地的状态机,日志状态变为committed
- 在commit通知发出的同时,leader也会向client发出成功处理的响应
- AP支持
- log由term index、log index 、command构成。为了确保可用性,各个节点中的日志可以不完全相同,但leader会不断给follower发送box,以使各个节点的log最终达到相同。
脑裂
raft集群存在脑裂问题,在多机房部署中,由于网络连接问题,很容易形成多个分区,从而容易产生脑裂,导致数据不一致。
要注意脑裂不是leader宕机,是网络问题引起的集群间通信故障
以下讨论的是各个机房服务器数量相近的情况(避免出现少数机房决定多数机房情况)
这里的邻接,可达等是图论术语
- leader对所有节点可达
- leader与所有其他机房邻接
- 成功容灾,leader不变
- leader不能与所有其它机房邻接
- 成功容灾,重新选举leader
- leader与所有其他机房邻接
- leader存在不可达节点
- 不可达机房节点度大于0
- 发生脑裂,新网络分区选举出新leader;
- 旧leader所在分区继续按该结论分析
- 不可达机房节点度等于0
- 发生脑裂,度为0的机房节点自身成为单独集群
- 旧leader所在图继续按照该结论分析
- 不可达机房节点度大于0
leader宕机处理
- 写请求到达前leader宕机
- 由于请求还未到达集群,故该请求会被集群忽视,对集群数据一致性影响
- 集群内部会选举产生新leader
- 由于stale leader 未发送成功处理响应,client会重新发送写请求
- 未开始发送同步数据前leader宕机
- 集群内部选举产生新leader
- stale leader重启完成后会作为follower重新加入集群,并同步新leader数据。之前接收到的client写请求数据被丢弃
- 由于stale leader 未发送成功处理响应,client会重新发送写请求
- 同步数据过程中leader宕机
- 集群内部选举产生新leader
- 若leader产生于已完成数据接收的follower,其会继续将前面接收到的写操作请求转换为日志,并写入到本地状态机,并向所有follower发出询问。在获取过半同意响应后会向所有followers发送commit指令,同时向client进行响应
- 若leader产生于尚未完成数据接收的follower那么原来已完成接收的follower则会放弃曾经接收到的数据,由于client没有接收到响应,故会重新发送写请求
- commit通知发送后leader宕机
- 由于stale leader已向client发送成功接收响应,且commit通知已经发出,说明该写操作请求已经被server成功处理