当前位置: 首页 > 科技观察

如何从0到1搭建稳定高性能的Redis集群?(附16张图)

时间:2023-03-19 15:22:33 科技观察

大家好,我叫快斗。在这篇文章中,我想和大家谈谈Redis架构的演变。如今,Redis越来越流行,几乎在很多项目中都会用到它。大家在使用Redis的时候,有没有想过Redis是如何稳定、高性能的提供服务的呢?您也可以尝试回答以下问题:我使用Redis的场景非常简单。只用单机版的Redis有什么问题吗?我的Redis出现故障,数据丢失了怎么办?如何确保我的业务应用程序不受影响??为什么需要主从集群?它的优点是什么?什么是分片集群?我真的需要分片集群吗?...如果你已经对Redis有所了解,那么你一定听说过数据持久化和主从复制这些概念之间有什么区别和联系?如果你有这样的疑惑,在本文中,我将带你从0到1,再从1到N,一步步带你搭建一个稳定的、高性能的RedisCluster。在这个过程中可以了解到Redis为了稳定和高性能采用了哪些优化方案,为什么?掌握这些原则,让你平时使用Redis时,游刃有余。”。本文干货很多,希望大家耐心看完。从最简单的:StandaloneRedis入手首先,我们从最简单的场景入手。假设你有一个业务应用,需要引入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中,但是对于不在缓存中的数据,我们的业务应用仍然可以通过查询后端数据库得到结果,但是查询后端的速度-end数据会比较慢,但对业务结果没有影响。基于这个特性,我们的Redis数据持久化也可以通过“数据快照”的方式来完成。那么什么是数据快照呢?简单的说,你可以这样理解:你把Redis想象成一个水杯,向Redis写入数据就相当于往杯子里倒水。这时候,你拿着相机拍下水杯。一张照片,在拍照的那一刻,杯子里的水的容量被记录在照片里,也就是杯子的数据快照。也就是说,Redis的数据快照就是将某个时刻的数据记录在Redis中,然后只需要将数据快照写入磁盘即可。它的优点是只在需要持久化的时候才将数据“一次”写入磁盘,其他时候不需要对磁盘进行操作。基于此方案,我们可以周期性的为Redis做数据快照,并将数据持久化到磁盘。其实上面说的持久化方案就是Redis的“RDB”和“AOF”:RDB:只把某个时刻的数据快照持久化到磁盘(创建一个子进程来做)AOF:每次写操作都持久化到磁盘(主线程写入内存,可根据策略配置主线程或子线程持久化数据)。它们的区别除了以上还有以下特点:RDB采用二进制+数据压缩方式写入磁盘,所以文件体积小,数据恢复速度也快。AOF记录每一次写命令,数据最完整,但文件体积大,数据恢复速度慢。如果让你选择持久化方案,你可以这样选择:如果你的业务对数据丢失不敏感,使用RDB方案持久化数据如果你的业务对数据完整性要求比较高,使用AOF方案持久化数据假设你的业务对Redis的数据完整性要求比较高,你选择了AOF方案,那么这个时候你又会遇到这些问题:AOF记录每一次写操作,随着时间的增长,AOF文件的大小会变得越来越大。这么大的AOF文件在数据恢复的时候会变得很慢。我应该怎么办?数据完整性要求变高了,恢复数据就变难了?有什么办法可以减小文件大小吗?提高恢复速度?下面继续分析AOF的特点。由于AOF文件中记录了每一次写操作,但是同一个key可能会发生多次修改,我们只保留最后一次修改的值,是不是也可以呢?是的,这就是我们经常听到的“AOF重写”,你也可以理解为AOF“瘦身”。我们可以定期重写AOF文件,避免文件大小的不断扩大,从而在恢复时缩短恢复时间。再想一想,有没有办法继续缩小AOF文件呢?回顾一下我们前面说的,RDB和AOF各自的特点:RDB采用二进制+数据压缩方式存储,文件体积小。AOF记录了每一个写命令,数据是最多的,能否发挥各自的优势呢?当然,这就是Redis的“混合持久化”。具体来说,在AOFrewrite时,Redis首先以RDB格式在AOF文件中写入一个数据快照,然后将这期间产生的每条写命令追加到AOF文件中。因为RDB是用二进制压缩写的,所以AOF文件的体积变小了。此时,当你使用AOF文件恢复数据时,恢复时间会更短!只有Redis4.0以上的版本才支持混合持久化。有了这样的优化,你的Redis再也不用担心实例宕机了。当发生宕机时,可以使用持久化文件快速恢复Redis中的数据。但这可以吗?仔细想想。虽然我们已经将持久化文件优化到最少,但是恢复数据还是需要时间的。在此期间,您的业务应用程序仍会受到影响。为什么?该怎么办?下面分析一下有没有更好的解决方案。如果实例宕机,只能通过恢复数据来解决。我们是不是可以部署多个Redis实例,然后让这些实例的数据实时同步,这样当一个实例宕机的时候,我们可以选择剩下的一个实例继续提供服务就可以了。没错,这个方案就是接下来要讲的“主从复制:多副本”。主从复制:多副本此时可以部署多个Redis实例,架构模型变成这样:这里我们称实时读写的节点为master,另一个实时同步数据的节点称为奴隶。采用多副本方案的好处是:缩短不可用时间:当master宕机时,我们可以手动将slave提升为master继续提供服务,提高读性能:让slave分担部分读请求并提高应用程序的整体性能。是的,它不仅节省了数据恢复的时间,而且还提高了性能,那么它有什么问题呢?你可以考虑一下。其实它的问题在于,当master宕机时,我们需要“手动”将slave提升为master,这个过程也是需要时间的。虽然比恢复数据快得多,但它仍然需要人工干预。一旦需要人工干预,就必须计算人的反应时间和操作时间,所以在此期间您的业务应用仍然会受到影响。如何解决这个问题呢?我们可以自动化切换过程吗?这种情况下,我们就需要一个“失败自动切换”的机制,也就是我们经常听到的“哨兵”的能力。Sentinel:自动故障转移现在,我们可以引入一个“观察者”来实时监控master的健康状态。这个观察者就是“哨兵”。具体怎么做呢?哨兵每隔一段时间就询问主人是否正常。master正常回复,说明状态正常,回复超时说明异常哨兵发现异常,发起主从切换。自动化很酷吗?但是这里还有一个问题。如果master状态正常,但是sentinel在询问master的时候他们之间的网络有问题,那么sentinel可能会误判。如何解决这个问题呢?答案是我们可以部署多个sentinel,分布在不同的机器上,一起监控master的状态。流程变成这样:多个sentinel每隔一段时间询问master是否正常master回复正常,说明状态正常,回复超时说明异常。一旦某个sentinel判断master出现异常(不管是不是网络问题),就会去询问其他的sentinel。如果多个哨兵(设置一个阈值)都认为master异常,那么就进行判断。师父确实失败了。多个哨兵协商后确定master故障,发起主从切换。因此,我们使用多个哨兵相互协商来确定主人的地位。这样一来,就可以大大降低误判的概率。在sentinel协商确定master异常之后,这里又出现了一个问题:主从切换是由哪个sentinel发起的?答案就是选出一个sentinel“leader”,这个leader会进行主从切换。问题又来了,这个leader是怎么选出来的?想象一下,在现实生活中,选举是如何进行的?是的,投票。在选举哨兵领袖的时候,我们可以制定这样一个选举规则:每个哨兵要求其他哨兵互相请对方为自己投票。每个哨兵只投票给第一个请求投票的哨兵,并且只能投票一次。获得半数以上选票的哨兵被选为领导者并发起主从切换。其实这个选举过程就是我们经常听到的:分布式系统领域的“共识算法”。什么是共识算法?我们把哨兵部署在多台机器上,他们需要协同工作来完成一个任务,所以他们形成了一个“分布式系统”。在分布式系统领域,多个节点如何就一个问题达成共识的算法称为共识算法。在这种情况下,多个哨兵一起协商选出一个他们都认可的领导者,这是使用共识算法完成的。该算法还规定节点数必须为奇数,这样可以保证即使系统中某个节点出现故障,其余“一半”以上的节点处于正常状态,仍能提供正确的结果。也就是说,这个算法也兼容有节点失效的情况。分布式系统领域的共识算法有很多,比如Paxos、Raft,以及哨兵选举领导者的场景。使用Raft共识算法是因为它足够简单,易于实现。现在,我们使用多个哨兵来共同监控Redis的状态。这样就可以避免误判的问题,架构模型就变成了这样:好了,这里先总结一下。你的Redis从最简单的单机版通过数据持久化、主从多副本、sentinel集群进行了优化。你的Redis性能和稳定性越来越高了。不用担心。如果你的Redis部署在这样的架构模式下,基本上可以长期稳定运行。...随着时间的发展,您的业务量开始迎来爆发式增长。这个时候你的架构模型还能承受这么大的流量吗?一起来分析一下:稳定性:Redis故障宕机,我们有Sentinel+copy,可以自动完成主从切换,实例达到瓶颈怎么办?你有没有看到,当你的写请求越来越大的时候,一个Master实例可能无法承受这么大的写流量。要完美解决这个问题,这时候就需要考虑使用“shardedclusters”。Shardedcluster:水平扩展什么是“shardedcluster”?简单的说,一个实例承受不了写的压力,那我们是不是可以部署多个实例,然后把这些实例按照一定的规则组织起来,作为一个整体,对外提供服务,这样行不行?解决单个实例集中写入的瓶颈问题?那么,现在的架构模型就变成了这样:现在问题又来了,那么多实例怎么组织?我们制定如下规则:每个节点存储一部分数据,所有节点数据的总和为全量数据。制定路由规则。对于不同的key,路由到一个固定的实例进行读写。分片集群基于路由规则的位置。可以分为两类:ClientshardingServershardingClientsharding就是把关键的路由规则放在客户端,如下:这种方案的缺点是客户端需要维护这条路由规则,也就是说,您需要将路由规则写入您的业务代码。如何避免将路由规则耦合到业务代码中?可以这样优化,把路由规则封装成一个模块,需要用的时候集成这个模块。这是RedisCluster采用的方案。RedisCluster内置哨兵逻辑,无需部署哨兵。当您使用RedisCluster时,您的业务应用程序需要使用配套的RedisSDK。路由规则都集成在这个SDK里面,不用自己写。让我们再看看服务器端分片。这种方案意味着路由规则不是在客户端完成的,而是在客户端和服务器之间增加了一个“中间代理层”。这个proxy就是我们经常听到的proxy。数据路由规则在代理层维护。这样就不需要关心服务器端有多少个Redis节点,只需要和这个Proxy进行交互即可。Proxy会根据路由规则将你的请求转发到对应的Redis节点。而且,当集群实例不足以支撑更大的流量请求时,还可以横向扩展,增加新的Redis实例来提升性能。这一切对于你的客户来说,都是透明的,难以察觉的。业界开源的Redis分片集群方案,如Twemproxy、Codis等均采用该方案。分片集群的数据扩容涉及到很多细节。该内容不是本文的重点,所以暂时不做详细介绍。至此,当你使用sharding集群,你可以从容面对未来更大的流量压力!总结完了,我们来总结一下我们是如何一步步搭建稳定高性能的Redis集群的。首先,我们在使用最简单的单机版Redis时,发现当Redis崩溃时,数据无法恢复,于是想到了“数据持久化”,将内存中的数据持久化到磁盘中,以便Redis在重启后可以快速从磁盘恢复数据。在进行数据持久化的时候,我们面临着如何更高效的将数据持久化到磁盘的问题。后来发现Redis提供了RDB和AOF两种方案,分别对应数据快照和实时命令记录。当我们对数据完整性要求不高时,可以选择RDB持久化方案。如果对数据完整性要求高,可以选择AOF持久化方案。但是我们也发现随着时间的推移,AOF文件的大小会越来越大。这时候我们想到的优化方案是使用AOFrewrite来减小文件体积,减小文件体积。后来发现可以结合RDB和AOF各自的优点,在AOFrewrite时使用结合两者的“混合持久化”的方式进一步减小AOF文件的大小。后来我们发现,虽然可以通过数据恢复来恢复数据,但是恢复数据是需要时间的,也就是说业务应用还是会受到影响。我们进一步优化并采用了“多副本”的方案,让多个实例实时同步。当一个实例出现故障时,可以手动提升其他实例继续提供服务。但是,这也存在问题。手动升级实例需要人工干预,人工干预也需要时间。我们开始想办法自动化这个过程,所以我们引入了一个“哨兵”集群。哨兵集群相互协商。发现故障节点并自动完成切换,大大降低了对业务应用的影响。最后,我们关注的是如何支持更大的写流量,所以我们引入了“分片集群”来解决这个问题,让多个Redis实例分担写压力。未来,面对更大的流量,我们还可以增加新的实例,横向扩展,进一步提升集群的性能。至此,我们的Redis集群已经能够长期稳定、高性能的为我们的业务提供服务。这里我画了一张思维导图,方便大家更好的理解它们之间的关系以及进化的过程。后记看到这里,我想你应该对如何搭建稳定高性能的Redis集群有了自己的看法。其实本文所讨论的优化思路都是围绕着“架构设计”的核心思想:高性能:读写分离,碎片化集群的高可用:数据持久化,多副本,自动故障转移和易扩展:拆分Shard集群,横向扩展当我们说到sentry集群和shard集群的时候,这也牵扯到“分布式系统”相关的知识:分布式共识:sentinelleader选举负载均衡:shard集群数据分片,数据路由当然,除了Redis,你可以用这个想法来思考和优化构建任何数据集群,看看他们是怎么做到的。比如你在使用MySQL的时候,你可以想一想MySQL和Redis的区别是什么?MySQL是如何做到高性能和高可用的?其实思路都是一样的。我们现在随处可见分布式系统和数据集群。希望通过这篇文章,大家能够了解这些软件是如何一步步进化的,在进化过程中遇到了哪些问题。为了解决这些问题,软件的设计者设计了什么样的方案,做了哪些取舍?你只需要了解其中的原理,掌握分析问题和解决问题的能力,这样在以后的开发过程中,或者学习其他优秀软件的时候,就能快速找到“重点”,在最短的时间内掌握,并在实际应用中充分发挥其优势。其实这个思考过程也是做“架构设计”的思路。在做软件架构设计的时候,你面临的场景就是发现问题、分析问题、解决问题,一步一步演化升级你的架构,最终在性能和可靠性上取得平衡。尽管各种软件层出不穷,但架构设计的思路是不会变的。我希望你真正吸收的是这些想法,这样你才能以不变应万变。本文转载自微信公众号“水滴银弹”,可通过以下二维码关注。转载本文请联系水滴和银弹公众号。