目录

image.png

单机版Redis

日常我们使用的redis大多都是单机版
image.png

你的业务应用可以把 Redis 当做缓存来使用,从 MySQL 中查询数据,然后写入到 Redis 中,之后业务应用再从 Redis 中读取这些数据,由于 Redis 的数据都存储在内存中,所以这个速度飞快。但是,突然有一天,你的 Redis 因为某些原因宕机了,这时你的所有业务流量,都会打到后端 MySQL 上,这会导致你的 MySQL 压力剧增,严重的话甚至会压垮 MySQL。
重启 Redis,虽然可以让它继续提供服务。
但是,因为之前 Redis 中的数据都在内存中,尽管你现在把 Redis 重启了,之前的数据也都丢失了。重启后的 Redis 虽然可以正常工作,但是由于 Redis 中没有任何数据,业务流量还是都会打到后端 MySQL 上,MySQL 的压力还是很大。

有没有什么好的办法解决这个问题?
既然 Redis 只把数据存储在内存中,那是否可以把这些数据也写一份到磁盘上呢?
如果采用这种方式,当 Redis 重启时,我们把磁盘中的数据快速恢复到内存中,这样它就可以继续正常提供服务了。

Redis数据持久化

image.png

RDB

RDB:只持久化某一时刻的数据快照到磁盘上(创建一个子进程来做)

Redis 写内存由主线程来做,写内存完成后就给客户端返回结果,然后 Redis 用另一个线程去写磁盘,这样就可以避免主线程写磁盘对性能的影响

思考一下还有什么方式可以持久化数据?

AOF

AOF:每一次写操作都持久到磁盘(主线程写内存,根据策略可以配置由主线程还是子线程进行数据持久化)

通常我们是把 Redis 当做缓存来用,这意味着尽管 Redis 中没有保存全量数据,对于不在缓存中的数据,我们的业务应用依旧可以通过查询后端数据库得到结果,只不过查询后端数据的速度会慢一点而已,但对业务结果其实是没有影响的。

基于这个特点,我们的 Redis 数据持久化还可以用「数据快照」的方式来做。

数据快照:

  • 你把 Redis 想象成一个水杯,向 Redis 写入数据,就相当于往这个杯子里倒水
  • 此时你拿一个相机给这个水杯拍一张照片,拍照的这一瞬间,照片中记录到这个水杯中水的容量,就是水杯的数据快照

image.png

也就是说,Redis 的数据快照,是记录某一时刻下 Redis 中的数据,然后只需要把这个数据快照写到磁盘上就可以了。

它的优势在于,只在需要持久化时,把数据「一次性」写入磁盘,其它时间都不需要操作磁盘。
基于这个方案,我们可以定时给 Redis 做数据快照,把数据持久化到磁盘上。

区别

  1. RDB 采用二进制 + 数据压缩的方式写磁盘,这样文件体积小,数据恢复速度也快
  2. AOF 记录的是每一次写命令,数据最全,但文件体积大,数据恢复速度慢

选择持久化方案

如果让你来选择持久化方案,你可以这样选择:

  1. 如果你的业务对于数据丢失不敏感,采用 RDB 方案持久化数据
  2. 如果你的业务对数据完整性要求比较高,采用 AOF 方案持久化数据

假设你的业务对 Redis 数据完整性要求比较高,选择了 AOF 方案,那此时你又会遇到这些问题:

  1. AOF 记录每一次写操作,随着时间增长,AOF 文件体积会越来越大
  2. 这么大的 AOF 文件,在数据恢复时变得非常慢

    缩小 AOF 文件

    1、定时 瘦身

    由于 AOF 文件中记录的都是每一次写操作,但对于同一个 key 可能会发生多次修改,我们只保留最后一次被修改的值,是不是也可以?
    是的,这就是我们经常听到的「AOF rewrite」,你也可以把它理解为 AO「瘦身」

    我们可以对 AOF 文件
    定时 rewrite**,避免这个文件体积持续膨胀,这样在恢复时就可以缩短恢复时间了。

image.png

2、混合持久化

利用RDB和AOF的特点进行混合持久化操作
具体来说,当 AOF rewrite 时,Redis 先以 RDB 格式在 AOF 文件中写入一个数据快照,再把在这期间产生的每一个写命令,追加到 AOF 文件中。因为 RDB 是二进制压缩写入的,这样 AOF 文件体积就变得更小了

image.png
此时,你在使用 AOF 文件恢复数据时,这个恢复时间就会更短了!

Redis 4.0 以上版本才支持混合持久化。

这么一番优化,你的 Redis 再也不用担心实例宕机了,当发生宕机时,你就可以用持久化文件快速恢复 Redis 中的数据。

主从复制:多副本

可以部署多个 Redis 实例,架构模型就变成了这样:

设计架构image.png

我们这里把实时读写的节点叫做 master,另一个实时同步数据的节点叫做 slave。

优势:

采用多副本的方案,它的优势是:

  1. 缩短不可用时间:master 发生宕机,我们可以手动把 slave 提升为 master 继续提供服务
  2. 提升读性能:让 slave 分担一部分读请求,提升应用的整体性能

image.png

缺陷:

它的问题在于:当 master 宕机时,我们需要「手动」把 slave 提升为 master,这个过程也是需要花费时间的。
虽然比恢复数据要快得多,但还是需要人工介入处理。一旦需要人工介入,就必须要算上人的反应时间、操作时间,所以,在这期间你的业务应用依旧会受到影响

对于这种情况,我们需要一个「故障自动切换」机制,这就是我们经常听到的「哨兵」所具备的能力。

哨兵:故障自动切换

我们可以引入一个「观察者」,让这个观察者去实时监测 master 的健康状态,这个观察者就是「哨兵」。

设计架构

  1. 哨兵每间隔一段时间,询问 master 是否正常
  2. master 正常回复,表示状态正常,回复超时表示异常
  3. 哨兵发现异常,发起主从切换

image.png

问题:

1、网络误判状态

如果 master 状态正常,但这个哨兵在询问 master 时,它们之间的网络发生了问题,那这个哨兵可能会误判

image.png

我们可以部署多个哨兵,让它们分布在不同的机器上,它们一起监测 master 的状态,流程就变成了这样:

  1. 多个哨兵每间隔一段时间,询问 master 是否正常
  2. master 正常回复,表示状态正常,回复超时表示异常
  3. 一旦有一个哨兵判定 master 异常(不管是否是网络问题),就询问其它哨兵,如果多个哨兵(设置一个阈值)都认为 master 异常了,这才判定 master 确实发生了故障
  4. 多个哨兵经过协商后,判定 master 故障,则发起主从切换

所以,我们用多个哨兵互相协商来判定 master 的状态,这样一来,就可以大大降低误判的概率。

2、谁发起主从切换

由哪个哨兵来发起主从切换呢?
答案是,选出一个哨兵「领导者」,由这个领导者进行主从切换。
这个领导者怎么选?
可以采用共识算法(在分布式系统领域,多个节点如何就一个问题达成共识的算法,就叫共识算法。
这个算法还规定节点的数量必须是奇数个
**

  1. 每个哨兵都询问其它哨兵,请求对方为自己投票
  2. 每个哨兵只投票给第一个请求投票的哨兵,且只能投票一次
  3. 首先拿到超过半数投票的哨兵,当选为领导者,发起主从切换

image.png

分片集群:横向扩展

「分片集群」?
简单来讲,一个实例扛不住写压力,那我们是否可以部署多个实例,然后把这些实例按照一定规则组织起来,把它们当成一个整体,对外提供服务,这样不就可以解决集中写一个实例的瓶颈问题吗?

设计架构

image.png

如何实现

这么多实例如何组织呢?

我们制定规则如下:

  1. 每个节点各自存储一部分数据,所有节点数据之和才是全量数据
  2. 制定一个路由规则,对于不同的 key,把它路由到固定一个实例上进行读写

而分片集群根据路由规则所在位置的不同,还可以分为两大类:

  1. 客户端分片
  2. 服务端分片

客户端分片

客户端分片指的是,key 的路由规则放在客户端来做,就是下面这样:
image.png
这个方案的缺点是,客户端需要维护这个路由规则,也就是说,你需要把路由规则写到你的业务代码中

如何做到不把路由规则耦合在业务代码中呢?
你可以这样优化,把这个路由规则封装成一个模块,当需要使用时,集成这个模块就可以了。
这就是 Redis Cluster 的采用的方案。
image.png

Redis Cluster 内置了哨兵逻辑,无需再部署哨兵。

当你使用 Redis Cluster 时,你的业务应用需要使用配套的 Redis SDK,这个 SDK 内就集成好了路由规则,不需要你自己编写了。

服务端分片

路由规则不放在客户端来做,而是在客户端和服务端之间增加一个「中间代理层」,这个代理就是我们经常听到的 Proxy
而数据的路由规则,就放在这个 Proxy 层来维护。样一来,你就无需关心服务端有多少个 Redis 节点了,只需要和这个 Proxy 交互即可。

Proxy 会把你的请求根据路由规则,转发到对应的 Redis 节点上,而且,当集群实例不足以支撑更大的流量请求时,还可以横向扩容,添加新的 Redis 实例提升性能,这一切对于你的客户端来说,都是透明无感知的。

业界开源的 Redis 分片集群方案,例如 Twemproxy、Codis 就是采用的这种方案。

image.png

附录:

构建Redis集群 - 图17