8K 字详细解析实现 Redis 的高可用 (主从、哨兵、集群)
高可用有两个含义:一是数据尽量不丢失,二是保证服务尽可能可用。 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 )
如上两个配置:要求至少有 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 协议的优点在于元数据的更新比较分散,不是集中在一个地方,更新请求会陆陆续续,打到所有节点上去更新有一定的延时,降低了压力; 去中心化、可扩展、
容错、一致性收敛、简单。 由于不能保证某个时刻所有节点都收到消息,但是理论上最终所有节点都会收到消息,因此它是一个最终一致性协议。
缺点: 元数据更新有延时可能导致集群的一些操作会有一些滞后。 消息的延迟 , 消息冗余 。
评论