面对集中式缓存实现上的挑战,redis交出的是何种答卷?聊聊redis在分布式方面的能力设计(代码片段)

架构悟道 架构悟道     2023-01-15     715

关键词:

对于一个集中式缓存的分布式能力构建,必须要额外提供一些机制,来保障数据在各个节点上的安全与一致性。本文以Redis为代表,看下集Redis面对上述问题交出的是怎样一份答卷。

大家好,又见面了。


本文是笔者作为掘金技术社区签约作者的身份输出的缓存专栏系列内容,将会通过系列专题,讲清楚缓存的方方面面。如果感兴趣,欢迎关注以获取后续更新。


在本专栏前面的文章中,我们介绍了各种本地缓存框架,也知晓了本地缓存的常见特性与设计理念。在前两篇文章中,我们介绍了集中式缓存 Redis的一些主流特性与典型使用场景。现在我们来对比一下,分布式缓存相比于本地缓存,在实现层面需要关注的点有哪些不同。梳理如下:

维度 本地缓存 集中式缓存
缓存量 受限于单机内存大小,存储数据有限 需要提供给分布式系统里面所有节点共同使用,对于大型系统而言,对集中式缓存的容量诉求非常的大,远超单机内存的容量大小。
可靠性 影响有限,只有本进程使用,不会影响其他进程的可靠性。 作为整个系统扛压屏障,系统内所有节点共同依赖的通用服务,一旦集中式缓存出问题,会影响与其对接的所有业务节点,对系统的影响是致命性的。
承压性 承载单机节点的压力,请求量有限 承载整个分布式集群所有节点的流量,系统内业务分布式节点部署数量越多、业务体量越大,会导致集中缓存要承载的压力就越大,甚至是上不封顶的。

从上述几个维度的对比可以发现,同样是缓存,但集中式缓存所承担的使命是完全不一样的,业务对集中式缓存的存储容量可靠性承压性等方面的诉求也是天壤之别,不可等同视之。以Redis为例:

  • 如何打破redis缓存容量受限于机器单机内存大小的问题?
  • 如何使得redis能够扛住多方过来的请求压力?
  • 如何保证redis不会成为单点故障源?

其实答案很简单,加机器!通过多台机器的叠加使用,达到比单机更优的效果 —— 现在业务系统的集群化部署,也都是采用的这个思路。Redis的分布式之路亦是如此,但相比于常规的业务系统分布式集群化构建更加复杂:

  1. 很多业务实现集群化部署会很简单,因为每个业务进程节点都是无状态的,只需要部署下然后通过负载均衡的方式对外提供请求应答即可。
  2. Redis作为一个集中式缓存数据库,它是有状态的,不仅需要将进程分别部署在多个节点上,还需要将数据也分散存储在各个节点上,同时还得保证整个Redis集群对外是一个统一整体。

所以对于一个集中式缓存的分布式能力构建,必须要额外提供一些机制,来保障数据在各个节点上的安全与一致性,还需要将分散在各个节点上的数据都组成一个逻辑上的整体。

下面,我们以Redis作为集中式缓存的代表,来看下集Redis面对上述各种难题,交出的是怎样的答卷。

Reids部署方式的演进史

单机部署 —— 原始形态,最简单

单机部署只能算是一个开发或测试场景去小范围使用的场景,它与普通本地缓存无二,在可靠性与承压性上无法得到保证。

虽说Redis的性能很高,但俗话也说双拳难敌四手,单机性能再高,也无法抗住大规模集群中所有节点过来的并发请求。此外,单机部署还有个致命点在于其不具备高可用性,系统容易出现单点故障

所以说,稍微正规点的项目,几乎不会有人天真到会用单机模式去部署线上使用。

主从(master-replica)

前面说过单机节点存在诸多问题,很少在生产环境上使用。在实际项目中,有些项目的存储容量要求其实并不是特别的高(比如常规的16G或者32G就已经足够使用),但是需要保证数据的可靠、并且支持大并发量请求,这种情况下,就可以选择主从部署的方式。

对于redis来说,一主两从是比较常见的搭配,如下所示:

主从模式按照读写分离的策略来提升整体的请求处理能力:

  1. 主节点(Master)同时对外提供读和写操作

  2. 从节点(Slave)通过replicate同步的方式,从主节点复制数据,保持自身数据与主节点一致

  3. 从节点只能对外提供读操作

当然,对于读多写少类的操作,为了提升整体读请求的处理能力,可以采用一主多从的方式:

所有的从节点都从主节点进行数据同步,这样会导致主节点的同步处理压力过大而成为瓶颈。为了解决这个问题,redis还支持了从slave节点分发的能力:

