前言
我们都知道,系统中每引入一个新的中间件,都会造成可用性的降低,举个例子,本来我只需要保障应用本身高可用即可,现在又引入了中间件A,如果它宕机了怎么办?所以,伴随着这个话题,我们今天就来聊聊如果保证redis的高可用
高可用方案
为什么要用保证redis的高可用?redis单点会带来哪些问题呢?
- 单点故障问题
如果目前只有一台redis服务器在运行,发生机器故障,服务直接不可用,如果故障比较严重,磁盘损坏了,数据就损坏了,单台机器时没有办法保证数据的安全性的 - 容量瓶颈
redis是内存存储的,单机很容易受到内存容量的限制
主从复制模式
主从复制,顾名思义,分为主节点和从节点,主节点负责写,从节点负责扩展主节点的读能力,并且可以将主节点的数据同步给从节点,一旦发现主节点发生故障,从节点可以随时顶上来
主从复制模式面临的问题
- 并没有解决单点的写压力
- 主节点发生故障的时候,需要手动将从节点晋升为主节点,同时需要通知应用放更改主节点地址并重启,并且需要命令其他从节点复制新的主节点,整个过程需要人工干预
- 主节点的存储能力受到单机的限制
redis 主从复制和sentinel架构示意图如下
redis sentinel 哨兵模式
哨兵模式实际上就是在主从复制的基础上,加上了一层监控的哨兵节点。Redis-sentinel也是官方提供的高可用方案,哨兵模式是在2.6以后开始提供的,如果需要在生产上使用,尽量使用2.8之后的版本,比较稳定。哨兵模式下在主节点发生故障时,可以实现自动的主备切换,并且可以监控多个主从集群,哨兵节点本身也支持集群,毕竟哨兵只有单个节点也无法支持哨兵节点的高可用,并且如果唯一的哨兵如果宕机了,哨兵模式又变回主从复制模式了,又将无法自动的进行主备切换了。
客户端连接的是哨兵节点,只要连接任意一个哨兵节点,就可以获得redis主从集群中的信息。
哨兵节点的作用
监控
sentinel 会不间断的检查主服务器和从服务器是否正常运行
通知
当被监控的某个redis服务器如果出现问题,sentinel通过api脚本向管理员或者其他应用程序发送通知
自动故障迁移
当主节点不能正常工作时,sentinel会开始一次自动的故障迁移,它将会与失效主节点是主从关系的其中一个主节点的其中一个从节点升级为新的主节点,并且将其他的从节点指向新的主节点。
配置提供者
在redis sentinel模式下,客户端在初始化连接的是sentinel的集合,从中获取主节点的信息。
主观下线(SDOWN)
主观下线其实是指单个sentinel实例对节点做出的下线判断,默认情况下,每个sentinel会以每秒一次的频率对redis节点和其他sentinel节点发送PING命令,并通过节点的回复来判断是否在线。
主观下线适用于所有主节点和从节点。如果在down-after-milliseconds毫秒内,sentinel没有收到目标节点的有效回复(+PONG,-LOADING,-MASTERDOWN),则会判定为该节点为主观下线
客观下线(ODOWN)
客观下线是指多个sentinel实例对同一个实例进行主观下线判断。
客观下线只适用于主节点,如果哨兵节点发现主节点主观下线后,会通过is-master-down-by-addr命令向其他sentinel节点询问该节点的状态,若果超过
自动故障迁移(automatic-failover)过程
- sentinel发现主节点主观下线,修改其状态为SDOWN
- sentinel和其他sentinel节点确认master是否down掉(SENTINEL is-master-down-by-addr),确认其状态为客观下线(ODOWN),
- sentinel间进行leader选举,由被选出的leader sentinel节点来进行后续的故障切换工作,选举基于Raft协议
- sentinel节点开始进行故障切换,并选出合适的从节点作为主节点
- 对选出的从节点执行 slaveof no one将其晋升为新的主节点
- 对其余的从节点发送命令,使其变为新的主节点的从节点,并且从新的主节点复制数据
- leader sentinel继续监控已下线主节点,一旦其重新上线,就把他降级为新的主节点的从节点,并且从新的主节点中复制数据
leader哨兵节点选举
某个sentinel发现master节点主观下线后,执行以下操作
- 如果该哨兵节点没有投过票,它就成为candidate
- 如果该哨兵节点已经投过票,则在2倍的故障时间内就不会成为leader,也就是相当于一个follower
sentinel节点成为candidate后执行以下操作
- 更新故障转移状态为start
- 令当前epoch + 1,即发起新一轮的选举,在sentinel中epoch相当于Raft协议中的term
- 向其他节点发送SENTINEL is-master-down-by-addr指令,该命令包含自己的epoch
- 投自己一票,投票的方式是将自己master结构中的leader和leader_epoch改成投给的sentinel和它的epoch
其他节点收到Candidate的SENTINEL is-master-down-by-addr命令,如果收到命令的sentinel判断发现当前epoch和通过命令收到的epoch一样,证明它已经投过票了,当前epoch内该sentinel就只能成为follower
candidate会不断统计自己的票数,直到他发现认同他成为leader超过一半且超过它配置的quorum,则该candidate成为leader哨兵节点
在一个选举时间内,如果一个candidate没有获得超过一半的票数且超过quorum,则该次选举失败
如果在一个epoch内,没有一个candidate获得更多的票数,则2倍故障转移的时间后,candidate增加epoch并重新投票
master节点选举
从已经宕机的主节点的从节点中挑选一个节点作为主节点,这个主节点是按照以下规则选出来的
- slave优先级(手动配置的) 如果有配置最高的slave节点,则返回,没有则继续
- 复制偏移量最大的(对主节点数据复制的最完整的从节点),如果有则返回,没有则继续
- 启动最早的slave节点(run_id最小)
redis cluster
来聊redis cluster之前先来聊聊几个问题
水平扩展性
水平扩展性或者说给系统中添加节点的能力,对于redis来说特别重要,redis只能进行垂直扩展(给予redis进程更多的CPU或者内存),不过垂直扩展的特点也很明显,很快就会变得非常昂贵并且变得无法管理。
我们只能通过把redis中的数据进行分区,将不同分区的数据存储到不同的redis实例中,很多公司针对redis已经实现了自己的分区方案,不过大部分方案都是在客户端进行分区,也就是,数据在哪个节点进行读写是客户端来决定的,这个方法的缺点也很明显,所有的客户端都要实现这个相同的分区策略,并且连接到同一个redis实例的不同的redis客户端将会紧紧的耦合在一起。
另一种分区策略对客户端进行了解耦,就是用代理进行协助分区,Twitter就采用了这种分区策略,在这种方式下,
proxy就像一个在客户端和redis实例之间的一个单独的节点负责处理数据分区,虽然这种方式给客户端解耦,但是又引入了proxy单点故障的问题,又需要考虑proxy的高可用问题,还有一个问题就是如果将来需要添加或者删除节点的时候,又会带来数据的重新分区,如果只是用redis做缓存的话,这个问题没什么大不了的,但是如果用来做数据存储,只是改变分区策略可能会引起数据的“丢失”(原来的key值可能会被分区到别的redis实例上面去了),并且这个新的redis实例上还没有数据,调整了分区策略后必须要进行数据迁移
高可用
当在一个大型企业级应用中依赖一个数据库的时候,是不允许数据库有停机时间的,现代的数据库必须要尽可能地有容灾和容错能力,并且不需要人为干预。尽管redis实例很少发生宕机,但是也有一些异常情况会导致redis实例不可用,由于没有好的redis高可用方案,一些公司开始自行研发自己的redis高可用方案,但是大部分用户都没有高可用方案,并且一些stackoverflow上面的问答都太专业化了,普通用户根本就被抛弃了,于是redis团队立刻研发了一套高可用方案redis sentinel,这是一个官方的高可用方案,但是引入了额外的复杂度,并且这种方案并不提供主节点的水平扩展,在redis实例外还需要3台额外的机器(存放哨兵节点)。
为了解决上述问题,redis 3.0.0发布了redis cluster ,redis cluster是一个多主多从并且去中心的架构,redis cluster简而言之就是一个数据分片策略,它可以在集群运行的时候将数据重新分片到其他节点上面,并且提供了故障切换的方法可以确保系统可以应对各种各样的故障。redis cluster使用hash分区将key分配到16,384个hash槽内,集群的每一个主节点负责一部分hash槽,集群中的每一个从节点负责复制指定主节点的数据,并且可以重新分配给别的主节点或者是被选举为新的主节点,主节点接收来自客户端的读和写请求,从节点不与客户端做任何交互,只做主节点的数据备份。
图片引用自Redis Cluster集群使用与原理
参考文章
Redis哨兵(Sentinel)模式快速入门
Redis 知识汇总
An Introduction to Redis Cluster
关于Raft协议,这个网站上面有动画演示,非常容易理解,可以去了解下