如何从0到1构建一个稳定、高性能的Redis集群?(附16张图解)

导语 |  Redis持久化、主从复制、哨兵、分片集群,每个概念单独来看都很容易理解,但它们之间存在哪些联系?Redis为什么会演化出这几种架构模式?这篇文章告诉你答案。


引言


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

  • 我使用Redis的场景很简单,只使用单机版Redis会有什么问题吗?

  • 我的Redis故障宕机了,数据丢失了怎么办?如何能保证我的业务应用不受影响?

  • 为什么需要主从集群?它有什么优势?

  • 什么是分片集群?我真的需要分片集群吗?

如果你对Redis已经有些了解,肯定也听说过「数据持久化、主从复制、哨兵、分片集群」这些概念,它们之间又有什么区别和联系呢?

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

在这个过程中,你可以了解到Redis为了做到稳定、高性能,都采取了哪些优化方案,以及为什么要这么做?

掌握了这些原理,这样平时你在使用Redis时,就能够做到「游刃有余」。


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

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

假设现在你有一个业务应用,需要引入Redis来提高应用的性能,此时你可以选择部署一个单机版的Redis来使用,就像这样:

如何从0到1构建一个稳定、高性能的Redis集群?(附16张图解)

这个架构非常简单,你的业务应用可以把Redis当做缓存来使用,从MySQL中查询数据,然后写入到Redis中,之后业务应用再从Redis中读取这些数据,由于Redis的数据都存储在内存中,所以这个速度飞快。

如果你的业务体量并不大,那这样的架构模型基本可以满足你的需求。是不是很简单?

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

突然有一天,你的Redis因为某些原因宕机了,这时你的所有业务流量,都会打到后端MySQL上,MySQL压力剧增,严重的话甚至会压垮MySQL。

如何从0到1构建一个稳定、高性能的Redis集群?(附16张图解)

这时你应该怎么办?

我猜你的方案肯定是,赶紧重启Redis,让它可以继续提供服务。


但是,因为之前Redis中的数据都在内存中,尽管你现在把Redis重启了,之前的数据也都丢失了(假设没开持久化)。重启后的Redis虽然可以正常工作,但是由于Redis中没有任何数据,业务流量还是都会打到后端MySQL上,MySQL的压力还是很大。

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

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

如果采用这种方式,当Redis重启时,我们把磁盘中的数据快速「恢复」到内存中,这样它就可以继续正常提供服务了。

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

二、数据持久化:有备无患

现在,你设想的Redis数据持久化是这样的:

如何从0到1构建一个稳定、高性能的Redis集群?(附16张图解)

但是,数据持久化具体应该怎么做呢?

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

如何从0到1构建一个稳定、高性能的Redis集群?(附16张图解)

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

但仔细想一下,这个方案有个问题:客户端的每次写操作,既需要写内存,又需要写磁盘,而写磁盘的耗时相比于写内存来说,肯定要慢很多!这势必会影响到Redis的性能

如何规避这个问题?

这时我们需要分析写磁盘的细节问题了。

我们都知道,要把内存数据写到磁盘,其实是分2步的:

  • 程序写文件的PageCache(write)

  • 把PageCache刷到磁盘(fsync)

具体就是下图这样:

如何从0到1构建一个稳定、高性能的Redis集群?(附16张图解)

数据持久化最粗暴的思路就是上面提到的那样,写完Redis内存后,同步写PageCache+fsync磁盘,当然,这样肯定因为磁盘拖慢整个写入速度。

如何优化?也很简单,我们可以这样做:Redis写内存由主线程来做,写内存完成后就给客户端返回结果,然后Redis用「另一个线程」去写磁盘,这样就可以避免主线程写磁盘对性能的影响

如何从0到1构建一个稳定、高性能的Redis集群?(附16张图解)

这种持久化方案,其实就是我们经常听到的Redis AOF(Append Only File)。

Redis AOF持久化提供了3种刷盘机制

  • appendfsync always:主线程同步fsync

  • appendfsync no:由OS fsync

  • appendfsync everysec:后台线程每间隔1秒fsync

解决了数据实时持久化,我们还会面临另一个问题,数据实时写入AOF,随着时间的推移,AOF文件会越来越大,那使用AOF恢复时变得非常慢,这该怎么办?

Redis很贴心地提供了AOF rewrite方案,俗称AOF「瘦身」,顾名思义,就是压缩AOF的体积。

因为AOF里记录的都是每一次写命令,例如执行set k1 v1,set k1 v2,其实我们只关心数据的最终版本v2就可以了。AOF rewrite正是利用了这个特点,在AOF体积越来越大时(超过设定阈值),Redis就会定期重写一份新的AOF,这个新的AOF只记录数据的最终版本就可以了。

如何从0到1构建一个稳定、高性能的Redis集群?(附16张图解)

这样就可以压缩AOF体积。

除此之外,我们可以换个角度,思考一下还有什么方式可以持久化数据?

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

回忆一下,我们在使用Redis时,通常把它用作什么场景?

是的,缓存。

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

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

那什么是数据快照呢?

