redis-高可用架构集解析

目标:

1、掌握redis主从复制的配置和原理

2、掌握redis哨兵机制的原理和实战

3、掌握redis分布式的各种方案对比,包括客户端sharding、代理Proxy、和redis cluster

为什么要分布式部署redis?

在分布式环境下,任何组件都要保证以下三点

  • 性能
  • 高可用
  • 可扩展

要做到以上三点需要依赖两种关键技术,一种是分片,一种是冗余。

分片就是把所有数据拆分到多个节点存储。

冗余就是在每个节点都有一个或者多个副本。

那么redis必须要提供数据分片和主从复制的功能,副本有不同的角色,如果主节点发生故障,则把某个从节点自动升级为主节点

所以本文主要围绕三点进行讨论:

1、主从复制怎么实现

2、主从切换怎么实现

3、数据分片怎么实现

1、redis主从复制

跟kafka、rocketmq等分布式组件一样,redis支持集群架构,集群的节点有主从之分,主节点叫master,从节点叫slave。slave会通过复制技术,自动同步master的数据

1.1 主从复制配置

redis的主从复制配置非常简单,有三种方式,

第一种:只需要在从节点的配置文件redis.conf中添加一行配置:

1
replicaof ip port

从节点启动之后,就会自动的连接到master节点开始同步数据。

如果master节点宕机了,选举出了新的master,这个配置在内存中会被重写。

第二种方式,就是在启动redis服务时通过参数指定master节点:

1
./redis-server --slaveof ip port

一个正在运行中的节点,也可以变成其他节点的从节点,这就是第三种方式:在客户端执行命令

1
slaveof ip port

需要注意的是,一个从节点也可以是其他从节点的主节点,形成级联复制的关系。

通过以下命令查看集群状态:

1
redis> info replication

从节点是只读的,不能执行写操作,执行写命令会报错:

1
(error)READONLY

在主节点写入后,slave会自动从master同步数据

从节点也可以改变从属关系,自己单飞

将redis.conf中的replica of配置去掉并重启,或者在客户端直接断开复制:

1
redis> slaveof no one

MySql的主从复制原理我们是清楚地,依赖binglog文件,然后还有几个线程。那么redis呢?

接下来研究主从复制到底是怎么实现的

1.2 主从复制原理

主从复制分为两类:全量复制 和 增量复制。

全量复制就是一个从节点第一次连接到主节点,需同步全部的数据;

增量复制就是从节点网络断开了或者宕机了,重启之后缺了一部分数据需要同步。

1.2.1 连接阶段

​ 1、slave节点启动时,或者执行slaveof命令是,会在自己本地保存master节点的信息,包括master 节点的host和port

​ 2、slave节点内部有个定时任务replicationCron,每隔1秒检查是否有新的master节点要连接和复制,见源码replication.c 3132line

​ 如果发现有master节点,就跟master节点建立连接,如果连接成功,从节点就为连接,为了让主节点感知到slave节点的存活,从节点会定时给主节点发送ping请求

​ 建立连接之后就可以同步数据了,这里分为两个阶段

1.2.2 数据同步阶段

​ 如果是新加入的slave节点,那就需要全量复制。master通过bgsave命令在本地生成一份RDB快照文件,将快照发给slave节点(如果超时会重连,可以调大repl-timeout的值)

​ 问题:如果slave节点自己本来有数据怎么办?

​ slave节点首先需要清除自己的旧数据,然后用RDB文件加载数据。

​ 问题:master节点生成RDB期间,接收到的写命令怎么处理?

​ 开始生成快照文件是,master会把所有的新命令缓存在内存中。在slave节点保存了RDB之后,再将新的写命令复制给slave节点(跟AOF重写rewrite期间接收到的命令处理思路是一样的)

​ 第一次全量同步完了,主从已经保持一致了,后面的就是持续把接收到的命令发送给slave节点。

1.2.3 命令传播阶段

​ 主节点持续吧命令异步复制给从节点。

​ 总结起来很简单,前面用RDB文件,后面把命令发给slave节点,就实现了主从复制。

​ 注意:一般情况下我们不会用redis做读写分离,因为redis的吞吐量已经够高了,做集群分片之后并发问题更少,所以不需要考虑主从延迟的问题

​ 与Mysql一样,主从复制延迟是不可避免的,只能通过优化网络来改善

​ 第二种情况:增量复制

​ slave通过master_repl_offset记录的偏移量,来记录上次复制到哪里,用命令

1
redis>info replication

可以查看到这个偏移量属性。

redis从2.8.18版本开始支持无盘复制

1
repl-diskless-sync=no

为了降低主节点的磁盘开销,Redis支持无盘复制,master生成的RDB文件不保存到磁盘而是直接通过网络发送给从节点。无盘复制适用于主节点所在的机器磁盘性能较差但是带宽比较充裕的的场景。

1.3 主从复制的不足

redis主从复制解决了数据备份和一部分性能的问题,但是没有解决高可用的问题,在一主一从或者一主多从的情况下,如果主服务器挂了,对外提供的服务就不可用了,单点问题没有得到解决。

