16张图带你吃透Redis架构演进

微信搜索关注「水滴与银弹」公众号,第一时间获取优质技术干货。7年资深后端研发,给你呈现不同的技术视角。程序员

你们好,我是 Kaito。算法

这篇文章我想和你聊一聊 Redis 的架构演化之路。数据库

现现在 Redis 变得愈来愈流行,几乎在不少项目中都要被用到,不知道你在使用 Redis 时,有没有思考过,Redis 究竟是如何稳定、高性能地提供服务的?后端

你也能够尝试回答一下如下这些问题:缓存

  • 我使用 Redis 的场景很简单,只使用单机版 Redis 会有什么问题吗?
  • 个人 Redis 故障宕机了,数据丢失了怎么办?如何能保证个人业务应用不受影响?
  • 为何须要主从集群?它有什么优点?
  • 什么是分片集群?我真的须要分片集群吗?
  • ...

若是你对 Redis 已经有些了解,确定也据说过数据持久化、主从复制、哨兵这些概念,它们之间又有什么区别和联系呢?微信

若是你存在这样的疑惑,这篇文章,我会从 0 到 1,再从 1 到 N,带你一步步构建出一个稳定、高性能的 Redis 集群。markdown

在这个过程当中,你能够了解到 Redis 为了作到稳定、高性能,都采起了哪些优化方案,以及为何要这么作?网络

掌握了这些原理,这样平时你在使用 Redis 时,就可以作到「游刃有余」。架构

这篇文章干货不少,但愿你能够耐心读完。负载均衡

从最简单的开始:单机版 Redis

首先,咱们从最简单的场景开始。

假设如今你有一个业务应用,须要引入 Redis 来提升应用的性能,此时你能够选择部署一个单机版的 Redis 来使用,就像这样:

这个架构很是简单,你的业务应用能够把 Redis 当作缓存来使用,从 MySQL 中查询数据,而后写入到 Redis 中,以后业务应用再从 Redis 中读取这些数据,因为 Redis 的数据都存储在内存中,因此这个速度飞快。

若是你的业务体量并不大,那这样的架构模型基本能够知足你的需求。是否是很简单?

随着时间的推移,你的业务体量逐渐发展起来了,Redis 中存储的数据也愈来愈多,此时你的业务应用对 Redis 的依赖也愈来愈重。

可是,忽然有一天,你的 Redis 由于某些缘由宕机了,这时你的全部业务流量,都会打到后端 MySQL 上,这会致使你的 MySQL 压力剧增,严重的话甚至会压垮 MySQL。

这时你应该怎么办?

我猜你的方案确定是,赶忙重启 Redis,让它能够继续提供服务。

可是,由于以前 Redis 中的数据都在内存中,尽管你如今把 Redis 重启了,以前的数据也都丢失了。重启后的 Redis 虽然能够正常工做,可是因为 Redis 中没有任何数据,业务流量仍是都会打到后端 MySQL 上,MySQL 的压力仍是很大。

这可怎么办?你陷入了沉思。

有没有什么好的办法解决这个问题?

既然 Redis 只把数据存储在内存中,那是否能够把这些数据也写一份到磁盘上呢?

若是采用这种方式,当 Redis 重启时,咱们把磁盘中的数据快速恢复到内存中,这样它就能够继续正常提供服务了。

是的,这是一个很好的解决方案,这个把内存数据写到磁盘上的过程,就是「数据持久化」。

数据持久化:有备无患

如今,你设想的 Redis 数据持久化是这样的:

可是,数据持久化具体应该怎么作呢?

我猜你最容易想到的一个方案是,Redis 每一次执行写操做,除了写内存以外,同时也写一份到磁盘上,就像这样:

没错,这是最简单直接的方案。

但仔细想一下,这个方案有个问题:客户端的每次写操做,既须要写内存,又须要写磁盘,而写磁盘的耗时相比于写内存来讲,确定要慢不少!这势必会影响到 Redis 的性能。

如何规避这个问题?

咱们能够这样优化:Redis 写内存由主线程来作,写内存完成后就给客户端返回结果,而后 Redis 用另外一个线程去写磁盘,这样就能够避免主线程写磁盘对性能的影响。

这确实是一个好方案。除此以外,咱们能够换个角度,思考一下还有什么方式能够持久化数据?

这时你就要结合 Redis 的使用场景来考虑了。

