Redis
单机模式
单机模式就是安装一个Redis,启动起来,调用相应的业务,一般用于无需保证高可用的场景中。
主从复制
主从复制指将一台 Redis 服务器的数据,复制到其他的 Redis 服务器。前者称为主节点,后者称为从节点;数据复制是单向的,只能从主节点复制到从节点。
既然有主从复制,那就代表master和slave的数据是一样的,有数据冗余问题。但是在程序设计上,为了保证高可用性和高性能,是允许有冗余存在的。主从模式在很多系统设计中都会考虑,一个master节点挂很多slave节点,当master节点宕机,会选举产生一个新的master节点,从而保证服务的高可用性。
主从模式的优点:
- 一旦 主节点宕机,从节点 作为 主节点 的 备份 可以随时顶上来。
- 扩展 主节点 的 读能力,分担主节点读压力。
- 高可用基石:除了上述作用以外,主从复制还是哨兵模式和集群模式能够实施的基础,因此说主从复制是Redis高可用的基石。
但这种模式也会有相应的缺点:
- 一旦 主节点宕机,从节点 晋升成 主节点,同时需要修改 应用方 的 主节点地址,还需要命令所有 从节点 去 复制 新的主节点,整个过程需要 人工干预。
- 主节点 的 写能力 受到 单机的限制。
- 主节点 的 存储能力 受到 单机的限制。
哨兵模式
刚刚提到了,主从模式,当主节点宕机之后,从节点是可以作为主节点顶上来,继续提供服务的。但是有一个问题,主节点的IP已经变动了,此时应用服务还是拿着原主节点的地址去访问,就会出现问题了。人工的干预效率低、易出错。因此在Redis 2.8版本开始引入,有了哨兵这个概念。在复制的基础上,哨兵实现了自动化的故障恢复。
如图,哨兵节点由两部分组成,哨兵节点和数据节点:
- 哨兵节点:哨兵系统由一个或多个哨兵节点组成,哨兵节点是特殊的redis节点,不存储数据。
- 数据节点:主节点和从节点都是数据节点。
访问redis集群的数据都是通过哨兵集群的,哨兵监控整个redis集群。
一旦发现redis集群出现了问题,比如刚刚说的主节点挂了,从节点会顶上来。但是主节点地址变了,这时候应用服务无感知,也不用更改访问地址,因为哨兵才是和应用服务做交互的。
Sentinel 很好的解决了故障转移,在高可用方面又上升了一个台阶,当然Sentinel还有其他功能。
比如 主节点存活检测、主从运行情况检测、主从切换。
Redis的Sentinel最小配置是 一主一从。
如果各位部署过哨兵集群的话几句知道,在配置哨兵的信息时,我们只需要用到下面的这个配置项,设置主库的IP和端口,并没有配置其他哨兵的连接信息。
sentinel monitor
节点自动发现
先看数据节点
一般情况下,哨兵节点每隔10秒(故障转移时每隔1秒)向主从节点发送INFO
命令,以此获取主从节点的信息。第一次执行时,哨兵仅知道我们给出的主节点信息,通过对主节点执行INFO
命令就可以获取其从节点列表。如此周期性执行,就可以不断发现新加入的节点。
- 如果
INFO
命令目标是从节点:哨兵从返回信息中获取从节点所属的最新主节点ip和port,如果与历史记录不一致,则执行更新;获取从节点的优先级、复制偏移量以及与主节点的链接状态并更新。 - 如果
INFO
命令目标是主节点:哨兵从返回信息中获取主节点的从机列表,如果从节点是新增的,则将其加入监控列表。 - 无论目标是主节点还是从节点,都会记录其runId。
- 如果节点的角色发生变化,哨兵会记录节点新的角色及上报时间。若此时哨兵运行在TILT模式下,则什么都不做。否则,会执行主从切换相关的逻辑,我们后面再细说。
再看哨兵节点
为了相互检查可用性及信息交互,哨兵之间是一直保持连接的,但是我们并没有显示的告知它们彼此的存在,它们之间是怎么发现对方并交互的呢?
是这样的:通过刚才的介绍,我们了解到哨兵通过INFO
命令发现了主节点及从节点的地址信息,而Redis提供了一种发布订阅的消息通信模式,即Pub/Sub,哨兵们就是通过一个约定好的通道(channel)发布/订阅hello信息进行通信。结合图示说明一下:
如上图所示:
- 每隔2秒,每个哨兵会通过它所监控的主节点、从节点向
__sentinel__:hello
通道发布一条hello消息。 - 每个哨兵会通过它所监控的主节点、从节点订阅
__sentinel__:hello
通道的消息,以此接收其他哨兵发布的信息。
故障检测
故障检测是哨兵执行故障转移的前提,在知晓需要监控的目标(主从节点)后,哨兵通过PING
命令实现对主从节点的故障检测。
哨兵以集群方式工作,官方建议至少要有三个节点,每个节点都以相同的方式对主从节点进行监控与故障检测。由于网络抖动或者网络分区,单个哨兵对节点的故障检测可能无法代表其真实的状态,为了降低误判,哨兵之间还需要对节点的故障状态进行协商。所以这里需要引入两个概念:
- 主观宕机(Subjective Down, SDOWN):是指一个哨兵实例通过检测发现某个主节点发生故障的一种状态。
- 客观宕机(Objective Down, ODOWN):是指哨兵检测到某个主节点发生故障,通过命令
SENTINEL is-master-down-by-addr
与其他哨兵节点协商,并且在指定时间内接收到指定数量的其他哨兵的确认反馈时的一种状态。
简单来说,SDOWN是哨兵自己认为节点宕机,而ODOWN是不但哨兵自己认为节点宕机,而且该哨兵与其他节点沟通后,达到一定数量的哨兵都认为节点宕机了。
主观宕机
每个Sentinel以每秒钟一次的频率,向它所有的 主服务器、从服务器 以及其他Sentinel实例 发送一个PING 命令。如果一个实例(instance)距离最后一次有效回复PING命令的时间超过 down-after-milliseconds 所指定的值,那么这个实例会被 Sentinel标记为 主观下线(SDOWN)。SDOWN状态是指在down-after-milliseconds
未收到节点的PING命令回复,如果该配置项为30秒,但是哨兵在29秒时收到节点的回复,哨兵也会认为节点是正常工作的。SDOWN无法触发故障转移,仅仅说明是一个哨兵认为节点发生故障(不可用)了,若要触发故障转移,必须达到ODOWN状态。
客观宕机
当Sentinel将一个主节点判断为主观下线之后,为了确认这个主服务器是否真的下线 了,它会向同样监视这一主服务器的其他Sentinel进行询问,看它们是否也认为主服务器已经进入了下线状态。当Sentinel从其他Sentinel那里接收到足够数量的已下线判断之后,Sentinel就会将从服务器判定为客观下线,并对主服务器执行故障转移操作。
故障转移
Sentinel判定主节点客观宕机(ODOWN)后,将进入故障转移过程。
进入故障转移过程有几个前提:主节点为客观宕机状态、当前没有故障转移在执行、上次故障转移已经超时。Sentinel确认可以执行故障转移后,会进行以下几项准备工作:
- 设置failover_state:SENTINEL_FAILOVER_STATE_WAIT_START(故障转移等待开始);
- 设置当前主节点标识位:SRI_FAILOVER_IN_PROGRESS(主节点处于故障转移过程中);
- 配置纪元加1,并以此作为故障转移的纪元;
- 记录故障转移开始时间及failover_state状态修改时间;
Sentinel Leader选举
当一个主节点被判断为客观下线时,监控这个主节点的所有Sentinel会进行协商,选举一个Leader对下线的主节点执行故障转移操作。怎么选呢?
思考一下,我们可以知道:故障检测是多个Sentinel同时执行的,也就是说可能多个Sentinel在相近的时间内都判定主节点客观宕机了,因此Leader的选举过程在Sentinel集群内可能是同步进行的。所以,Sentinel需要在集群内进行“拉票”,“拉票”的依据就是配置纪元及“拉票”的时间。配置纪元越大,优先级越高;“拉票”请求越早,优先级越高。我们来看下:
当Sentinel判断主节点客观下线后,会把自己的配置纪元加1,未检测到主节点ODOWN或检测慢的,自然落后于当前纪元;
Sentinel会使用
Sentinel is-master-down-by-addr
命令向其他所有Sentinel发起投票请求,与故障检测过程中的“询问“不同,这里的runId将被设置为当前Sentinel的runId,epoch为最新的纪元。其他Sentinel接收到“投票”请求后,执行以下过程:
- 若请求纪元大于自身配置纪元,则更新替换;若监控主节点的配置纪元小于请求纪元,则更新替换,并“投票”给发起请求的Sentinel。这个过程是抢占式的,同一纪元,先到先得。(Redis命令处理是单线程,无并发冲突)。
- 根据判断结果,回复“投票”请求:回复内容为该Sentinel选举的Leader的runId。
Sentinel接收并处理
Sentinel is-master-down-by-addr
回复:把投票结果(runId)更新到该Sentinel的节点信息中。
“投票”完成后就到了“唱票”环节,该过程是在SENTINEL_FAILOVER_STATE_WAIT_START
状态下执行的。Sentinel会遍历当前主节点下所有的Sentinel节点,把它们的投票信息进行统计;然后判断是否有Sentinel胜出。这里胜出的条件是:
- Sentinel必须获取集群内大多数Sentinel的选票,即票数大于50%(防止“脑裂“);
- Sentinel所获票数必须大于等于法定人数(quorum);
举例:监控主节点的Sentinel有5个,quorum为4,投票情况:
如果某个Sentinel的获得3票,但是3<4,该Sentinel不能被选为Leader;
如果某个Sentinel的获得4票,但是4>=4,该Sentinel可以被选为Leader;
因为Sentinel Leader的产生需要半数以上Sentinel的支持,并且每个Sentinel在每个配置纪元里面只能设置一次Leader,所以在一个配置纪元立main,只会出现一个Leader。
如果在给定时限内,没有一个Sentinel被选举为Leader,那么各个Sentinel将在一段时间后再次进行选择,直到选出Leader为止。
Sentinel Leader选举完成,设置failover_state为SENTINEL_FAILOVER_STATE_SELECT_SLAVE
。
新主节点选举
主节点已经客观宕机,Sentinel Leader会从该主节点存活的从节点中选出一个新的主节点。
首先,Sentinel Leader会按照以下条件剔除从节点:
- 主观宕机(SDOWN)或与处于断线状态的从节点;
- 最近5秒内未回复过Sentinel Leader INFO命令的从节点;
- 从节点的优先级为0的从节点,由配置项
replica-priority
决定; - 与主节点断开连接超过10倍
down-after-milliseconds
的从节点;
筛选过后,剩下的从节点都是数据比较新、与Sentinel Leader通信正常的,可以保证故障转移后最小的数据丢失。
然后,按照以下规则选择新的主节点:
- 选择
replica-priority
最低的节点。如果存在相同,则继续; - 选择复制偏移量最大的的从节点。如果存在相同,则继续;
- 选择runId最小的从节点;
如果新主节点选举失败,将等待重试。选举成功,则将此从节点提升,并设置failover_state为SENTINEL_FAILOVER_STATE_SEND_SLAVEOF_NOONE
。
配置新的主节点
选出新的主节点之后,Sentinel Leader会向它发送slaveof NO ONE
,把这个从节点转为主节点(这是在从节点自身来看的角色转换)。
从节点接收slaveof NO ONE
命令后,会重置其主节点信息,断开与其主节点、从节点的网络连接,重置其复制ID,并执行持久化重写操作。
发送命令后,Sentinel Leader会设置failover_state为SENTINEL_FAILOVER_STATE_WAIT_PROMOTION
,等待从节点角色提升。
Sentinel Leader会向它发送slaveof NO ONE
命令后,每隔一秒发送一次INFO命令(正常是10秒一次),并观察命令回复中的角色信息。当被升级的从节点的角色从原来的slave变为master时,Sentinel Leader就直到该从节点已经升级主节点了。
从节点角色提升成功,设置failover_state状态为SENTINEL_FAILOVER_STATE_RECONF_SLAVES。
配置其他从节点
新的主节点已经配置完成,接下来就是要让其他存活的从节点以该节点为主节点,然后向该节点发起主从复制。
该过程原理比较简单:遍历原主节点的从节点,向这些从节点发送slaveof <ip> <port>
命令,该部分在上一篇《Redis专题:一文搞懂主从复制原理》讲过,大家可以自行翻阅。
所有从节点配置完成后,就会修改failover_state为SENTINEL_FAILOVER_STATE_UPDATE_CONFIG
。
不过,这一过程受配置项parallel_syncs(同时执行主从复制的节点数量)的影响。由于主从复制过程中从节点数据加载阶段无法对外提供服务,所以,如果同时进行主从复制的从节点数量较多,可能会导致短时间内系统不可用。
该配置越小,从节点完成配置的时间越长;反之,时间越短。实际环境中,我们需要根据从节点的数量,系统压力,按照比例合理设置。
更新配置
故障转移过程中,新主节点是以“储君”的身份在工作,其他所有从节点切换至新的主节点后,就要正式把新主节点“立”起来了。简单来说有三步(实现方法在sentinelFailoverSwitchToPromotedSlave
,由周期函数触发):
- 重置新主节点的信息状态、清空从节点、Sentinel节点等,failover_state修改为
SENTINEL_FAILOVER_STATE_NONE
。 - 从旧主节点中迁移Sentinel节点、从节点,迁移至新的主节点中。
- 释放就主节点配置信息。
至此,故障转移工作完成。
其他问题
主从节点移除
Sentinel从不移除从节点,即使很长时间都不可用。这一点是非常有用的,因为当发生网络分区或者故障后,Sentinel需要正确的对恢复节点进行重新配置。经过故障转移,旧主节点将以从节点的角色加入集群,Sentinel会对他进行重新配置,让它从新的主节点执行主从复制。
如果需要移除故障节点,需要依次向Sentinel节点发送Sentinel Reset
命令,经过10秒,Sentinel会重新刷新它们的从节点列表,仅保存主节点INFO命令回复内容中的从节点。
脑裂问题
redis的主从模式下脑裂是指因为网络问题,导致redis主节点跟从节点和Sentinel集群处于不同的网络分区,此时因为Sentinel集群无法感知到 主节点的存在,就会将某一个从节点提升为主节点。此时就存在两个不同的主节点,就像一个大脑分裂成了两个。
集群脑裂问题中,如果客户端还在基于原来的主节点继续写入数据,那么新的主节点将无法同步这些数据,当网络问题解决之后,Sentinel 集群将原先的主节点降为从节点,此时再从新的主中同步数据,将会造成大量的数据丢失。
解决方案
min-slaves-to-write 3
min-slaves-max-lag 10
第一个参数表示连接到master的最少slave数量
第二个参数表示slave连接到master的最大延迟时间
按照上面的配置,要求至少3个slave节点,且数据复制和同步的延迟不能超过10秒,否则的话master就会拒绝写请求,配置了这两个参数之后,如果发生集群脑裂,原先的master节点接收到客户端的写入请求会拒绝,就可以减少数据同步之后的数据丢失。