高可用性 (HA),原本是系统的一个特性,旨在确保在高于平均水平的时间内保持约定的运行性能水平,通常是正常运行时间。
Redis 作为一个内存数据库,其数据通常存储在内存中,一旦发生故障,可能导致数据丢失或服务中断,避免单点故障至关重要,这样系统才能顺利快速地恢复。
Redis 高可用 是指 Redis 通过一系列技术手段确保在面临故障的情况下也能持续提供服务的能力。
由于我们现在已经进入了一个分布式系统,并且需要考虑许多错误,因此在这个拓扑结构中需要考虑一些新问题,以前简单的事情现在变得更加复杂。
为了保证 Redis 的高可用,它主要采用了以下三种手段:
-
Redis 主从复制
-
Redis 哨兵模式
-
Redis 集群Cluster
接下来,我们逐个来分析看看。
1、Redis 主从复制
主从复制是 Redis 多机运行中最基础的功能,它是把多个 Redis 节点组成一个 Redis 集群,在这个集群当中有一个主节点用来进行数据的操作,其他从节点用于同步主节点的内容,并且提供给客户端进行数据查询。
Redis 主从同步分为:
-
全量复制:首次数据同步时
-
增量复制:只需把主从库网络断连期间主库收到的命令,同步给从库
注意:在2.8版本之前只有全量复制,而2.8版本后有全量和增量复制
1.1 全量复制
全量复制主要的实施流程,包括以下几个方面:
- 建立主从关系
当我们启动多个 Redis 实例的时候,它们相互之间就可以通过 replicaof(Redis 5.0 之前使用 slaveof)命令形成主库和从库的关系
markdown
/*
* 主:实例 1(ip:172.168.0.1)
* 从:实例 2(ip:172.168.0.2)
* 在从库上执行以下命令
*/
replicaof 172.16.0.1 6379
- 全量复制过程
1)主从库间建立连接、协商同步
从库给主库发送 psync 命令,表示要进行数据同步,主库根据这个命令的参数来启动复制。
psync 命令包含了主库的 runID 和复制进度 offset 两个参数:
-
runID,是每个 Redis 实例启动时都会自动生成的一个随机 ID,用来唯一标记这个实例
-
offset,此时设为 -1,表示第一次复制
2)主库将所有数据同步给从库
主库执行 bgsave 命令,生成 RDB 文件,接着将文件发给从库。从库接收到 RDB 文件后,会先清空当前数据库,然后加载 RDB 文件。
在主库将数据同步给从库的过程中,主库不会被阻塞,仍然可以正常接收请求,否则,Redis 的服务就被中断了。但是,这些请求中的写操作并没有记录到刚刚生成的 RDB 文件中。为了保证主从库的数据一致性,主库会在内存中用专门的 replication buffer,记录 RDB 文件生成后收到的所有写操作。
3)主库会把第二阶段执行过程中新收到的写命令,再发送给从库
当主库完成 RDB 文件发送后,就会把此时 replication buffer 中的修改操作发给从库,从库再重新执行这些操作。
1.2 增量复制
此功能在 Redis 2.8 版本才引入,主要为了控制主从复制的成本开销。网络断了之后,主从库会采用增量复制的方式继续同步。
先来看一个概念: replication buffer
。
replication_backlog 复制积压缓冲区。命令一方面会传输给从节点,另外还会记录在这个复制积压缓冲区里。Redis 使用一个环形缓冲区的结构保存最近的一些命令。在缓冲区中,对字节进行编号,这个编号在 Redis 中叫复制偏移量。
是否满足增量同步的条件:
-
从节点 replid 和 主节点的 replid 相同
-
复制偏移量 offset 在复制积压缓冲区的 backlog_off 和 offset 范围之间。
2、Redis 哨兵模式
哨兵模式是redis高可用的实现方式之一,使用一个或者多个哨兵(Sentinel)实例组成的系统,对redis节点进行监控,在主节点出现故障的情况下,能将从节点中的一个升级为主节点,进行故障转义,保证系统的可用性。
2.1 哨兵实现了什么功能呢?
-
监控(Monitoring):确保主从实例是否运作正常
-
自动故障转移(Automatic failover):如果主实例不可用并且足够多的(法定数量)节点同意这是真的,Sentinel 节点可以启动故障转移
-
配置提供者(Configuration provider):客户端在初始化时,通过连接哨兵来获得当前Redis服务的主节点地址
-
通知(Notification):哨兵可以将故障转移的结果发送给客户端
以这种方式使用 Redis Sentinel 可以进行故障检测。此检测涉及多个哨兵进程同意当前主实例不再可用。这个协议过程称为 Quorum。这可以提高鲁棒性并防止一台机器行为异常导致无法访问主 Redis 节点。
2.2 自动故障转移
- 主观下线
哨兵(Sentinel)节点会每秒一次的频率向建立了命令连接的实例发送 PING 命令,如果在down-after-milliseconds 毫秒内没有做出有效响应,包括(PONG/LOADING/MASTERDOWN)以外的响应,哨兵就会将该实例在本结构体中的状态标记为 SRI_S_DOWN 主观下线。
- 客观下线
当一个哨兵节点发现主节点处于主观下线状态时,会向其他的哨兵节点发出询问,该节点是不是已经主观下线了。
如果超过配置参数 quorum 个节点认为是主观下线时,该哨兵节点就会将自己维护的结构体中该主节点标记为 SRI_O_DOWN 客观下线 询问命令:
csharp
SENTINEL is-master-down-by-addr <current_epoch> <run_id>
- leader选举
在认为主节点客观下线的情况下,哨兵节点节点间会发起一次选举,命令如下:
csharp
SENTINEL is-master-down-by-addr <current_epoch> <run_id>
只是这次会将自己的 run_id 带进去,希望接受者将自己设置为主节点。
如果超过半数以上的节点返回将该节点标记为 leader 的情况下,会由该 leader 对故障进行转移。
- 故障转移
- 在从节点中挑选出新的主节点
-
a. 通讯正常
-
b. 优先级排序
-
c. 优先级相同是选择offset最大的
-
将该节点设置成新的主节点 SLAVEOF no one,并确保在后续的INGO命令时,该节点返回状态为master
-
将其他的从节点设置成从新的主节点复制, SLAVEOF命令
-
将旧的主节点变成新的主节点的从节点
3、Redis 集群Cluster
Cluster 即 集群模式。类似MySQL,Redis 集群也是一种分布式数据库方案,集群通过分片(sharding)模式来对数据进行管理,并具备分片间数据复制、故障转移和流量调度的能力。
Redis Cluster 允许 Redis 的水平扩展。
3.1 集群Cluster 介绍
Redis 集群将数据划分为 16384(2的14次方)个哈希槽(slots),如果你有多个实例节点,那么每个实例节点将管理其中一部分的槽位,槽位的信息会存储在各自所归属的节点中。以下图为例,该集群有4个 Redis 节点,每个节点负责集群中的一部分数据,数据量可以不均匀。比如性能好的实例节点可以多分担一些压力。
一个Redis集群一共有16384个哈希槽,你可以有1 ~ n个节点来分配这些哈希槽,可以不均匀分配,每个节点可以处理0个 到至多 16384 个槽点。当16384个哈希槽都有节点进行管理的时候,集群处于online 状态。同样的,如果有一个哈希槽没有被管理到,那么集群处于offline状态。
上面图中4个实例节点组成了一个集群,集群之间的信息通过 Gossip协议 进行交互,这样就可以在某一节点记录其他节点的哈希槽(slots)的分配情况。
3.2 Cluster模式扩展
单机的吞吐无法承受持续扩增的流量的时候,最好的办法是从横向(scale out) 和 纵向(scale up)两方面进行扩展:
-
纵向扩展(scale up):将单个实例的硬件资源做提升,比如 CPU核数量、内存容量、SSD容量
-
横向扩展(scale out):横向扩增 Redis 实例数,这样每个节点只负责一部分数据就可以(典型的分治思维)
4、总结
高可用一般来说有两个含义:1)数据尽量不丢失,2)保证服务尽可能可用。
AOF 和 RDB 数据持久化保证了数据尽量不丢失,而多节点来保证服务尽可能提供服务。单个节点的系统吞吐量有限,容量也有限,缺点明显,一旦发生故障会导致服务不可用。
-
使用读写分离之前,可以考虑其他方法增加Redis的读负载能力:如尽量优化主节点(减少慢查询、减少持久化等其他情况带来的阻塞等)提高负载能力;使用Redis集群同时提高读负载能力和写负载能力等
-
哨兵模式已经实现了故障自动转移的能力,但业务规模的不断扩展,用户量膨胀,并发量持续提升,会出现了 Redis 响应慢的情况
-
使用 Redis Cluster 集群,主要解决了大数据量存储导致的各种慢问题,同时也便于横向拓展。在面对千万级甚至亿级别的流量的时候,很多是在千百台的实例节点组成的集群上进行流量调度、服务治理的。
·END·
希望今天的讲解对大家有所帮助,谢谢!
Thanks for reading!
作者:张张,十年研发风雨路,大厂架构师,「架构精进之路」专注架构技术沉淀学习及分享,职业与认知升级,坚持分享接地气儿的干货文章,期待与你一起成长。
关注并私信我回复"01",送你一份程序员成长进阶大礼包,欢迎勾搭。