回忆一下,咱们在使用 Redis 时,一般把它用做什么场景?

是的,缓存。

把 Redis 当作缓存来用,意味着尽管 Redis 中没有保存全量数据,对于不在缓存中的数据,咱们的业务应用依旧能够经过查询后端数据库获得结果,只不过查询后端数据的速度会慢一点而已,但对业务结果实际上是没有影响的。

基于这个特色,咱们的 Redis 数据持久化还能够用「数据快照」的方式来作。

那什么是数据快照呢?

简单来说,你能够这么理解:

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

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

它的优点在于,只在须要持久化时,把数据「一次性」写入磁盘,其它时间都不须要操做磁盘。

基于这个方案,咱们能够定时给 Redis 作数据快照,把数据持久化到磁盘上。

其实,上面说的这些持久化方案,就是 Redis 的「RDB」和「AOF」:

  • RDB:只持久化某一时刻的数据快照到磁盘上(建立一个子进程来作)
  • AOF:每一次写操做都持久到磁盘(主线程写内存,根据策略能够配置由主线程仍是子线程进行数据持久化)

它们的区别除了上面讲到的,还有如下特色:

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

若是让你来选择持久化方案,你能够这样选择:

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

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

  1. AOF 记录每一次写操做,随着时间增加,AOF 文件体积会愈来愈大
  2. 这么大的 AOF 文件,在数据恢复时变得很是慢

这怎么办?数据完整性要求变高了,恢复数据也变困难了?有没有什么方法,能够缩小文件体积?提高恢复速度呢?

咱们继续来分析 AOF 的特色。

因为 AOF 文件中记录的都是每一次写操做,但对于同一个 key 可能会发生屡次修改,咱们只保留最后一次被修改的值,是否是也能够?

是的,这就是咱们常常听到的「AOF rewrite」,你也能够把它理解为 AOF 「瘦身」。

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

再进一步思考一下,还有没有办法继续缩小 AOF 文件?

回顾一下咱们前面讲到的,RDB 和 AOF 各自的特色:

  1. RDB 以二进制 + 数据压缩方式存储,文件体积小
  2. AOF 记录每一次写命令,数据最全

咱们能否利用它们各自的优点呢?

固然能够,这就是 Redis 的「混合持久化」。

具体来讲,当 AOF rewrite 时,Redis 先以 RDB 格式在 AOF 文件中写入一个数据快照,再把在这期间产生的每个写命令,追加到 AOF 文件中。由于 RDB 是二进制压缩写入的,这样 AOF 文件体积就变得更小了。

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

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

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

但这样就没问题了吗?

仔细想一下,虽然咱们已经把持久化的文件优化到最小了,但在恢复数据时依旧是须要时间的,在这期间你的业务应用仍是会受到影响,这怎么办?

咱们来分析有没有更好的方案。

一个实例宕机,只能用恢复数据来解决,那咱们是否能够部署多个 Redis 实例,而后让这些实例数据保持实时同步,这样当一个实例宕机时,咱们在剩下的实例中选择一个继续提供服务就行了。

没错,这个方案就是接下来要讲的「主从复制:多副本」。

主从复制:多副本

此时,你能够部署多个 Redis 实例,架构模型就变成了这样:

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

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

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

这个方案不错,不只节省了数据恢复的时间,还能提高性能,那它有什么问题吗?

你能够思考一下。

其实,它的问题在于:当 master 宕机时,咱们须要「手动」把 slave 提高为 master,这个过程也是须要花费时间的。

虽然比恢复数据要快得多,但仍是须要人工介入处理。一旦须要人工介入,就必需要算上人的反应时间、操做时间,因此,在这期间你的业务应用依旧会受到影响。

怎么解决这个问题?咱们是否能够把这个切换的过程,变成自动化呢?

对于这种状况,咱们须要一个「故障自动切换」机制,这就是咱们常常听到的「哨兵」所具有的能力。

哨兵:故障自动切换

如今,咱们能够引入一个「观察者」,让这个观察者去实时监测 master 的健康状态,这个观察者就是「哨兵」。

具体如何作?

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

有了这个方案,就不须要人去介入处理了,一切就变得自动化了,是否是很爽?

但这里还有一个问题,若是 master 状态正常,但这个哨兵在询问 master 时,它们之间的网络发生了问题,那这个哨兵可能会误判。

这个问题怎么解决?