Redis的主从模式重点在于解决整体的承压能力,利用从节点分担读取操作的压力。但是其在容错恢复等可靠性层面欠缺明显,不具备自动的故障转移与恢复能力:

  • 如果slave从节点宕机,整个redis依旧可以正常提供服务,待slave节点重新启动后,可以恢复从master节点的数据同步、然后继续提供服务。
  • 如果master主节点宕机,则redis功能受损,无法继续提供写服务,直到手动修复master节点方可恢复。

当然,master节点故障后,也可以手动将其中一个从节点切换为新的master节点来恢复故障。而原先的master节点恢复后,需要手动将其降级为slave节点,对外提供只读服务。

实际使用的时候,手动故障恢复的时效无法得到保证,为了支持自动的故障转移与恢复能力,Redis在主从模式的基础上进行优化增强,提供了哨兵(Sentinel)架构模式。

哨兵(sentinel)

哨兵模式是在现代自动化系统里面常见的一种模式。比如特斯拉汽车就配置了哨兵模式,当车辆停车锁定并启动哨兵模式时,会通过车辆四周的摄像头持续的监控车辆四周的环境,如果发现异常则启动报警系统。

同样地,在软件架构领域,也可以通过设定一些主进程之外的辅助进程,充当“哨兵”的角色时刻监控着主服务,一旦主服务出现异常则进行报警或者自动介入辅助故障转移,以最大限度的保证系统功能的持续性。

Redis的哨兵模式,就是在主从模式的基础上,额外部署若干独立的哨兵进程,通过哨兵进程去监视者Redis主从节点的状态,一旦发现主节点宕机,则哨兵可以重新从剩余slave节点中推选一个新的节点并将其升级为master节点,以此保证整个系统功能可以正常使用。

比较典型的一个Redis sentinel部署场景是“一主二从三哨兵”的组合,如下:

哨兵可以准实时的监控着组网内所有的节点的状态信息,如果判定master节点宕机之后,所有的sentinel节点会一起推选出一个新的master节点。由于sentinel哨兵节点需要承担着master节点推选的责任,所以实施的时候要去sentinel节点个数必须为奇数(比如3个、5个等),这是为了保证投票的时候不会出现平局的情况。

在哨兵模式下:

  1. 如果Redis的master节点宕机之后,Sentinel监控到之后,需要先判定确认master节点已经宕机,然后会从剩余存活的slave节点中投票选出一个新的节点作为master节点。
  2. Sentinel监控到此前宕机的master节点重新恢复之后,会将其作为slave节点,挂到现有的新的master节点下面。

哨兵模式有效的解决了高可用的问题,保证了主节点的自动切换操作,进一步保障了Redis缓存节点的可靠性。但是,不管是哨兵模式还是主从模式,其增加的多台部署机器,都仅仅是扩展其承压能力与可靠性,并没有解决分布式场景下对于集中缓存容量的焦虑 —— 只能适用于数据量有限的场景。

成年人的世界总是贪婪的。如果我们既想要保证Redis的可靠性与承压性,还想要突破容量上的限制,就需要Redis的集群模式登场了。

集群(cluster)

Redis提供了去中心化的集群部署模式,集群内所有Redis节点之间两两连接,而很多的客户端工具会根据key将请求分发到对应的分片下的某一个节点上进行处理。

一个典型的Redis集群部署场景如下图所示:

在Redis集群里面,又会划分出分区的概念,一个集群中可有多个分区。分区有几个特点:

  1. 同一个分区内的Redis节点之间的数据完全一样,多个节点保证了数据有多份副本冗余保存,且可以提供高可用保障。
  2. 不同分片之间的数据不相同。
  3. 通过水平增加多个分片的方式,可以实现整体集群的容量的扩展。

按照Cluster模式进行部署的时候,要求最少需要部署6个Redis节点(3个分片,每个分片中1主1从),其中集群中每个分片的master节点负责对外提供读写操作,slave节点则作为故障转移使用(master出现故障的时候充当新的master)、对外提供只读请求处理。

集群数据分布策略

Redis Sharding(数据分片)

Redis Cluster前,为了解决数据分发到各个分区的问题,普遍采用的是Redis Sharding(数据分片)方案。所谓的Sharding,其实就是一种数据分发的策略。根据key的hash值进行取模,确定最终归属的节点。

使用Redis Sharding方式进行数据分片的时候,当集群内数据分区个数出现变化的时候,比如集群扩容的时候,会导致请求被分发到错误节点上,导致缓存命中率降低

如果需要解决这个问题,就需要对原先扩容前已经存储的数据重新进行一次hash计算和取模操作,将全部的数据重新分发到新的正确节点上进行存储。这个操作被称为重新Sharding,重新sharding期间服务不可用,可能会对业务造成影响。

一致性Hash

