Appearance
Redis 主从架构高并发高可用
1. 概述
如果你用 Redis 缓存技术的话,肯定要考虑如何用 Redis 来加多台机器,保证 Redis 是高并发的,还有就是如何让 Redis 保证自己不是挂掉以后就直接死掉了,Redis 高可用。
Redis 高并发:主从架构,一主多从,一般来说,很多项目其实就足够了,单主用来写入数据,单机几万 QPS,多从用来查询数据,多个从实例可以提供每秒 10 万的 QPS。
Redis 高可用:如果你做主从架构部署,其实就是加上哨兵就可以了,就可以实现,任何一个实例宕机,自动会进行主备切换。
Redis 高并发的同时,还需要容纳大量的数据:一主多从,每个实例都容纳了完整的数据,比如 Redis 主就 10G 的内存量,其实你就最对只能容纳 10g 的数据量。如果你的缓存要容纳的数据量很大,达到了几十 G,甚至几百 G,或者是几 T,那你就需要 Redis 集群,而且用 Redis 集群之后,可以提供可能每秒几十万的读写并发。
2. Redis 读写分离
Redis 高并发跟整个系统高并发的关系
系统要搞高并发,那就要把底层的缓存搞好,让更少的请求直接到数据库,因为数据库的高并发实现起来是比较麻烦的,而且有些操作还有事务的要求等等,所以很难做到非常高的并发。
Redis 并发做的好对于整个系统的并发来说还是不够的,但是 Redis 作为整个大型的缓存架构,在支撑高并发的架构里面是非常重要的一环。
要实现系统的高并发,首先缓存中间件、缓存系统必须要能够支撑起高并发,然后在经过良好的整体缓存架构设计(多级缓存、热点缓存),才能真正支撑起高并发。
Redis 不能支撑高并发的瓶颈
Redis 不能支撑高并发的瓶颈主要是单机问题,也就是说只有一个单一的 Redis,就算机器性能再怎么好,也是有上限的。
如何支撑更高的并发
单机的 Redis 不可能支撑太高的并发量,要想支持更高的并发可以进行读写分离。对于缓存来说,一般都是支撑读高并发的,写的请求是比较少的,因此可以基于主从架构进行读写分离。
配置一个 master(主)机器用来写入数据,配置多个 slave(从)来进行数据的读取,在 master 接收到数据之后将数据同步到 slave 上面即可,这样 slave 可以配置多台机器,就可以提高整体的并发量。
基于主从的读写分离简单示意图:

3. Redis Replication 以及 master 持久化
3.1. Redis Replication 原理
一个 master 节点下面挂若干个 slave 节点,写操作将数据写到 master 节点上面去,然后在 master 写完之后,通过异步操作的方式将数据同步到所有的 slave 节点上面去,保证所有节点的数据是一致的。
3.2. Redis Replication 的核心机制
- Redis 采用异步方式复制数据到 slave 节点,不过 Redis 2.8 开始,slave node 会周期性地确认自己每次复制的数量;
- 一个 master node 可以配置多个 salve node;
- slave node 也可以连接其他的 slave node;
- slave node 做复制的时候,是不会阻塞 master node 的正常工作的;
- slave node 在做复制的时候,也不会阻塞自己的操作,它会用旧的数据来提供服务;但是复制完成的时候,需要删除旧的数据,加载新的数据,这个时候会对外暂停提供服务;
- slave node 主要用来进行横向扩容,做读写分离,扩容的 slave node 可以提高吞吐量;
3.3. master 持久化对主从架构的安全意义
如果采用这种主从架构,那么必须要开启 master node 的持久化。不建议使用 slave node 作为 master node 的热备份,因为如果这样的话,如果 master 一旦宕机,那么 master 的数据就会丢失,重启之后数据是空的,其他的 slave node 要是来复制数据的话,就会复制到空,这样所有节点的数据就都丢了。
要对备份文件做多种冷备份,防止整个机器坏了,备份的 rdb 数据也丢失的情况。
4. Redis 主从复制原理、断点续传、无磁盘化复制、过期 key 处理
4.1. 主从复制原理
- 当启动一个 slave node 的时候,它会发送一个
PSYNC命令给 master node; - 如果这个 slave node 是重新连接 master node,那么 master node 仅仅会复制给 slave 部分缺失的数据;如果是第一次连接 master node,那么就会触发一次 full resynchronization;
- 开始 full resynchronization 的时候,master 会启动一个后台线程,开始生成一份 RDB 快照文件,同时还会将从客户端新接收到的所有写命令缓存在内存当中;
- master node 将生成的 RDB 文件发送给 slave node,slave 现将其写入本地磁盘,然后再从磁盘加载到内存当中。然后 master node 会将内存中缓存的写命令发送给 slave node,slave node 也会同步这部分数据;
- slave node 如果跟 master node 因为网络故障断开了连接,会自动重连;
- master 如果发现有多个 slave node 来重新连接,仅仅会启动一个 rdb save 操作,用一份数据服务所有 slave node;
4.2. 主从复制的断点续传
从 Redis 2.8 开始支持断点续传。如果在主从复制的过程中,网络突然断掉了,那么可以接着上次复制的地方,继续复制,而不是从头复制一份。
原理:
master node 会在内存中创建一个 backlog,master 和 slave 都会保存一个 replica offset 还有一个 master id,offset 就保存在 backlog 中。如果 master 和 slave 网络连接断掉了,slave 会让 master 从上次的 replica offset 开始继续复制。但是如果没有找到 offset,就会执行一次 full resynchronization 操作。
4.3. 无磁盘化复制
无磁盘化复制是指,master 直接再内存中创建 RDB 文件,然后发送给 slave,不会在自己本地磁盘保存数据。
设置方式:
配置 repl-diskless-sync 和 repl-diskless-sync-delay 参数。
repl-diskless-sync:该参数保证进行无磁盘化复制;repl-diskless-sync-delay:该参数表示等待一定时长再开始复制,这样可以等待多个 slave 节点从新连接上来;
4.4. 过期 key 处理
slave 不会过期 key,只有等待 master 过期 key。
如果 master 过期了一个 key,或者淘汰了一个 key,那么 master 会模拟发送一条 del 命令给 slave,slave 接到之后会删除该 key。
5. Redis Replication 的完整流运行程和原理
5.1. 复制的完整流程
- slave node 启动,仅仅保存了 master node 的信息,包括 master node 的 host 和 ip,但是数据复制还没有开始。master node 的 host 和 ip 是在
redis.conf文件里面的 slaveOf 中配置的; - slave node 内部有一个定时任务,每秒检查是否有新的 master node 要连接复制,如果发现,就跟 master node 建立 socket 网络连接;
- slave node 发送 ping 命令给 master node;
- 如果 master 设置了 requirepass,那么 slave node 必须发送 master auth 的口令过去进行口令验证;
- master node 第一次执行全量复制,将所有数据发送给 slave node;
- master node 持续降写命令,异步复制给 slave node;
5.2. 数据同步的机制
指的是 slave 第一次连接 master 时的情况,执行的是全量复制。
master 和 slave 都会维护一个 offset
master 会在自身不断累加 offset,slave 也会在自身不断累加 offset。slave 每秒都会上报自己的 offset 给 master,同时 master 也会保存每个 slave 的 offset。
这个倒不是说特定就用在全量复制的,主要是 master 和 slave 都要知道各自的数据的 offset,才能知道互相之间的数据不一致的情况。
backlog
master node 有一个 backlog 在内存中,默认是 1M 大。
master node 给 slave node 复制数据时,也会将数据在 backlog 中同步一份。
backlog 主要是用来做全量复制中断时候的增量复制的。
master run id
Redis 通过 info server 可以查看到 master run id。
用途:slave 根据其来定位唯一的 master。
为什么不用 host + ip:因为使用 host + ip 来定位 master 是不靠谱的,如果 master node 重启或者数据出现了变化,那么 slave 应该根据不同的 master run id 进行区分,run id 不同就需要做一次全量复制。
如果需要不更改 run id 重启 Redis,可以使用 redis-cli debug reload 命令。
5.3. 全量复制流程与机制
- master 执行 bgsave,在本地生成一份 RDB 文件;
- master node 将 RDB 快照文件发送给 slave node,如果 RDB 文件的复制时间超过 60 秒(repl-timeout),那么 slave node 就会任务复制失败,可以适当调整这个参数;
- 对于千兆网卡的机器,一般每秒传输 100M,传输 6G 文件很可能超过 60 秒;
- master node 在生成 RDB 文件时,会将所有新接到的写命令缓存在内存中,在 slave node 保存了 RDB 文件之后,再将这些写命令复制给 slave node;
- 查看
client-output-buffer-limit slave参数,比如client-output-buffer-limit slave 256MB 64MB 60,表示在复制期间,内存缓存去持续消耗超过 64M,或者一次性超过 256MB,那么停止复制,复制失败; - slave node 接收到 RDB 文件之后,清空自己的数据,然后重新加载 RDB 文件到自己的内存中,在这个过程中,基于旧数据对外提供服务;
- 如果 slave node 开启了 AOF,那么会立即执行
BRREWRITEAOF,重新 AOF;
rdb 生成、rdb 通过网络拷贝、slave 旧数据的清理、slave aof rewrite,很耗费时间。
如果复制的数据量在 4G~6G 之间,那么很可能全量复制时间消耗到 1 分半到 2 分钟。
5.4. 增量复制流程与机制
- 如果全量复制过程中,master 和 slave 网络连接断掉,那么 slave 重新连接 master 会触发增量复制;
- master 直接从自己的 backlog 中获取部分丢失是数据,发送给 slave node;
- master 就是根据 slave 发送的 psync 中的 offset 来从 backlog 中获取数据的;
5.5. 心跳
master 和 slave 互相都会发送 heartbeat 信息。
master 默认每隔 10 秒发送一次,slave node 默认每隔 1 秒发送一次。
5.6. 异步复制
master 每次接收到写命令之后,现在内部写入数据,然后异步发送给 slave node。
6. Redis 主从架构下如何才能做到 99.99% 的高可用性?
6.1. 什么是 99.99% 高可用?
高可用性(英语:high availability,缩写为 HA),IT 术语,指系统无中断地执行其功能的能力,代表系统的可用性程度。是进行系统设计时的准则之一。高可用性系统与构成该系统的各个组件相比可以更长时间运行。
高可用性通常通过提高系统的容错能力来实现。定义一个系统怎样才算具有高可用性往往需要根据每一个案例的具体情况来具体分析。
其度量方式,是根据系统损害、无法使用的时间,以及由无法运作恢复到可运作状况的时间,与系统总运作时间的比较。计算公式为:
A(可用性),MTBF(平均故障间隔),MDT(平均修复时间) 在线系统和执行关键任务的系统通常要求其可用性要达到 5 个 9 标准(99.999%)。
| 可用性 | 年故障时间 |
|---|---|
| 99.9999% | 32 秒 |
| 99.999% | 5 分 15 秒 |
| 99.99% | 52 分 34 秒 |
| 99.9% | 8 小时 46 分 |
| 99% | 3 天 15 小时 36 分 |
6.2. Redis 不可用
Redis 不可以包含了单实例的不可用,主从架构的不可用。
不可用的情况:
- 主从架构的 master 节点挂了,如果 master 节点挂了那么缓存数据无法再写入,而且 slave 里面的数据也无法过期,这样就导致了不可用;
- 如果是单实例,那么可能因为其他原因导致 Redis 进程死了。或者部署 Redis 的机器坏了;
不可用的后果:首先缓存不可用了,那么请求就会直接走数据库,如果涌入大量请求超过了数据库的承载能力,那么数据库就挂掉了,这时候如果不能及时处理好缓存问题,那么由于请求过多,数据库重启之后很快就又会挂掉,直接导致整个系统不可用。
6.3. 如何实现高可用
- 保证每个 Redis 都有备份;
- 保证在当前 Redis 出故障之后,可以很快切换到备份 Redis 上面去;
为了解决这个问题,引入下面的哨兵机制。
7. Redis 哨兵架构的相关基础知识的讲解
7.1. 什么是哨兵
哨兵(Sentinal)是 Redis 集群架构当中非常重要的一个组件,它主要有一下功能:
- 集群监控:负责监控 Redis master 和 slave 进程是否正常工作;
- 消息通知:如果某个 Redis 实例有故障,那么哨兵负责发送消息作为报警通知给管理员;
- 故障转移:如果 master 挂掉了,会自动转移到 slave 上;
- 配置中心:如果故障发生了,通知 client 客户端连接到新的 master 上面去;
7.2. 哨兵的核心知识
- 哨兵本身是分布式的,需要作为一个集群去运行,个哨兵协同工作;
- 故障转移时,判断一个 master 宕机了,需要大部分哨兵同意才行;
- 即使部分哨兵挂掉了,哨兵集群还是能正常工作的;
- 哨兵至少需要 3 个实例,来保证自己的健壮性;
- 哨兵 + Redis 主从结构,是无法保证数据零丢失的,只会保证 Redis 集群的高可用;
- 对应哨兵 + Redis 主从这种架构,在使用之前,要做重复的测试和演练;
7.3. 为什么哨兵集群部署 2 个节点无法正常工作
哨兵集群必须部署 2 个以上的节点。如果集群仅仅部署了 2 个哨兵实例,那么 quorum=1(执行故障转移需要同意的哨兵个数)。