答案是,咱们能够部署多个哨兵,让它们分布在不一样的机器上,它们一块儿监测 master 的状态,流程就变成了这样:

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

因此,咱们用多个哨兵互相协商来断定 master 的状态,这样一来,就能够大大下降误判的几率。

哨兵协商断定 master 异常后,这里还有一个问题:由哪一个哨兵来发起主从切换呢?

答案是,选出一个哨兵「领导者」,由这个领导者进行主从切换。

问题又来了,这个领导者怎么选?

想象一下,在现实生活中,选举是怎么作的?

是的,投票。

在选举哨兵领导者时,咱们能够制定这样一个选举规则:

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

其实,这个选举的过程就是咱们常常听到的:分布式系统领域中的「共识算法」。

什么是共识算法?

咱们在多个机器部署哨兵,它们须要共同协做完成一项任务,因此它们就组成了一个「分布式系统」。

在分布式系统领域,多个节点如何就一个问题达成共识的算法,就叫共识算法。

在这个场景下,多个哨兵共同协商,选举出一个都承认的领导者,就是使用共识算法完成的。

这个算法还规定节点的数量必须是奇数个,这样能够保证系统中即便有节点发生了故障,剩余超过「半数」的节点状态正常,依旧能够提供正确的结果,也就是说,这个算法还兼容了存在故障节点的状况。

共识算法在分布式系统领域有不少,例如 Paxos、Raft,哨兵选举领导者这个场景,使用的是 Raft 共识算法,由于它足够简单,且易于实现。

如今,咱们用多个哨兵共同监测 Redis 的状态,这样一来,就能够避免误判的问题了,架构模型就变成了这样:

好了,到这里咱们先小结一下。

你的 Redis 从最简单的单机版,通过数据持久化、主从多副本、哨兵集群,这一路优化下来,你的 Redis 无论是性能仍是稳定性,都愈来愈高,就算节点发生故障,也不用担忧了。

你的 Redis 以这样的架构模式部署,基本上就能够稳定运行很长时间了。

...

随着时间的发展,你的业务体量开始迎来了爆炸性增加,此时你的架构模型,还可以承担这么大的流量吗?

咱们一块儿来分析一下:

  1. 稳定性:Redis 故障宕机,咱们有哨兵 + 副本,能够自动完成主从切换
  2. 性能:读请求量增加,咱们能够再部署多个 slave,读写分离,分担读压力
  3. 性能:写请求量增加,但咱们只有一个 master 实例,这个实例达到瓶颈怎么办?

看到了么,当你的写请求量愈来愈大时,一个 master 实例可能就没法承担这么大的写流量了。

要想完美解决这个问题,此时你就须要考虑使用「分片集群」了。

分片集群:横向扩展

什么是「分片集群」?

简单来说,一个实例扛不住写压力,那咱们是否能够部署多个实例,而后把这些实例按照必定规则组织起来,把它们当成一个总体,对外提供服务,这样不就能够解决集中写一个实例的瓶颈问题吗?

因此,如今的架构模型就变成了这样:

如今问题又来了,这么多实例如何组织呢?

咱们制定规则以下:

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

而分片集群根据路由规则所在位置的不一样,还能够分为两大类:

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

客户端分片指的是,key 的路由规则放在客户端来作,就是下面这样:

这个方案的缺点是,客户端须要维护这个路由规则,也就是说,你须要把路由规则写到你的业务代码中。

如何作到不把路由规则耦合在业务代码中呢?

你能够这样优化,把这个路由规则封装成一个模块,当须要使用时,集成这个模块就能够了。

这就是 Redis Cluster 的采用的方案。

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

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

再来看服务端分片。

这种方案指的是,路由规则不放在客户端来作,而是在客户端和服务端之间增长一个「中间代理层」,这个代理就是咱们常常听到的 Proxy。

而数据的路由规则,就放在这个 Proxy 层来维护。

这样一来,你就无需关心服务端有多少个 Redis 节点了,只须要和这个 Proxy 交互便可。

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

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

分片集群在数据扩容时,还涉及到了不少细节,这块内容不是本文章重点,因此暂不详述。

至此,当你使用分片集群后,对于将来更大的流量压力,均可以从容面对了!

总结

好了,咱们来总结一下,咱们是如何一步步构建一个稳定、高性能的 Redis 集群的。

