Redis的高并发及高可用,到底该如何保证?

# 一、redis 如何通过读写分享来承载读请求 QPS 超过 10 万 +

# 1、redis 高并发跟整个系统的高并发之间的关系

redis,你要搞高并发的话,不可避免,要把底层的缓存搞得很好

mysql,高并发,做到了,那么也是通过一系列复杂的分库分表,订单系统,事务要求的,QPS 到几万,比较高了

要做一些电商的商品详情页,真正的超高并发,QPS 上十万,甚至是百万,一秒钟百万的请求量

光是 redis 是不够的,但是 redis 是整个大型的缓存架构中,支撑高并发的架构里面,非常重要的一个环节

首先,你的底层的缓存中间件,缓存系统,必须能够支撑的起我们说的那种高并发,其次,再经过良好的整体的缓存架构的设计(多级缓存架构、热点缓存),支撑真正的上十万,甚至上百万的高并发

# 2、redis 不能支撑高并发的瓶颈在哪里?

单机

# 3、如果 redis 要支撑超过 10 万 + 的并发,那应该怎么做?

单机的 redis 几乎不太可能说 QPS 超过 10 万 +,除非一些特殊情况,比如你的机器性能特别好,配置特别高,物理机,维护做的特别好,而且你的整体的操作不是太复杂

单机在几万

读写分离,一般来说,对缓存,一般都是用来支撑读高并发的,写的请求是比较少的,可能写请求也就一秒钟几千,一两千

大量的请求都是读,一秒钟二十万次读

读写分离

主从架构 -> 读写分离 -> 支撑 10 万 + 读 QPS 的架构

# 二、Redis Replication 以及 master 持久化对主从架构的安全意义

课程大纲

1、图解 redis replication 基本原理 2、redis replication 的核心机制 3、master 持久化对于主从架构的安全保障的意义

redis replication -> 主从架构 -> 读写分离 -> 水平扩容支撑读高并发

redis replication 的最最基本的原理,铺垫

# 1、图解 redis replication 基本原理

img

img

# 2、redis replication 的核心机制

(1)redis 采用异步方式复制数据到 slave 节点,不过 redis 2.8 开始,slave node 会周期性地确认自己每次复制的数据量 (2)一个 master node 是可以配置多个 slave node 的 (3)slave node 也可以连接其他的 slave node (4)slave node 做复制的时候,是不会 block master node 的正常工作的 (5)slave node 在做复制的时候,也不会 block 对自己的查询操作,它会用旧的数据集来提供服务;但是复制完成的时候,需要删除旧数据集,加载新数据集,这个时候就会暂停对外服务了 (6)slave node 主要用来进行横向扩容,做读写分离,扩容的 slave node 可以提高读的吞吐量

slave,高可用性,有很大的关系

# 3、master 持久化对于主从架构的安全保障的意义

如果采用了主从架构,那么建议必须开启 master node 的持久化!

不建议用 slave node 作为 master node 的数据热备,因为那样的话,如果你关掉 master 的持久化,可能在 master 宕机重启的时候数据是空的,然后可能一经过复制,slave node 数据也丢了

master -> RDB 和 AOF 都关闭了 -> 全部在内存中

master 宕机,重启,是没有本地数据可以恢复的,然后就会直接认为自己的数据是空的

master 就会将空的数据集同步到 slave 上去,所有 slave 的数据全部清空

100% 的数据丢失

  1. master 节点,必须要使用持久化机制
  2. master 的各种备份方案,要不要做,万一说本地的所有文件丢失了;从备份中挑选一份 rdb 去恢复 master; 这样才能确保 master 启动的时候,是有数据的

即使采用了后续讲解的高可用机制,slave node 可以自动接管 master node,但是也可能 sentinal 还没有检测到 master failure,master node 就自动重启了,还是可能导致上面的所有 slave node 数据清空故障

# 三、redis 主从复制的原理

# 1、主从架构的核心原理

当启动一个 slave node 的时候,它会发送一个 PSYNC 命令给 master node

如果这是 slave node 重新连接 master node,那么 master node 仅仅会复制给 slave 部分缺少的数据;否则如果是 slave node 第一次连接 master node,那么会触发一次 full resynchronization

开始 full resynchronization 的时候,master 会启动一个后台线程,开始生成一份 RDB 快照文件,同时还会将从客户端收到的所有写命令缓存在内存中。RDB 文件生成完毕之后,master 会将这个 RDB 发送给 slave,slave 会先写入本地磁盘,然后再从本地磁盘加载到内存中。然后 master 会将内存中缓存的写命令发送给 slave,slave 也会同步这些数据。