如果每次都是手动把之前的从服务切换成主服务器,然后把剩余节点设置成它的从节点,太费时费力,还会造成一定时间服务器不可用。

2、可用性保证之哨兵Sentinel

配置和使用:https://www.jianshu.com/p/06ab9daf921d

2.1 Sentinel原理

怎么实现高可用呢?

对于服务起来说,能够实现主从自动切换

对于客户端来说,能够获取最新的master

要实现以上两点,我们就必须引入一个第三方来管理redis节点的存活状态,而且具备路由功能,例如rocketmq的nameserver

思路:

创建一台监控服务器监控所有的redis服务节点的状态,比如,master节点超过一定时间没有给监控服务发送心跳报文,就将master下线,然后把某一个slave提升为master。应用每一次都是从这个监控服务器拿到master地址的。

问题:如果监控服务器出问题怎么办,那还要创建一个监控服务器来监控监控服务器吗。。。这似乎陷入了死循环。

redis的高可用是通过哨兵Sentinel来保证的。它的思路就是通过运行监控服务器来保证服务的可用性。

从redis2.8开始,redis就提供了一个稳定版本的Sentinel来解决高可用问题。

我们一般会启动奇数个的Sentinel服务

用脚本启动

1
./redis-sentinel ../sentinel.con

或者

1
./redis-server ../sentinel.con --sentinel

它本质上是一个运行在特殊模式下的redis。Sentinel通过info命令得到被监听Redis机器的master,slave信息。

为了保证可用性,会对Sentinel做集群部署。Sentinel既监控所有的Redis服务,Sentinel之间也会互相监控。

注意:Sentinel本身没有主从之分

问题:Sentinel之间唯一的联系就是他们监控相同的redis节点,那他们怎么知道相互之间存在的?

答案是使用redis的发布订阅功能:

Sentinel上线时,给所有的Redis节点的__sentinel__:hello频道(channel)发送消息;

每个Sentinel节点都订阅了所有redis节点的__sentinel__:hello频道,所以能互相感知对方的存在。

2.1.1 服务下线

问题:Sentinel如何感知master下线了?

Sentinel默认每秒1次的频率向Redis服务节点发送Ping命令,如果指定时间没有收到回复(默认30秒。可配置)Sentinel将该节点标记为下线(主观下线)

时间配置:

1
sentinel down-after-milliseconds <master-name> <milliseconds>

但是,自己发现master下线不能代表真的下线,万一是你自己网络问题呢,这时候Sentinel会询问其他的Sentinel节点,确认是否真的下线,如果多数Sentinel节点都认为该master下线了,那才真的下线(客观下线)

确认下线后,要重新选举master

2.1.2 故障转移

redis的选举和故障转移都是由Sentinel完成的。

问题:有这么多的Sentinel节点,谁来做呢?

故障转移的第一步就是在Sentinel集群选择一个Leader,由Leader完成故障转移流程、Sentinel通过Raft算法,实现leader选举。

问题:那么Sentinel leader又是根据什么来选举redis的呢?

对于所有的slave节点,一共有四个因素会影响选举的结果,分别是断开连接时长,优先级排序,复制数量和进程id

  1. 如果节点与哨兵连接断开的比较久,超过某个阈值,直接失去选举权
  2. 有选举权,再看谁的优先级高,这个在配置文件中可以配置(replica-priority 100),数字越小优先级越大
  3. 优先级相同,比较谁的偏移量大
  4. 最后再比较谁的进程id小

master节点确定之后,要将其他的节点设置为他的从节点,

  1. Sentinel leader向master节点发送命令 slaveof no one
  2. 再向其他节点发送slaveof ip port,让他成为主节点的从节点,故障转移完成。

2.2 Sentinel功能总结

监控:Sentinel会不断检查节点是否正常运行

通知:若某节点被监控到出问题,Sentinel会通过api发出通知

自动故障转移

配置管理:客户端只需要连接到Sentinel集群,就能获取当前redis集群的master

哨兵机制的不足:

  1. 主从切换过程会丢失数据,因为只有一个master
  2. 只能单点写,没有解决水平扩容

如果数据量非常大,这时候就要对redis进行数据分片

这时候我们需要多个master-slave的group ,把数据分布到不同的group中。

问题:怎么分片?分片后怎么路由?

3、redis数据分片方案

三种方案:

第一种:在客户端实现相关分片逻辑,例如用取模或者一致性哈希对key进行分片,查询和修改都先判断key路由

第二种:把做分片处理的逻辑抽取出来,运行一个独立的代理服务,客户端连接到这个代理服务,代理服务做请求的转发。

第三种:基于服务端实现。

3.1 客户端分片

​ Jedis客户端中就支持分片功能,Jedis是Spring Boot 2.X版本之前默认的Redis客户端,RedisTemplate就是对Jedis的封装

3.1.1 ShardedJedis
3.1.2 一致性hash算法

把所有的哈希值空间组织成一个虚拟的圆环,整个空间按顺时针方向组织。因为是环形空间,0和2^32-1是重叠的。

每个节点在哈希环上都有一个位置,