为了降低节点的增加或者移除对于整体已有缓存数据访问的影响,最大限度的保证缓存命中率,改良后的一致性Hash算法浮出水面。

通过一致性Hash算法,将所有的存储节点排列在首尾相接的Hash环上,每个key在计算Hash后会顺时针找到最近的存储节点存放。而当有新的分区节点加入或退出时,仅影响该节点在Hash环上顺时针相邻的后续一个节点。

当然咯,如果Hash圆环上的分区节点数太少,可能会出现数据在各个分片中分布不均衡的情况,也即出现数据倾斜

为了解决这个问题,引入了虚拟节点的机制,通过增加虚拟节点,来实现数据尽可能的均匀分布在各个节点上。

上图中,A1、A2实际上都对应真实的A分区节点,而B1、B2则对应真实的B分区节点。通过虚拟节点的方式,尽可能让节点在Hash环上保持均分,实现数据在分区内的均分。

Hash槽

不管是原本的Hash取模,还是经过改良后的一致性Hash,在节点的新增或者删减的时候,始终都会出现部分缓存数据丢失的问题 —— 只是丢失的数据量的多少区别。如何才能实现扩展或者收缩节点的时候,保持已有数据不丢失呢?

既然动态变更调整的方式行不通,那就手动指定咯!Hash槽的实现策略因此产生。何为Hash槽?Hash槽的原理与HashMap有点相似,共有16384个槽位,每个槽位对应一个数据桶,然后每个Redis的分区都可以负责这些hash槽中的部分区间。存储数据的时候,数据key经过Hash计算后会匹配到一个对应的槽位,然后数据存储在该槽位对应的分片中。然后各个分区节点会与Hash槽之间有个映射绑定关系,由指定的Redis分区节点负责存储对应的Has槽对应的具体分片文件。

数据查询的时候,先根据key的Hash值进行计算,确定应该落入哪个Hash槽,进而根据映射关系,确定负责此Hash槽数据存储的redis分区节点是哪个,然后就可以去做对应的查询操作。

执行数据节点增加的时候,需要手动执行下处理:

  • 为新的节点分配新其负责的Hash槽位区间段;
  • 调整已有的节点的Hash槽位负责区间段;
  • 将调整到新节点上的hash槽位区间段对应的数据分片文件拷贝到新的节点上。

这样,就不会出现已有数据无法使用的情况了。鉴于Hash槽的自主可控性以及节点伸缩场景下的优势,其也成为了Redis Cluster中使用的方案。

小结回顾

好啦,关于Redis部署模式的演进探讨,就聊到这里了。通过本篇文章,我们也可以感受出集中式缓存相对本地而言,在实现与设计机制上要更加的复杂,因为需要考虑与解决多方面的问题,比如可靠性、承压性、容量以及后期的水平扩容能力等等,而这些也都是一个合格的集中式缓存所必须要具备的基本品格。

那么,了解Redis对于集中式缓存在节点安全性与扩展性上的实现后,如果让你来设计一个集中缓存的话,你会采用何种方式来保证其可靠性与后续的扩展性呢?欢迎评论区一起交流下,期待和各位小伙伴们一起切磋、共同成长。

探讨下如何更好的使用缓存——redis缓存的特殊用法以及与本地缓存一起构建多级缓存的实现(代码片段)

...存框架的原理与使用场景,也一同领略了以Redis为代表的集中式缓存在分布式高并发场景下无可替代的价值。现在的很多大型高并发系统都是采用的分布式部署方式,而作为高并发系统的基石,缓存是不可或缺的重要环节。项目... 查看详情

springboot2.x基础教程:使用集中式缓存redis

之前我们介绍了两种进程内缓存的用法,包括SpringBoot默认使用的ConcurrentMap缓存以及缓存框架EhCache。虽然EhCache已经能够适用很多应用场景,但是由于EhCache是进程内的缓存框架,在集群模式下时,各应用服务器之间的缓存都是独... 查看详情

springboot集成redis来实现缓存技术方案

概述在我们的日常项目开发过程中缓存是无处不在的,因为它可以极大的提高系统的访问速度,关于缓存的框架也种类繁多,今天主要介绍的是使用现在非常流行的NoSQL数据库(Redis)来实现我们的缓存需求。 Redis简介Redis是... 查看详情

基于redis和nginx实现高并发缓存架构(代码片段)

目录1缓存架构设计1.1缓存架构设计2Redis集群高级应用3Nginx缓存3.1OpenRestry安装3.2浏览器缓存3.2.1NginxWeb缓存配置3.2.2Http缓存控制头3.3代理缓存3.3.1proxy_cache3.3.2缓存操作4Canal的使用1缓存架构设计一谈到缓存架构,很多人想到的是... 查看详情

aop-代理拦截实现redis缓存(代码片段)