slave node 如果跟 master node 有网络故障,断开了连接,会自动重连。master 如果发现有多个 slave node 都来重新连接,仅仅会启动一个 rdb save 操作,用一份数据服务所有 slave node。

# 2、主从复制的断点续传

从 redis 2.8 开始,就支持主从复制的断点续传,如果主从复制过程中,网络连接断掉了,那么可以接着上次复制的地方,继续复制下去,而不是从头开始复制一份

master node 会在内存中常见一个 backlog,master 和 slave 都会保存一个 replica offset 还有一个 master id,offset 就是保存在 backlog 中的。如果 master 和 slave 网络连接断掉了,slave 会让 master 从上次的 replica offset 开始继续复制

但是如果没有找到对应的 offset,那么就会执行一次 resynchronization

# 3、无磁盘化复制

master 在内存中直接创建 rdb,然后发送给 slave,不会在自己本地落地磁盘了

repl-diskless-sync repl-diskless-sync-delay,等待一定时长再开始复制,因为要等更多 slave 重新连接过来

# 4、过期 key 处理

slave 不会过期 key,只会等待 master 过期 key。如果 master 过期了一个 key,或者通过 LRU 淘汰了一个 key,那么会模拟一条 del 命令发送给 slave。

# 四、Redis Replication 的完整运行流程和原理的再次深入剖析

# 1、复制的完整流程

(1)slave node 启动,仅仅保存 master node 的信息,包括 master node 的 host 和 ip,但是复制流程没开始

master host 和 ip 是从哪儿来的,redis.conf 里面的 slaveof 配置的

(2)slave node 内部有个定时任务,每秒检查是否有新的 master node 要连接和复制,如果发现,就跟 master node 建立 socket 网络连接 (3)slave node 发送 ping 命令给 master node (4)口令认证,如果 master 设置了 requirepass,那么 slave node 必须发送 masterauth 的口令过去进行认证 (5)master node 第一次执行全量复制,将所有数据发给 slave node (6)master node 后续持续将写命令,异步复制给 slave node

# 2、数据同步相关的核心机制

指的就是第一次 slave 连接 msater 的时候,执行的全量复制,那个过程里面你的一些细节的机制

(1)master 和 slave 都会维护一个 offset

master 会在自身不断累加 offset,slave 也会在自身不断累加 offset slave 每秒都会上报自己的 offset 给 master,同时 master 也会保存每个 slave 的 offset

这个倒不是说特定就用在全量复制的,主要是 master 和 slave 都要知道各自的数据的 offset,才能知道互相之间的数据不一致的情况

(2)backlog

master node 有一个 backlog,默认是 1MB 大小 master node 给 slave node 复制数据时,也会将数据在 backlog 中同步写一份 backlog 主要是用来做全量复制中断后的增量复制的

(3)master run id

info server,可以看到 master run id 如果根据 host+ip 定位 master node,是不靠谱的,如果 master node 重启或者数据出现了变化,那么 slave node 应该根据不同的 run id 区分,run id 不同就做全量复制 如果需要不更改 run id 重启 redis,可以使用 redis-cli debug reload 命令

(4)psync

从节点使用 psync 从 master node 进行复制,psync runid offset master node 会根据自身的情况返回响应信息,可能是 FULLRESYNC runid offset 触发全量复制,可能是 CONTINUE 触发增量复制

# 3、全量复制

(1)master 执行 bgsave,在本地生成一份 rdb 快照文件 (2)master node 将 rdb 快照文件发送给 slave node,如果 rdb 复制时间超过 60 秒(repl-timeout),那么 slave node 就会认为复制失败,可以适当调节大这个参数 (3)对于千兆网卡的机器,一般每秒传输 100MB,6G 文件,很可能超过 60s (4)master node 在生成 rdb 时,会将所有新的写命令缓存在内存中,在 slave node 保存了 rdb 之后,再将新的写命令复制给 slave node (5)client-output-buffer-limit slave 256MB 64MB 60,如果在复制期间,内存缓冲区持续消耗超过 64MB,或者一次性超过 256MB,那么停止复制,复制失败 (6)slave node 接收到 rdb 之后,清空自己的旧数据,然后重新加载 rdb 到自己的内存中,同时基于旧的数据版本对外提供服务 (7)如果 slave node 开启了 AOF,那么会立即执行 BGREWRITEAOF,重写 AOF