先对key进行hash,根据hash值落在环上的位置按照顺时针的方向找,找到第一个节点就将该key存入。

虚拟节点–解决节点分布不均匀的问题

jedis对一致性hash的实现:

1
2


3.2 代理分片

典型的代理分区方案有:

Twitter开源的Twemproxy

豌豆荚开源的codis

3.2.1 Twemproxy

优点:比较稳定,可用性高

不足:

  1. 出现故障不能自动转移,架构复杂,需要借助其他组件实现高可用(LVS/HAProxy + Keepalived)
  2. 扩缩绒需要修改配置,不能实现平滑的扩缩绒
3.2.2 codis

​ Codis是一个代理中间件,豌豆荚用GO语言开发,跟Mycat是一类的组件。

​ 功能:客户端连接Codis和连接Redis没有区别

​ 分片原理:Codis把所有的key分成了N个槽(例如1024),每个槽对应一个分组,一个分组对应一个或者一组Redis实例。Codis对key进行CRC32运算,得到一个32位的数字,然后模以N,得到余数,这个余数就是key对应的槽,槽能对应道redis节点,跟Mycat的先取模后范围思想类似。

​ Codis的槽位映射关系是保存在proxy中的,如果要解决单点故障问题,Codis也要组集群部署,多个Codis节点要同步槽和节点映射关系需要运行一个zookeeper。

​ 再新增节点的时候,可以为节点指定特定的槽位,Codis也提供了自动均衡策略。

​ Codis不支持事务,其他一些命令也不支持

​ 获取数据原理:以mget为例,在各个节点中获取到符合的key,然后在汇总到Codis中返回给客户端

​ Codis是第三方提供的分布式解决方案,在官网的集群功能稳定前,Codis也得到了大量的应用。

3.3 redis cluster

​ Redis Cluster是在Redis3.0的版本正式推出的,用来解决分布式的需求,同时也可以实现高可用。跟Codis不一样,它是去中心化的,客户端可以连接到任意一个可用的节点。

​ 数据分片需要解决的问题:

- 数据怎么相对均匀的分片
- 客户端怎么访问到相应的节点
- 重新分片的过程,怎么保证正常服务
3.3.1 架构

Redis Cluster 可以看成由多个节点组成的数据集合。客户端不需要关注数据的子集到底存在哪个节点,只需要关注这个集合整体。

3.3.2 搭建
3.3.3 数据分布

使用虚拟槽位实现数据的分片

Redis创建了16384个槽(slot),每个redis group负责一定区间的slot

redis-cluster架构

对象分布到Redis节点上时,对Key用CRC16算法计算再模以16384,得到slot值,数据落到负责该槽位的节点上。

集群的每个master节点都会维护自己负责的slot,用一个bit序列实现,比如:序列的第0位是1,代表第一个sloat由他负责,为0表示不负责。

查看key属于哪个slot:

1
redis>cluster keyslot zzk

问题:如何让相关的数据落到一个节点上?

比如有些multi key操作是不能跨节点的,

在key里面加入{hash tag}即可。redis在计算slot时只会获取{}之间的字符串进行slot计算,

3.3.4 客户端重定向

问题:客户端连接到那一台服务器?访问的数据不再当前节点上怎么办?

jedis等客户端会在本地维护一份slot–node的映射关系,大部分时候不需要重定向,所以叫smart jedis(需要客户端支持)

3.3.5 数据迁移

问题:新增或下线了master节点,数据如何迁移?

添加新节点:

1
redis-cli --cluster add-node localip localport  newip newport

新增的节点没有hash槽,不能分配数据,在原来任意一个节点上执行

1
redis-cli --cluster reshard localip localport

输入需要分配的哈希槽的数量和哈希槽的来源节点

3.3.6 高可用和主从切换原理

当slave发现自己的master变为FAIL状态时,会尝试进行Failover,以期成为新的master。由于挂掉的master可能有多个slave,从而存在slave竞争上岗的过程:

  1. slave发现自己的master变为Fail
  2. 将自己记录的集群currentEpoch+1,并广播FAILOVER_AUTH_REQUEST信息
  3. 其他节点收到该信息,只有master(其他group的master)响应,判断请求者的合法性,并发送FAILOVER_AUTH_ACK,,对每个epoch只发送一个一次ack
  4. 尝试failover的slaver收集FAILOVER_AUTH_ACK
  5. 超过半数后变成新的master
  6. 广播PONG通知其他集群节点。

总结:Redis Cluster既能实现主从角色分配,又能实现主从切换,相当于继承了Replication和Sentinel的功能

3.3.7 总结

redis cluster特点:

  1. 无中心架构
  2. 数据按照slot存储分布在多个节点,节点间数据共享,可动态调整数据分布。
  3. 可扩展性,可线性扩展到1000个节点,官方推荐不超过1000个,节点可动态添加或删除
  4. 高可用性,部分节点不可用时,集群仍可用。通过增加slave做standby数据副本,能够实现故障自动failover,节点之间通过gossip协议贾环状态信息,用投票机制完成slave到master的角色提升
  5. 运维成本极低

深入理解Redis Cluster

那些年用过的Redis集群架构(含面试解析)