使用AOP代理拦截方式实现缓存.上文简单的缓存实现方式:.NetCoreWebAPI利用IActionFilter实现请求缓存需要将缓存定义在控制器Controller层,增加了对控制器层的耦合度。另外,缓存的是控制器层面的结果IActionResult缓存。很... 查看详情

springboot整合redis缓存

在我们的日常项目开发过程中缓存是无处不在的,因为它可以极大的提高系统的访问速度,关于缓存的框架也种类繁多,今天主要介绍的是使用现在非常流行的NoSQL数据库(Redis)来实现我们的缓存需求。SpringBoot整合Redis是非常... 查看详情

面试官:你对redis缓存了解吗?面对这11道面试题你是否有很多问号?(代码片段)

点击上方关注“终端研发部”设为“星标”,和你一起掌握更多数据库知识关于Redis的知识,总结了一个脑图分享给大家1、在项目中缓存是如何使用的?为什么要用缓存?缓存使用不当会造成什么后果?面试... 查看详情

面试官:你对redis缓存了解吗?面对这11道面试题你是否有很多问号?(代码片段)

点击上方关注“终端研发部”设为“星标”,和你一起掌握更多数据库知识关于Redis的知识,总结了一个脑图分享给大家1、在项目中缓存是如何使用的?为什么要用缓存?缓存使用不当会造成什么后果?面试... 查看详情

Redis 上的哈希缓存驱逐

】Redis上的哈希缓存驱逐【英文标题】:CacheevictiononhashesonRedis【发布时间】:2014-12-2908:59:01【问题描述】:如果我在Redis上有多个散列,每个散列的键在24小时内过期,如果在使用allkeys-lru之类的驱逐策略时内存耗尽,Redis会删除... 查看详情

面对缓存,有哪些问题需要思考?

缓存可以说是无处不在,比如PC电脑中的内存、CPU中的二级缓存、HTTP协议中的缓存控制、CDN加速技术都是使用了缓存的思想来解决性能问题。缓存是用于解决高并发场景下系统的性能及稳定性问题的银弹。本文主要是讨论我们经... 查看详情

分布式缓存技术redis学习——redis简介以及linux上的安装

...种,NoSQL是以Key-Value的形式存储数据。当前主流的分布式缓存技术有redis,memcached,ssdb,mongodb等。既可以把redis理解为理解为缓存技术,因为它的数据都是缓存在内从中的;也可以理解为数据库,因为redis可以周期性的将数据写入磁... 查看详情

shiro整合springboot缓存之redis实现(代码片段)

Shiro整合Springboot缓存之Redis实现Redis下载安装引入redis依赖配置redis连接启动redis服务自定义redis缓存的实现自定义shiro缓存管理器自定义salt实现实现序列化接口自定义Realm改造开启redis缓存Redis下载安装Windows系统中Redis下载安装其它... 查看详情

34_redis阶段性总结:1t以上海量数据+10万以上qps高并发+99.99%高可用

...户过来的请求,上千万用户过来访问,每个用户访问10次;集中式的请求,1个用户过来,一天访问1亿次支撑商品展示的最重要的,就是rediscluster,去抗住每天上亿的请求流量,支撑高并发的访问rediscluster在整个缓存架构中,如何... 查看详情

redis-分布式缓存

目录:一、单机Redis的问题​二、Redis哨兵机制1、认识Redis哨兵机制2、主观下线和客观下线3、新master选举4、故障转移步骤5、搭建哨兵集群6、RedisTemplate的哨兵模式一、单机Redis的问题1、数据丢失问题实现Redis数据持久化;2、并... 查看详情

redis适合啥场景?

1、缓存。缓存现在几乎是所有中大型网站都在用的必杀技,合理的利用缓存不仅能够提升网站访问速度,还能大大降低数据库的压力。Redis提供了键过期功能,也提供了灵活的键淘汰策略,所以,现在Redis用在缓存的场合非常多... 查看详情

springboot+mybatis+redis实现二级缓存

对于查询比较多的项目可以考虑配置二级缓存,mybatis本身的二级缓存是缓存到本地,但是对于多个节点的项目来说,可能会出现数据不一致的问题,所以采用redis缓存,这样二级缓存的数据就可以缓存到内存,可实现多个节点项... 查看详情

高可用学习与研究

...有多少高可用 有多少中间件,就有多少高可用。比如缓存中间件redis,就要有redis 查看详情

redis缓存使用技巧(代码片段)

缓存能够有效加速应用的访问速度,同时可以降低后端负载,在应用架构中起着至关重要的作用,本文主要介绍缓存使用的一些技巧。缓存更新策略LRU/LFU/FIFO算法剔除场景:数据一致性要求较低原理:缓存使用量超过了预设值,... 查看详情