rdb 生成、rdb 通过网络拷贝、slave 旧数据的清理、slave aof rewrite,很耗费时间

如果复制的数据量在 4G~6G 之间,那么很可能全量复制时间消耗到 1 分半到 2 分钟

# 4、增量复制

(1)如果全量复制过程中,master-slave 网络连接断掉,那么 slave 重新连接 master 时,会触发增量复制 (2)master 直接从自己的 backlog 中获取部分丢失的数据,发送给 slave node,默认 backlog 就是 1MB (3)msater 就是根据 slave 发送的 psync 中的 offset 来从 backlog 中获取数据的

# 5、heartbeat

主从节点互相都会发送 heartbeat 信息

master 默认每隔 10 秒发送一次 heartbeat,slave node 每隔 1 秒发送一个 heartbeat

# 6、异步复制

master 每次接收到写命令之后,现在内部写入数据,然后异步发送给 slave node

# 五、Redis 主从架构如何做到高可用

# 1、什么是 99.99% 高可用?

架构上,高可用性,99.99% 的高可用性

讲的学术,99.99%,公式,系统可用的时间 / 系统故障的时间,365 天,在 365 天 * 99.99% 的时间内,你的系统都是可以哗哗对外提供服务的,那就是高可用性,99.99%

系统可用的时间 / 总的时间 = 高可用性,然后会对各种时间的概念,说一大堆解释

# 2、redis 不可用是什么?单实例不可用?主从架构不可用?不可用的后果是什么?

  • redis 不可用是什么
  1. redis 进程死了
  2. redis 进程所在的机器死了
  • 单实例不可用?主从架构不可用?
  1. 一个 slave 挂掉了,是不会影响可用性的,还有其他的 slave 在提供相同数据下的对外查询服务
  2. 如果 master node 死掉,就没法写数据了,slave node 就没有用了,因为没有 master 给他们复制数据了,这时系统相当于就是不可用了
  • 不可用的后果是什么?

高并发高性能的缓存不可用了,超过 mysql 最大承载能力大并发的大流量会涌入 mysql 中,导致 mysql 宕机

# 3、redis 怎么才能做到高可用?

redis 的高可用架构,叫做故障转移 failover,也可以叫做主备切换。

在 master node 故障时,自动检测,并且将某个 slave node 自动切换为 master node 的过程,叫做主备切换。这个过程,实现了 redis 的主从架构下的高可用性。

一旦 master 故障,在很短的时间内,就会切换到另一个 master 上去,可能就几分钟、几秒钟 redis 是不可用的。

这都依赖于 sentinal node,即哨兵。

# 六、哨兵

# 1、哨兵的介绍

sentinal,中文名是哨兵

哨兵是 redis 集群架构中非常重要的一个组件,主要功能如下

(1)集群监控,负责监控 redis master 和 slave 进程是否正常工作 (2)消息通知,如果某个 redis 实例有故障,那么哨兵负责发送消息作为报警通知给管理员 (3)故障转移,如果 master node 挂掉了,会自动转移到 slave node 上 (4)配置中心,如果故障转移发生了,通知 client 客户端新的 master 地址

哨兵本身也是分布式的,作为一个哨兵集群去运行,互相协同工作

(1)故障转移时,判断一个 master node 是宕机了,需要大部分的哨兵都同意才行,涉及到了分布式选举的问题 (2)即使部分哨兵节点挂掉了,哨兵集群还是能正常工作的,因为如果一个作为高可用机制重要组成部分的故障转移系统本身是单点的,那就很坑爹了

目前采用的是 sentinal 2 版本,sentinal 2 相对于 sentinal 1 来说,重写了很多代码,主要是让故障转移的机制和算法变得更加健壮和简单

# 2、哨兵的核心知识

(1)哨兵至少需要 3 个实例,来保证自己的健壮性 (2)哨兵 + redis 主从的部署架构,是不会保证数据零丢失的,只能保证 redis 集群的高可用性 (3)对于哨兵 + redis 主从这种复杂的部署架构,尽量在测试环境和生产环境,都进行充足的测试和演练

# 3、为什么 redis 哨兵集群只有 2 个节点无法正常工作?

哨兵集群必须部署 2 个以上节点

如果哨兵集群仅仅部署了个 2 个哨兵实例,quorum=1

+----+         +----+
| M1 |---------| R1 |
| S1 |         | S2 |
+----+         +----+

