高可用有两个含义:一是数据尽量不丢失,二是保证服务尽可能可用。 AOF 和 RDB 数据持久化保证了数据尽量不丢失,那么多节点来保证服务尽可能提供服务。
一般在实际生产中,服务不会部署成单节点,主要是有三个原因.
- 容易出现单点故障,导致服务不可用
- 单节点处理所有的请求,吞吐量有限
- 单节点容量有限
为了实现高可用,通常的做法是,将数据库复制多个副本以部署在不同的服务器上,其中一台挂了也可以继续提供服务。Redis 实现高可用有三种部署模式:主从模式,哨兵模式,
集群模式。
一、主从模式
既然一台服务宕机了会导致提供不可用,那是不是可以考虑多台就可以解决了。Redis 提供了主从模式。通过主从复制,将数据冗余一份复制到其他 Redis 服务器。
Master节点,负责读写操作,Slave节点,只负责读操作。
1、主从复制原理
主从模式采用了读写分离,所有数据的写操作只会在Master库上进行,Master库有了最新的数据后,会同步给Slave库,这样,主从库的数据就是一致的。
这里要思考是主从库同步是如何完成的?Master库数据是一次性传给Slave库,还是分批同步的?正常运行中又怎么同步呢?要是主从库间的网络断连了,重新连接后需要再次全量同步还是只需部分同步呢?
主从复制包括全量复制,增量复制两种。redis2.8版本之后还支持部分同步。
(1) 全量同步
一般当Slave第一次启动连接Master,认为是第一次连接,就采用全量复制,全量复制流程如下:
完成上面几个步骤后就完成了Salve节点初始化的所有操作,Slave服务器此时可以接收来自用户的读请求。
redis2.8版本之后,已经使用psync来替代sync,因为sync命令非常消耗系统资源,而且不支持部分同步,psync的效率更高,支持部分同步,有关部分同步下面细说。
(2) 增量同步
Redis增量复制是指Slave初始化后开始正常工作时,Master服务器发生的写操作同步到Slave服务器的过程。
增量复制的过程主要是Master服务器每执行一个写命令就会向从服务器发送相同的写命令,从服务器接收并执行收到的写命令。
(3) 部分同步
在redis 2.8版本之前,并不支持部分同步,当主从服务器之间的连接断掉之后,Master服务器和Slave服务器之间必须进行全量数据同步,此时Slave服务器会清空所有数据,
再次加载Master的RDB文件。但是从redis 2.8开始,即使主从连接中途断掉,也不一定需要进行全量同步,它可以支持部分同步,来提高效率。
它的工作原理大致是这样的:
通过这个图我们再来理解下 为什么2.8部分可以实现部分同步
- Slave节点根据当前状态,发送 psync 命令给 Master节点:
- 如果Slave节点从未执行过 replicaof,则Slave节点发送 psync ? -1,向Master节点发送全量复制请求;
- 如果Slave节点之前执行过 replicaof 则发送 psync <runID> <offset>, runID 是上次复制保存的Master节点 runID,offset 是上次复制截至时Slave节点保存的复制偏移量。
- Master节点根据接受到的psync命令和当前服务器状态,决定执行全量复制还是部分复制:
- runID 与Slave节点发送的 runID 相同,且Slave节点发送的 slave_repl_offset 之后的数据在 repl_backlog_buffer 缓冲区中都存在,则回复 CONTINUE,表示将进行
部分复制,Slave节点等待Master节点发送其缺少的数据即可;
- runID 与Slave节点发送的 runID 不同,或者Slave节点发送的 slave_repl_offset 之后的数据已不在Master节点的 repl_backlog_buffer缓冲区中 (在队列中被挤出来了),
则回复Slave节点 FULLRESYNC <runid> <offset>,表示要进行全量复制,其中 runID 表示Master节点当前的 runID,offset 表示Master节点当前的 offset,Slave节点
保存这两个值,以备使用。
一个Slave库如果和Master库断连时间过长,造成它在Master库 repl_backlog_buffer的 slave_repl_offset 位置上的数据已经被覆盖掉了,此时Slave库和Master库间将进行
全量复制。
2、主从模式的优缺点
优点
- 做到读写分离,提高服务器性能。Salve可以分载Master的读操作压力,当然写服务依然必须由Master来完成;
- 当Master节点服务挂了,可以让Slave变成Master节点继续提供服务;
缺点
- 在主从模式中,一旦Master节点由于故障不能提供服务,需要人工将Slave节点晋升为Master节点,同时还要通知应用方更新Master节点地址。显然,大多数业务场景都不能接受这种故障处理方式;
- redis的Master节点和Slave节点中的数据是一样的,降低的内存的可用性,而且存储能力也有限。
- 主从复制写还都是在Master节点,所以写的压力并没有减少。
因此,主从复制其实并不能满足我们高可用的要求。
二、哨兵模式
在主从模式中,一旦Master节点由于故障不能提供服务,需要人工将Slave节点晋升为Master节点。显然,大多数业务场景都不能接受这种故障处理方式。Redis从2.8开始正式提供了
Redis Sentinel(哨兵)架构来解决这个问题。
哨兵模式,由一个或多个Sentinel实例组成的Sentinel系统,它可以监视所有的Master节点和Slave节点,并在被监视的Master节点进入下线状态时,自动将下线Master服务器
属下的某个Slave节点升级为新的Master节点。但是呢,一个哨兵进程对Redis节点进行监控,就可能会出现问题(单点问题),因此,可以使用多个哨兵来进行监控Redis节点,
并且各个哨兵之间还会进行监控。
sentinel是一种特殊的redis实例,它不存储数据,只对集群进行监控。
简单来说,哨兵模式就三个作用:
- 通过发送命令,等待Redis服务器返回监控其运行状态,包括Master服务器和Slave服务器;
- 当哨兵监测到Master节点宕机,会自动将Slaver节点切换成Master节点,然后通过发布订阅模式通知其他的Slave节点,修改配置文件,让它们切换主机;
- 如果是只有一个哨兵对进程对Redis服务器进行监控,也可能会出现问题,为此,我们可以使用多个哨兵进行监控。它们之间还会相互监控,从而达到高可用。
1、哨兵主要工作任务
哨兵主要有三个定时监控任务完成对各节点的发现和监控。
任务1:每个哨兵节点每 10 秒会向Master节点和Slave节点发送 info 命令获取最拓扑结构图,哨兵配置时只要配置对Master节点的监控即可,通过向Master节点发送info,
获取Slave节点的信息,并当有新的Slave节点加入时可以马上感知到
任务2,每个哨兵节点每隔 2 秒会向redis 数据节点的指定频道上发送该哨兵节点对于Master节点的判断以及当前哨兵节点的信息,同时每个哨兵节点也会订阅该频道,来了解
其它哨兵节点的信息及对Master节点的判断,其实就是通过消息publish 和subscribe 来完成的;
任务3,每隔 1 秒每个哨兵会向Master节点、Slave节点及其余哨兵节点发送一次ping 命令做一次心跳检测,这个也是哨兵用来判断节点是否正常的重要依据
2、哨兵发现服务下线
这里可以分为 哨兵主观下线 和 哨兵客观下线
哨兵主观下线
上面说过哨兵节点每隔 1 秒对Master节点和Slave节点、其它哨兵节点发送 ping 做心跳检测,当这些心跳检测时间超过 down-after-milliseconds 时,哨兵节点则认为
该节点 错误或下线,这叫主观下线;
当然这但不代表这个master真的不能用(有可能网络波动导致该哨兵与服务连接异常),所以主观下线是不可靠的,可能存在误判。
哨兵客观下线
当主观下线的节点是Mater节点时,此时该哨兵 节点会通过指令
sentinelis-masterdown-by-addr 寻求其它哨兵节点对Master节点的判断,当超过quorum(法定人数)
个数,此时哨兵节点则认为该Master节点确实有问题,这样就客观下线了,大部分哨兵节点都同意下线操作,也就说是客观下线
3、自动故障转移机制
如果哨兵客观下线某Master,那是不是接下来要选举新的Master,这个工作只要一个哨兵完成即可,所以首先要做的是选举一个哨兵领导者。
1) 领导者选举
原因:只有一个sentinel节点完成故障转移所以需要选举。 选举通过
sentinelis-master-down-by-addr命令希望成为领导者:
- 每个做主观下线的Sentinel节点向其他Sentinel节点发送命令,要求将它设置为领导者
- 收到命令的Sentinel节点如果没有同意通过其他Sentinel节点发送的命令,那么将同意该请求,否则拒绝
- 如果该Sentinel节点发现自己的票数已经超过Sentinel集合半数且超过quorum,则将成为领导者
- 如果此过程有多个Sentinel节点成为了领导者,那么将等待一段时间重新选举
2) 在从节点中选择新的Master节点
sentinel状态数据结构中保存了主服务的所有从服务信息,领头sentinel按照如下的规则从从服务列表中挑选出新的主服务
- 过滤掉主观下线的节点
- 选择slave-priority最高的节点,如果由则返回没有就继续选择
- 选择出复制偏移量最大的系节点,因为复制偏移量越大则数据复制的越完整,如果由就返回了,没有就继续
- 选择run_id最小的节点
3) 更新主从状态
通过slaveof no one命令,让选出来的Slave节点成为Master节点;并通过slaveof命令让其他节点成为其Slave节点。
将已下线的Master节点设置成新的Master节点的Slave节点,当其回复正常时,复制新的Master节点,变成新的Master节点的Slave节点
4、脑裂导致数据丢失
1) 什么是脑裂
脑裂 也就是说,某个 Master 所在机器突然脱离了正常的网络,跟其他 slave 机器不能连接,但是实际上 master 还运行着。此时哨兵可能就会认为 master 宕机了,然后开启
选举,将其他 slave 切换成了 master。这个时候,集群里就会有两个 Master ,也就是所谓的脑裂。
此时虽然某个 slave 被切换成了 master,但是可能 client 还没来得及切换到新的 master,还继续向旧 master 写数据。因此旧 master 再次恢复的时候,会被作为一个 slave
挂到新的 master 上去,自己的数据会清空,重新从新的 master 复制数据。而新的 master 并没有后来 client 写入的数据,因此,这部分数据也就丢失了。
2) 如果解决脑裂问题
Redis 已经提供了两个配置项来限制Master库的请求处理,分别是 min-slaves-to-write 和 min-slaves-max-lag。 (2.8以后改为min-replicas-to-write 和 min-replicas-max-lag )
min-slaves-to-write 1
min-slaves-max-lag 10
如上两个配置:要求至少有 1 个 slave,数据复制和同步的延迟不能超过 10 秒,如果超过 1 个 slave,数据复制和同步的延迟都超过了 10 秒钟,那么这个时候,master 就
不会再接收任何请求了。
这样一配置的话,就算你的Master库是假故障,那它在假故障期间也无法响应哨兵心跳,也不能和Slave库进行同步,自然也就无法和Slave库进行 ACK 确认了。原Master库就
会被限制接收客户端请求,客户端也就不能在原Master库中写入新数据了。
当然这个配置做不到让数据一点也不丢失,而是让数据尽可能的少丢失。
5、哨兵模式的优缺点
优点
- 哨兵模式是基于主从模式的,所有主从的优点,哨兵模式都具有。
- 主从可以自动切换,系统更健壮,可用性更高。
缺点
- 具有主从模式的缺点,每台机器上的数据是一样的,内存的可用性较低。
- 还要多维护一套哨兵模式,实现起来也变的更加复杂增加维护成本。
- Redis较难支持在线扩容,在集群容量达到上限时在线扩容会变得很复杂。
三、Cluster集群模式
先说一个误区:Redis的集群模式本身没有使用一致性hash算法,而是使用slots插槽。因为我查了很多资料都是这么说的,至于为什么使用slots插槽,我个人理解slots插槽多少
个是固定的,这样更加方便数据迁移。
哨兵模式基于主从模式,实现读写分离,它还可以自动切换,系统可用性更高。但是它每个节点存储的数据是一样的,浪费内存。因此,在Redis3.0后Cluster集群应运而生,
它实现了Redis的分布式存储。对数据进行分片,也就是说每台Redis节点上存储不同的内容,来解决在线扩容的问题。
一个Redis Cluster由多个Redis节点构成,节点组内部分为主备两类节点,对应master和slave节点。两者数据准实时一致,通过异步化的主备复制机制来保证。
一个节点组有且只有一个master节点,同时可以有0到多个slave节点,在这个节点组中只有master节点对用户提供些服务,读服务可以由master或者slave提供。如上图中,
包含三个master节点以及三个master对应的slave节点,一般一组集群至少要6个节点才能保证完整的高可用。
其中三个master会分配不同的slot(表示数据分片区间),当master出现故障时,slave会自动选举成为master顶替Master节点继续提供服务。
1、集群的一些特点
- redis cluster模式采用了无中心节点的方式来实现,每个Master节点都会与其它Master节点保持连接。节点间通过gossip协议交换彼此的信息,同时每个Master节点又有
一个或多个Slave节点;
- 客户端连接集群时,直接与redis集群的每个Master节点连接,根据hash算法取模将key存储在不同的哈希槽上;
- 在集群中采用数据分片的方式,将redis集群分为16384个哈希槽。如下图所示,这些哈希槽分别存储于三个Master节点中:
- Master1负责0~5460号哈希槽
- Master2负责5461~10922号哈希槽
- Master3负责10922~16383号哈希槽
- 每个节点会保存一份数据分布表,节点会将自己的slot信息发送给其他节点,节点间不停的传递数据分布表;
2、Master节点故障处理方式
redis 集群中Master节点故障处理方式与哨兵模式较为相像,当约定时间内某节点无法与集群中的另一个节点顺利完成ping消息通信时,则将该节点标记为主观下线状态,同时
将这个信息向整个集群广播。
如果一个节点收到某个节点失联的数量达到了集群的大多数时,那么将该节点标记为客观下线状态,并向集群广播下线节点的fail消息。然后立即对该故障节点进行主从切换。
等到原来的Master节点恢复后,会自动成为新Master节点的Slave节点。如果Master节点没有Slave节点,那么当它发生故障时,集群就将处于不可用状态。
3、扩容问题
在cluster中我们如何动态上线某个节点呢。当集群中加入某个节点时,哈希槽又是如何来进行分配的?当集群中加入新节点时,会与集群中的某个节点进行握手,该节点会把集群
内的其它节点信息通过gossip协议发送给新节点,新节点与这些节点完成握手后加入到集群中。
然后集群中的节点会各取一部分哈希槽分配给新节点,如下图:
- Master1负责1365-5460
- Master2负责6827-10922
- Master3负责12288-16383
- Master4负责0-1364,5461-6826,10923-12287
当集群中要删除节点时,只需要将节点中的所有哈希槽移动到其它节点,然后再移除空白(不包含任何哈希槽)的节点就可以了。
4、关于 gossip协议
有关gossip协议这里需要单独解释下
在整个redis cluster架构中,如果出现以下情况
- 新加入节点
- slot迁移
- 节点宕机
- slave选举成为master
我们希望这些变化能够让整个集群中的每个节点都能够尽快发现,传播到整个集群并且集群中所有节点达成一致,那么各个节点之间就需要相互连通并且携带相关状态数据进行
传播,按照正常的逻辑是采用广播的方式想集群中的所有节点发送消息,有点是集群中的数据同步较快,但是每条消息都需要发送给所有节点,对CPU和带宽的消耗过大,所以
这里采用了gossip协议。
Gossip protocol 也叫 Epidemic Protocol(流行病协议),别名很多比如:“流言算法”、“疫情传播算法”等。
它的特点是,在节点数量有限的网络中,每个节点都会“随机”(不是真正随机,而是根据规则选择通信节点)与部分节点通信,经过一番杂乱无章的通信后,每个节点的状态在一定
时间内会达成一致,如下图所示。
5、gossip的优缺点
1、Gossip 是周期性的散播消息,把周期限定为 1 秒
2、被感染节点随机选择 k 个邻接节点(fan-out)散播消息,这里把 fan-out 设置为 3,每次最多往 3 个节点散播。
3、每次散播消息都选择尚未发送过的节点进行散播
4、收到消息的节点不再往发送节点散播,比如 A -> B,那么 B 进行散播的时候,不再发给 A。
这里一共有 16 个节点,节点 1 为初始被感染节点,通过 Gossip 过程,最终所有节点都被感染:
gossip协议包含多种消息,包括ping,pong,meet,fail等等。
ping:每个节点都会频繁给其他节点发送ping,其中包含自己的状态还有自己维护的集群元数据,互相通过ping交换元数据;
pong: 返回ping和meet,包含自己的状态和其他信息,也可以用于信息广播和更新;
fail: 某个节点判断另一个节点fail之后,就发送fail给其他节点,通知其他节点,指定的节点宕机了。
meet:某个节点发送meet给新加入的节点,让新节点加入集群中,然后新节点就会开始与其他节点进行通信,不需要发送形成网络的所需的所有CLUSTER MEET命令。
发送CLUSTER MEET消息以便每个节点能够达到其他每个节点只需通过一条已知的节点链就够了。由于在心跳包中会交换gossip信息,将会创建节点间缺失的链接。
5、gossip的优缺点
优点: gossip协议的优点在于元数据的更新比较分散,不是集中在一个地方,更新请求会陆陆续续,打到所有节点上去更新有一定的延时,降低了压力; 去中心化、可扩展、
容错、一致性收敛、简单。 由于不能保证某个时刻所有节点都收到消息,但是理论上最终所有节点都会收到消息,因此它是一个最终一致性协议。
缺点: 元数据更新有延时可能导致集群的一些操作会有一些滞后。 消息的延迟 , 消息冗余 。