简单来讲,你可以这么理解:

  • 你把Redis想象成一个水杯,向Redis写入数据,就相当于往这个杯子里倒水。

  • 此时你拿一个相机给这个水杯拍一张照片,拍照的这一瞬间,照片中记录到这个水杯中水的容量,就是水杯的数据快照。

如何从0到1构建一个稳定、高性能的Redis集群?(附16张图解)


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

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

基于这个方案,我们可以「定时」给Redis做数据快照,把数据持久化到磁盘上。

如何从0到1构建一个稳定、高性能的Redis集群?(附16张图解)

这种方案就是我们经常听到的Redis RDB,RDB采用「定时快照」的方式进行数据持久化,它的优点是:

  • 持久化文件体积小(二进制+压缩)

  • 写盘频率低(定时写入)

缺点也很明显,因为是定时持久化,数据肯定没有AOF实时持久化完整,如果你的Redis只当做缓存,对于丢失数据不敏感(可从后端的数据库查询),那这种持久化方式是非常合适的。

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

  • 业务对于数据丢失不敏感,选RDB。

  • 业务对数据完整性要求比较高,选AOF。

理解了RDB和AOF,我们再进一步思考一下,有没有什么办法,既可以保证数据完整性,还能让持久化文件体积更小,恢复更快呢


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

  • RDB以二进制+数据压缩方式存储,文件体积小。

  • AOF记录每一次写命令,数据最全。

我们可否利用它们各自的优势呢?


当然可以,这就是Redis的「混合持久化」。


要想数据完整性更高,肯定就不能只用RDB了,重点还是要放在AOF优化上。

具体来说,当AOF在做rewrite时,Redis先以RDB格式在AOF文件中写入一个数据快照,再把在这期间产生的每一个写命令,追加到AOF文件中

因为RDB是二进制压缩写入的,这样AOF文件体积就变得更小了。

如何从0到1构建一个稳定、高性能的Redis集群?(附16张图解)

因为AOF体积进一步压缩,你在使用AOF恢复数据时,这个恢复时间就会更短了!

Redis4.0以上版本才支持混合持久化。

注意:混合持久化是对AOF rewrite的优化,这意味着使用它必须基于AOF+AOF rewrite。

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

但这样就没问题了吗?

仔细想一下,虽然我们已经把持久化的文件优化到最小了,但在恢复数据时依旧是需要时间的,在这期间你的业务应用无法提供服务,这怎么办?

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


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

三、主从复制:多副本

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

如何从0到1构建一个稳定、高性能的Redis集群?(附16张图解)

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

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

  • 缩短不可用时间:master发生宕机,我们可以手动把slave提升为master继续提供服务。

  • 提升读性能:让slave分担一部分读请求,提升应用的整体性能。

如何从0到1构建一个稳定、高性能的Redis集群?(附16张图解)

这个方案不错,不仅节省了数据恢复的时间,还能提升性能。


但它的问题在于:当master宕机时,我们需要「手动」把slave提升为master,这个过程也是需要花费时间的


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

我们是否可以把这个切换的过程,变成自动化?

四、哨兵:故障自动切换

要想自动切换,肯定不能依赖人了。

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

具体如何做?


  • 哨兵每间隔一段时间,询问master是否正常。

  • master正常回复,表示状态正常,回复超时表示异常。

  • 哨兵发现异常,发起主从切换。

如何从0到1构建一个稳定、高性能的Redis集群?(附16张图解)

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

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

如何从0到1构建一个稳定、高性能的Redis集群?(附16张图解)

这个问题怎么解决?

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

如何从0到1构建一个稳定、高性能的Redis集群?(附16张图解)

  • 多个哨兵每间隔一段时间,询问master是否正常。

  • master正常回复,表示状态正常,回复超时表示异常。


  • 一旦有一个哨兵判定master异常(不管是否是网络问题),就询问其它哨兵,如果多个哨兵(设置一个阈值)都认为master异常了,这才判定master确实发生了故障。

  • 多个哨兵经过协商后,判定master故障,则发起主从切换。

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

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

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

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

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

是的,投票。

在选举哨兵领导者时,我们可以制定这样一个选举规则:

  • 每个哨兵都询问其它哨兵,请求对方为自己投票。

  • 每个哨兵只投票给第一个请求投票的哨兵,且只能投票一次。

  • 首先拿到超过半数投票的哨兵,当选为领导者,发起主从切换。

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

什么是共识算法?

我们在多个机器部署哨兵,它们需要共同协作完成一项任务,所以它们就组成了一个「分布式系统」。

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

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

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

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

到这里我们先小结一下。

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

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

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

我们一起来分析一下:

  • 数据怕丢失:持久化(RDB/AOF)

  • 恢复时间久:主从副本(副本随时可切)

  • 手动切换时间长:哨兵集群(自动切换)

  • 读存在压力:扩容副本(读写分离)

  • 写存在压力:一个mater扛不住怎么办?

可见,现在剩下的问题是,当写请求量越来越大时,一个master实例可能就无法承担这么大的写流量了。

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


五、分片集群:横向扩展