如图,如果这时候 master1 宕机了,哨兵 1 和哨兵 2 中只要有一个认为 master1 宕机了就可以进行故障转移,同时哨兵 1 和哨兵 2 会选举出一个哨兵来执行故障转移。
同时这个时候需要 majority(也就是所有集群中超过一半哨兵的数量),2 个哨兵那么 majority 就是 2,也就说需要至少 2 个哨兵还运行着,才可以进行故障转移。
但是如果整个 master 和哨兵 1 同时宕机了,那么就只剩一个哨兵了,这个时候就没有 majority 来运行执行故障转移了,虽然两外一台机器还有一个哨兵,但是 1 无法大于 1,也就是无法保证半数以上,因此故障转移不会执行。
7.4. 经典的 3 节点哨兵集群

Configuration: quorum = 2,majority=2
如果 M1 所在机器宕机了,那么三个哨兵还剩下 2 个,S2 和 S3 可以一致认为 master 宕机,然后选举出一个来执行故障转移。
同时 3 个哨兵的 majority 是 2,所以还剩下的 2 个哨兵运行着,就可以允许执行故障转移。
8. Redis 哨兵主备切换的数据丢失问题:异步复制、集群脑裂
8.1. 两种数据丢失的场景
8.1.1. 异步复制导致的数据丢失
因为从 master 到 slave 的数据复制过程是异步的,可能有部分数据还没来得及复制到 slave 上面去,这时候 master 就宕机了,那么这部分数据就丢失了。
8.1.2. 集群脑裂导致的数据丢失
什么是脑裂:脑裂,也就是说,某个 master 所在机器突然脱离了正常的网络,跟其他 slave 机器不能连接,但是实际上 master 还运行着。
此时哨兵可能就会认为 master 宕机了,然后开启选举,将其他 slave 切换成了 master。
这个时候,集群里就会有两个 master,也就是所谓的脑裂。
此时虽然某个 slave 被切换成了 master,但是可能 client 还没来得及切换到新的 master,还继续写向旧 master 的数据可能也丢失了。
因此旧 master 再次恢复的时候,会被作为一个 slave 挂到新的 master 上去,自己的数据会清空,重新从新的 master 复制数据
8.2. 解决异步复制的脑裂导致的数据丢失
要解决这个问题,就需要配置两个参数:
min-slaves-to-write 1 和 min-slaves-max-lag:
表示要求至少有一个 slave 在进行数据的复制和同步的延迟不能超过 10 秒。
如果一旦所有的 slave 数据同步和复制的延迟都超过了 10 秒,那么这个时候,master 就会在接受任何请求了。
减少异步复制的数据丢失
有了
min-slaves-max-lag这个配置,就可以确保说,一旦 slave 复制数据和 ack 延时太长,就认为可能 master 宕机后损失的数据太多了,那么就拒绝写请求,这样可以把 master 宕机时由于部分数据未同步到 slave 导致的数据丢失降低的可控范围内。减少脑裂的数据丢失
如果一个 master 出现了脑裂,跟其他 slave 丢了连接,那么上面两个配置可以确保说,如果不能继续给指定数量的 slave 发送数据,而且 slave 超过 10 秒没有给自己 ack 消息,那么就直接拒绝客户端的写请求。
这样脑裂后的旧 master 就不会接受 client 的新数据,也就避免了数据丢失。
上面的配置就确保了,如果跟任何一个 slave 丢了连接,在 10 秒后发现没有 slave 给自己 ack,那么就拒绝新的写请求。
因此在脑裂场景下,最多就丢失 10 秒的数据。
9. Redis 哨兵的多个核心底层原理的深入解析(包含 slave 选举算法)
9.1. sdown 和 odown 两种状态
sdown 是主观宕机,就一个哨兵如果自己觉得一个 master 宕机了,那么就是主观宕机。
odown 是客观宕机,如果 quorum 数量的哨兵都觉得一个 master 宕机了,那么就是客观宕机。
sdown 达成的条件很简单,如果一个哨兵 ping 一个 master,超过了 is-master-down-after-milliseconds 指定的毫秒数之后,就主观认为 master 宕机
sdown 到 odown 转换的条件很简单,如果一个哨兵在指定时间内,收到了 quorum 指定数量的其他哨兵也认为那个 master 是 sdown 了,那么就认为是 odown 了,客观认为 master 宕机。
9.2. 哨兵集群的字段发现机制
- 哨兵相互之间的发现,是通过 Redis 的 pub/sub 系统实现的,每个哨兵都会往
__sentinel__:hello这个 channel 里面发送一个消息,这时候其他的哨兵都可以消费这个消息,并感知其他哨兵的存在; - 每个两秒钟,每个哨兵都会往自己监控的某个 master + slave 对应的
__sentinel__:hello channel里面发送一个消息,内容是自己的 host、ip 和 run id 还有对这个 master 的监控配置; - 每个哨兵也会去监听自己监控的每个 master + slave 对应的
__sentinel__:hello channel,然后去感知到同样在监听这个 master + slave 的其他哨兵的存在; - 每个哨兵还会根据其他哨兵交换对 master 的监控配置,互相进行监控配置的同步;
9.3. slave 配置的自我纠正
哨兵会负责自动纠正 slave 的一些配置,比如 slave 如果要成为潜在的 master 候选人,哨兵会确保 slave 在复制现有 master 数据;如果 slave 连接到了一个错误的 master 上,比如故障转移之后,那么哨兵会确保他们连接到正确的 master 上来。
9.4. 选举算法
如果一个 master 被认为 odown 了,而且 majority 数量的哨兵都允许了主备切换,那么某个哨兵就会执行主备切换,此时首先要选举一个 slave 出来。选举会考虑到以下情况:
- slave 跟 master 断开连接的时长;
- slave 的优先级;
- slave 复制数据的 offset;
- slave 的 run id;
首先,如果一个 slave 跟 master 断开连接已经超过了 down-after-millisecondes 的 10 倍,外加 master 宕机的时长,那么 slave 就被认为不适合选举为 master 了。
即:断开连接时间 > down-after-milliseconds * 10 + milliseconds_since_master_is_in_SDOWN_sate。
对应剩下的 slave 按照如下规定排序:
- 首先,按照 slave 的优先级进行排序,slave priority 越低,优先级就越高;
- 如果优先级相同,那么就看 replica offset,那个 slave 复制了越多的数据,offset 越靠后,优先级就越高;
- 如果上面都想同,那就选择 run id 最小的那个 slave;
9.5. quorum 和 majority
每次一个哨兵做主备切换,首先需要 quorum 数量的哨兵认为 odown,然后选举出一个哨兵来做主备切换,这个哨兵还要得到 majority 数量哨兵的授权,才能正式执行切换。
如果 quorum < majority,比如 5 个哨兵,majority 就是 3(超过半数),quorum 设置为 2,那么就需要 3 个哨兵授权就可以执行切换。
如果 quorum >= majority,那么必须 quorum 数量的哨兵都授权才可以进行切换,比如 5 个哨兵,quorum 是 5,那么必须 5 个哨兵都同意授权,才可以进行切换。
9.6. configuration epoch
哨兵会对一套 Redis master + slave 进行监控,有相应的监控的配置。
执行切换的那个哨兵,会从要切换到的新 master(salve->master)那里得到一个 configuration epoch,这就是一个 version 号,每次切换的 version 号都必须是唯一的。
如果第一个选举出的哨兵切换失败了,那么其他哨兵,会等待 failover-timeout 时间,然后接替继续执行切换,此时会重新获取一个新的 configuration epoch,作为新的 version 号。
9.7. configuraiton 传播
哨兵完成切换之后,会在自己本地更新生成最新的 master 配置,然后同步给其他的哨兵,就是通过之前说的 pub/sub 消息机制。
这里之前的 version 号就很重要了,因为各种消息都是通过一个 channel 去发布和监听的,所以一个哨兵完成一次新的切换之后,新的 master 配置是跟着新的 version 号的。
其他的哨兵都是根据版本号的大小来更新自己的 master 配置的。