首先,在使用最简单的单机版 Redis 时,咱们发现当 Redis 故障宕机后,数据没法恢复的问题,所以咱们想到了「数据持久化」,把内存中的数据也持久化到磁盘上一份,这样 Redis 重启后就能够从磁盘上快速恢复数据。

在进行数据持久化时,咱们又面临如何更高效地将数据持久化到磁盘的问题。以后咱们发现 Redis 提供了 RDB 和 AOF 两种方案,分别对应了数据快照和实时的命令记录。当咱们对数据完整性要求不高时,能够选择 RDB 持久化方案。若是对于数据完整性要求较高,那么能够选择 AOF 持久化方案。

可是咱们又发现,AOF 文件体积会随着时间增加变得愈来愈大,此时咱们想到的优化方案是,使用 AOF rewrite 的方式对其进行瘦身,减少文件体积,再后来,咱们发现能够结合 RDB 和 AOF 各自的优点,在 AOF rewrite 时使用二者结合的「混合持久化」方式,又进一步减少了 AOF 文件体积。

以后,咱们发现尽管能够经过数据恢复的方式还原数据,但恢复数据也是须要花费时间的,这意味着业务应用仍是会受到影响。咱们进一步优化,采用「多副本」的方案,让多个实例保持实时同步,当一个实例故障时,能够手动把其它实例提高上来继续提供服务。

可是这样也有问题,手动提高实例上来,须要人工介入,人工介入操做也须要时间,咱们开始想办法把这个流程变得自动化,因此咱们又引入了「哨兵」集群,哨兵集群经过互相协商的方式,发现故障节点,并能够自动完成切换,这样就大幅下降了对业务应用的影响。

最后,咱们把关注点聚焦在如何支撑更大的写流量上,因此,咱们又引入了「分片集群」来解决这个问题,让多个 Redis 实例分摊写压力,将来面对更大的流量,咱们还能够添加新的实例,横向扩展,进一步提高集群的性能。

至此,咱们的 Redis 集群才得以长期稳定、高性能的为咱们的业务提供服务。

这里我画了一个思惟导图,方便你更好地去理解它们之间的关系,以及演化的过程。

后记

看到这里,我想你对如何构建一个稳定、高性能的 Redis 集群问题时,应该会有本身的看法了。

其实,这篇文章所讲的优化思路,围绕的主题就是「架构设计」的核心思想:

  • 高性能:读写分离、分片集群
  • 高可用:数据持久化、多副本、故障自动切换
  • 易扩展:分片集群、横向扩展

当咱们讲到哨兵集群、分片集群时,这还涉及到了「分布式系统」相关的知识:

  • 分布式共识:哨兵领导者选举
  • 负载均衡:分片集群数据分片、数据路由

固然,除了 Redis 以外,对于构建任何一个数据集群,你均可以沿用这个思路去思考、去优化,看看它们究竟是如何作的。

例如当你在使用 MySQL 时,你能够思考一下 MySQL 与 Redis 有哪些不一样?MySQL 为了作到高性能、高可用,又是如何作的?其实思路都是相似的。

咱们如今处处可见分布式系统、数据集群,我但愿经过这篇文章,你能够理解这些软件是如何一步步演化过来的,在演化过程当中,它们遇到了哪些问题,为了解决这些问题,这些软件的设计者设计了怎样的方案,作了哪些取舍?

你只有了解了其中的原理,掌握了分析问题、解决问题的能力,这样在之后的开发过程当中,或是学习其它优秀软件时,就能快速地找到「重点」,在最短的时间掌握它,并能在实际应用中发挥它们的优点。

其实这个思考过程,也是作「架构设计」的思路。在作软件架构设计时,你面临的场景就是发现问题、分析问题、解决问题,一步步去演化、升级你的架构,最后在性能、可靠性方面达到一个平衡。虽然各类软件层出不穷,但架构设计的思想不会变,我但愿你真正吸取的是这些思想,这样才能够作到以不变应万变。

qr_block.jpg

想看更多硬核技术文章?欢迎关注个人公众号「水滴与银弹」。

我是 Kaito,是一个对于技术有思考的资深后端程序员,在个人文章中,我不只会告诉你一个技术点是什么,还会告诉你为何这么作?我还会尝试把这些思考过程,提炼成通用的方法论,让你能够应用在其它领域中,作到触类旁通。

相关文章
相关标签/搜索