什么是「分片集群」?

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

所以,现在的架构模型就变成了这样:

如何从0到1构建一个稳定、高性能的Redis集群?(附16张图解)

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

我们制定规则如下:

  • 每个节点各自存储一部分数据,所有节点数据之和才是全量数据。

  • 制定一个路由规则,对于不同的key,把它路由到固定一个实例上进行读写。

数据分多个实例存储,那寻找key的路由规则需要放在客户端来做,具体就是下面这样:

如何从0到1构建一个稳定、高性能的Redis集群?(附16张图解)

这种方案也叫做「客户端分片」,这个方案的缺点是,客户端需要维护这个路由规则,也就是说,你需要把路由规则写到你的业务代码中

        

如何做到不把路由规则耦合在客户端业务代码中呢?

继续优化,我们可以在客户端和服务端之间增加一个「中间代理层」,这个代理就是我们经常听到的Proxy,路由转发规则,放在这个Proxy层来维护。

这样,客户端就无需关心服务端有多少个Redis节点了,只需要和这个Proxy交互即可。

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

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

如何从0到1构建一个稳定、高性能的Redis集群?(附16张图解)

这种方案的优点在于,客户端无需关心数据转发规则,只需要和Proxy打交道,客户端像操作单机Redis那样去操作后面的集群,简单易用。

架构演进到目前为止,路由规则无论是客户端来做,还是Proxy来做,都是「社区」演进出来的分片解决方案,它们的特点是集群中的Redis节点,都不知道对方的存在,只有客户端或Proxy才会统筹数据写到哪里,从哪里读取,而且它们都依赖哨兵集群负责故障自动切换。

也就是说我们其实就是把多个孤立的Redis节点,自己组合起来使用。

Redis3.0其实就推出了「官方」的Redis Cluster分片方案,但由于推出初期不稳定,所以用的人很少,也因此业界涌现出了各种开源方案,上面讲到的Twemproxy、Codis分片方案就是在这种背景下诞生的。


但随着Redis Cluster方案的逐渐成熟,业界越来越多的公司开始采用官方方案(毕竟官方保证持续维护,Twemproxy、Codis目前都逐渐放弃维护了),Redis Cluster方案比上面讲到的分片方案更简单,它的架构如下。

如何从0到1构建一个稳定、高性能的Redis集群?(附16张图解)

Redis Cluster无需部署哨兵集群,集群内Redis节点通过Gossip协议互相探测健康状态,在故障时可发起自动切换

另外,关于路由转发规则,也不需要客户端自己编写了,Redis Cluster提供了「配套」的SDK,只要客户端升级SDK,就可以和Redis Cluster集成,SDK会帮你找到key对应的Redis节点进行读写,还能自动适配Redis节点的增加和删除,业务侧无感知。

虽然省去了哨兵集群的部署,维护成本降低了不少,但对于客户端升级SDK,对于新业务应用来说,可能成本不高,但对于老业务来讲,「升级成本」还是比较高的,这对于切换官方Redis Cluster方案有不少阻力。

于是,各个公司有开始自研针对Redis Cluster的Proxy,降低客户端的升级成本,架构就变成了这样:

如何从0到1构建一个稳定、高性能的Redis集群?(附16张图解)

这样,客户端无需做任何变更,只需把连接地址切到Proxy上即可,由Proxy负责转发数据,以及应对后面集群增删节点带来的路由变更。

至此,业界主流的Redis分片架构已经成型,当你使用分片集群后,对于未来更大的流量压力,也都可以从容面对了!


总结


总结一下,我们是如何从0到1,再从1到N构建一个稳定、高性能的Redis集群的,从这之中你可以清晰地看到Redis架构演进的整个过程。

  • 数据怕丢->持久化(RDB/AOF)

  • 恢复时间久->主从副本(副本随时可切)

  • 故障手动切换慢->哨兵集群(自动切换)

  • 读存在压力->扩容副本(读写分离)

  • 写存在压力/容量瓶颈->分片集群

  • 分片集群社区方案->Twemproxy、Codis(Redis节点之间无通信,需要部署哨兵,可横向扩容)

  • 分片集群官方方案->Redis Cluster(Redis节点之间Gossip协议,无需部署哨兵,可横向扩容)

  • 业务侧升级困难->Proxy+Redis Cluster(不侵入业务侧)

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

希望这篇文章可以帮你更好理解Redis架构的演进之路。

 作者简介


如何从0到1构建一个稳定、高性能的Redis集群?(附16张图解)

谭帅

腾讯后台开发工程师

腾讯后台开发工程师,目前负责TCS/TCE容器化Redis研发工作,对Redis架构有过深入理解。



0 0 投票数
文章评分

本文转载自Magic Kaito 腾讯云开发者,原文链接:https://mp.weixin.qq.com/s/FaPRY9KyQmZKRtq_k1j5rQ。

(0)
上一篇 2022-08-01 21:59
下一篇 2022-08-01 22:13

相关推荐

订阅评论
提醒
guest
0 评论
内联反馈
查看所有评论
0
希望看到您的想法,请您发表评论x