Configuration: quorum = 1

master 宕机,s1 和 s2 中只要有 1 个哨兵认为 master 宕机就可以还行切换,同时 s1 和 s2 中会选举出一个哨兵来执行故障转移

同时这个时候,需要 majority,也就是大多数哨兵都是运行的,2 个哨兵的 majority 就是 2(2 的 majority=2,3 的 majority=2,5 的 majority=3,4 的 majority=2),2 个哨兵都运行着,就可以允许执行故障转移

但是如果整个 M1 和 S1 运行的机器宕机了,那么哨兵只有 1 个了,此时就没有 majority 来允许执行故障转移,虽然另外一台机器还有一个 R1,但是故障转移不会执行

# 4、经典的 3 节点哨兵集群

+----+
       | M1 |
       | S1 |
       +----+
          |
+----+    |    +----+
| R2 |----+----| R3 |
| S2 |         | S3 |
+----+         +----+

Configuration: quorum = 2,majority

如果 M1 所在机器宕机了,那么三个哨兵还剩下 2 个,S2 和 S3 可以一致认为 master 宕机,然后选举出一个来执行故障转移

同时 3 个哨兵的 majority 是 2,所以还剩下的 2 个哨兵运行着,就可以允许执行故障转移

# 七、redis 哨兵主备切换的数据丢失问题

1、两种数据丢失的情况

2、解决异步复制和脑裂导致的数据丢失

# 1、两种数据丢失的情况

主备切换的过程,可能会导致数据丢失

(1)异步复制导致的数据丢失

因为 master -> slave 的复制是异步的,所以可能有部分数据还没复制到 slave,master 就宕机了,此时这些部分数据就丢失了

(2)脑裂导致的数据丢失

脑裂,也就是说,某个 master 所在机器突然脱离了正常的网络,跟其他 slave 机器不能连接,但是实际上 master 还运行着

此时哨兵可能就会认为 master 宕机了,然后开启选举,将其他 slave 切换成了 master

这个时候,集群里就会有两个 master,也就是所谓的脑裂

此时虽然某个 slave 被切换成了 master,但是可能 client 还没来得及切换到新的 master,还继续写向旧 master 的数据可能也丢失了

因此旧 master 再次恢复的时候,会被作为一个 slave 挂到新的 master 上去,自己的数据会清空,重新从新的 master 复制数据

# 2、解决异步复制和脑裂导致的数据丢失

min-slaves-to-write 1 min-slaves-max-lag 10

要求至少有 1 个 slave,数据复制和同步的延迟不能超过 10 秒

如果说一旦所有的 slave,数据复制和同步的延迟都超过了 10 秒钟,那么这个时候,master 就不会再接收任何请求了

上面两个配置可以减少异步复制和脑裂导致的数据丢失

(1)减少异步复制的数据丢失

有了 min-slaves-max-lag 这个配置,就可以确保说,一旦 slave 复制数据和 ack 延时太长,就认为可能 master 宕机后损失的数据太多了,那么就拒绝写请求,这样可以把 master 宕机时由于部分数据未同步到 slave 导致的数据丢失降低的可控范围内

(2)减少脑裂的数据丢失

如果一个 master 出现了脑裂,跟其他 slave 丢了连接,那么上面两个配置可以确保说,如果不能继续给指定数量的 slave 发送数据,而且 slave 超过 10 秒没有给自己 ack 消息,那么就直接拒绝客户端的写请求

这样脑裂后的旧 master 就不会接受 client 的新数据,也就避免了数据丢失

上面的配置就确保了,如果跟任何一个 slave 丢了连接,在 10 秒后发现没有 slave 给自己 ack,那么就拒绝新的写请求

因此在脑裂场景下,最多就丢失 10 秒的数据

# redis 哨兵的多个底层原理的深入解析

# 1、sdown 和 odown 转换机制

sdown 和 odown 两种失败状态

sdown 是主观宕机,就一个哨兵如果自己觉得一个 master 宕机了,那么就是主观宕机

odown 是客观宕机,如果 quorum 数量的哨兵都觉得一个 master 宕机了,那么就是客观宕机

sdown 达成的条件很简单,如果一个哨兵 ping 一个 master,超过了 is-master-down-after-milliseconds 指定的毫秒数之后,就主观认为 master 宕机

sdown 到 odown 转换的条件很简单,如果一个哨兵在指定时间内,收到了 quorum 指定数量的其他哨兵也认为那个 master 是 sdown 了,那么就认为是 odown 了,客观认为 master 宕机

# 2、哨兵集群的自动发现机制

哨兵互相之间的发现,是通过 redis 的 pub/sub 系统实现的,每个哨兵都会往__sentinel__:hello 这个 channel 里发送一个消息,这时候所有其他哨兵都可以消费到这个消息,并感知到其他的哨兵的存在

每隔两秒钟,每个哨兵都会往自己监控的某个 master+slaves 对应的__sentinel__:hello channel 里发送一个消息,内容是自己的 host、ip 和 runid 还有对这个 master 的监控配置

每个哨兵也会去监听自己监控的每个 master+slaves 对应的__sentinel__:hello channel,然后去感知到同样在监听这个 master+slaves 的其他哨兵的存在

每个哨兵还会跟其他哨兵交换对 master 的监控配置,互相进行监控配置的同步

# 3、slave 配置的自动纠正

哨兵会负责自动纠正 slave 的一些配置,比如 slave 如果要成为潜在的 master 候选人,哨兵会确保 slave 在复制现有 master 的数据;如果 slave 连接到了一个错误的 master 上,比如故障转移之后,那么哨兵会确保它们连接到正确的 master 上

# 4、slave->master 选举算法

如果一个 master 被认为 odown 了,而且 majority 哨兵都允许了主备切换,那么某个哨兵就会执行主备切换操作,此时首先要选举一个 slave 来

会考虑 slave 的一些信息

(1)跟 master 断开连接的时长 (2)slave 优先级 (3)复制 offset (4)run id

如果一个 slave 跟 master 断开连接已经超过了 down-after-milliseconds 的 10 倍,外加 master 宕机的时长,那么 slave 就被认为不适合选举为 master

(down-after-milliseconds * 10) + milliseconds_since_master_is_in_SDOWN_state

接下来会对 slave 进行排序

(1)按照 slave 优先级进行排序,slave priority 越低,优先级就越高 (2)如果 slave priority 相同,那么看 replica offset,哪个 slave 复制了越多的数据,offset 越靠后,优先级就越高 (3)如果上面两个条件都相同,那么选择一个 run id 比较小的那个 slave

# 5、quorum 和 majority

每次一个哨兵要做主备切换,首先需要 quorum 数量的哨兵认为 odown,然后选举出一个哨兵来做切换,这个哨兵还得得到 majority 哨兵的授权,才能正式执行切换

如果 quorum < majority,比如 5 个哨兵,majority 就是 3,quorum 设置为 2,那么就 3 个哨兵授权就可以执行切换

但是如果 quorum >= majority,那么必须 quorum 数量的哨兵都授权,比如 5 个哨兵,quorum 是 5,那么必须 5 个哨兵都同意授权,才能执行切换

# 6、configuration epoch

哨兵会对一套 redis master+slave 进行监控,有相应的监控的配置

执行切换的那个哨兵,会从要切换到的新 master(salve->master)那里得到一个 configuration epoch,这就是一个 version 号,每次切换的 version 号都必须是唯一的

如果第一个选举出的哨兵切换失败了,那么其他哨兵,会等待 failover-timeout 时间,然后接替继续执行切换,此时会重新获取一个新的 configuration epoch,作为新的 version 号

# 7、configuraiton 传播

哨兵完成切换之后,会在自己本地更新生成最新的 master 配置,然后同步给其他的哨兵,就是通过之前说的 pub/sub 消息机制

这里之前的 version 号就很重要了,因为各种消息都是通过一个 channel 去发布和监听的,所以一个哨兵完成一次新的切换之后,新的 master 配置是跟着新的 version 号的

其他的哨兵都是根据版本号的大小来更新自己的 master 配置的

# 总结

redis 高并发:主从架构,一主多从,一般来说,很多项目其实就足够了,单主用来写入数据,单机几万 QPS,多从用来查询数据,多个从实例可以提供每秒 10 万的 QPS。

redis 高并发的同时,还需要容纳大量的数据:一主多从,每个实例都容纳了完整的数据,比如 redis 主就 10G 的内存量,其实你最多只能容纳 10g 的数据量。如果你的缓存要容纳的数据量很大,达到了几十 g,甚至几百 g,或者是几 t,那你就需要 redis 集群,而且用 redis 集群之后,可以提供可能每秒几十万的读写并发。

redis 高可用:如果你做主从架构部署,其实就是加上哨兵就可以了,就可以实现,任何一个实例宕机,自动会进行主备切换。