网站首页 > 技术文章 正文
缓存,分布式系统架构中一个永远绕不开的话题。
如何在一个业务系统设计中,根据不同业务场景特点,选用适合的缓存模式,是我们晋升系统架构师的一项必修能力,也是大厂面试中高频知识点。
这篇文章,会包含以下几方面内容:
- 为什么需要缓存?
- 常用缓存模式有哪些?分别适用什么场景?
- 互联网大厂业务应用举例
说明:
这篇文章洋洋洒洒写的内容比较多,担心一下子过多内容,给阅读和理解带来负担。
写完模式说明和场景应用分析后,决定将第三部分内容(互联网大厂业务应用举例)放到下一姊妹篇:头部互联网大厂业务之缓存模式应用。计划一周后完成这篇姊妹文章。
为什么需要缓存?
在分布式系统架构设计中,缓存是不可或缺的一环,尤其是对于那些需要应对高并发请求的服务(如面向客户端提供功能的后端服务)。
适合引入缓存的场景可以有很多,在众多场景中,有两大场景我认为是最为必要来引入缓存的。
基于“性能”角度考虑,支撑高并发或实时响应
在微服务体系中,单服务承担着责任有限的服务能力。在对外提供服务时,不可避免的会从上游获取数据。这里的上游,可能是最靠后端的数据库,也可能是其他后端服务所提供的接口。
数据库同时要为众多业务提供数据存取服务,并且像SELECT 语句又有着诸如JOIN、ORDER、LIKE等丰富的语义,数据库就容易成为分布式系统中的性能瓶颈。
而在分布式系统中,当RPC远程调用上游服务时,由于网络质量不稳定、以及上游服务的逻辑黑盒问题,也会导致当前服务对外提供响应的时间不可控。
对于一些需要实时响应或高并发请求的系统,使用缓存非常必要。
基于“可用性”考虑,支撑业务提供连续高可用服务
关于可用性,从当前服务及上游两个角度来展开讲。这里的上游,既可指后端数据库,也可以是其他应用程序。
从上游角度来说,当前服务若无缓存机制,将请求透传到上游的数据库或者其他应用,有可能会将上游打垮,从而形成整个分布式系统的雪崩效应;
从当前服务角度来说,由于强依赖于上游提供的数据,一旦由于网络抖动或上游服务故障,都将直接导致当前服务无法正常对外提供服务,造成业务不可用。
所以,对于可用性要求高的系统,使用缓存也非常必要。
无论是上面提到的性能问题,还是可用性问题,都可以引用架构设计中那句经典的话:“没有什么问题是加一层不能解决的,如果有,那就再加一层!”。
这里被加上的层,便是“缓存”;被加上多层,便可形成“多级缓存”。
常用缓存模式有哪些?分别适用什么场景?
从上面的介绍可以知道,缓存被添加在“应用程序-上游服务“之间,这里的上游服务,既可以是数据库,也可以是其他应用程序。在业务中最常见的上游服务,便是数据库,如典型的”应用程序-Redis缓存-MySQL数据存储“结构。
下面在介绍几种常见缓存模式时,上游服务,也用最常见的数据库来说明。
1、缓存模式之Cache Aside
1.1 模式说明
Cache Aside模式(又叫旁路缓存),是分布式系统中最常用的一种缓存模式。Cache Aside模式下的读写操作,大致的逻辑如下图所示:
其中:
- 当读数据时:
- 如果命中缓存,则从缓存返回数据
- 如果未命中缓存,则从数据库中返回数据,获取成功后,更新数据到缓存中
先写入到数据库中,成功后,再删除缓存里的数据
要格外说明的是,为什么写数据时,建议删除缓存数据条目,而不是更新缓存?
这里主要是从并发写场景下的”数据一致性“角度考虑的,并发场景下,删除缓存比更新缓存,能更大概率保证缓存与数据库的数据一致性。
1)在出现写-写并发时
若采用删除缓存的方式,由于两次写都仅删除,不会触发数据不一致问题;
若采用更新缓存的方式,当两次写操作耗时差异大(如业务逻辑不同,或机器负载波动大)时,会触发数据不一致问题。
2)在出现读-写并发时
无论采用删除缓存,还是更新缓存,都有概率出现数据不一致问题。但这种只是理论上的概率,因为对场景要求很严格(读未命中,且读逻辑的耗时>写逻辑的耗时),而一般业务场景中,都是写耗时远超读耗时。
从上面两种场景的说明可以看出,在读写并发场景下,两种方式都存在数据不一致的可能;在写写并发场景,删除缓存能保证数据一致性,而更新缓存不能。所以在Cache Aside模式中,建议采用的是写操作删除缓存。
1.2 应用场景
- 适合:缓存值更新复杂度高的场景
在这种模式下,更新操作只更新数据库而直接删除缓存条目,缓存数据的写入是在读取操作时进行。所以无论缓存值更新有多复杂,都不会在写操作(本身耗时就较长)时触发。
一些缓存,并不是简单的将数据库中值进行缓存,而是经一套复杂的业务逻辑计算得出。也就是说,如果直接在写操作时不仅写入数据库,还把缓存值计算出来,就会导致写操作耗时长+可能进行了很多无必要缓存更新操作(因为这些缓存可能是个冷数据)。
譬如,用户在Web控制台上,点击保存了某个配置,那对于本次保存,最终要的是“数据入库”,保证本次操作数据不会丢失,并将保存结果及时的反馈到用户。 如果采用更新时,同时更新缓存。就会导致,用户点击了保存,后端接口要将数据入库(长耗时1),同时还要经过一系列复杂运算,将缓存进行更新(长耗时2),用户体验不佳。
- 适合:对数据一致性要求较高的场景
并发读场景,都是基于缓存/数据库→缓存的操作,不会触发数据不一致问题;
并发写场景,由于直接删缓存条目,只保存数据到数据库,也不会触发数据不一致问题;
并发读写场景,理论上的确会存在数据不一致的可能,不过由于触发条件过于严苛(参见“模式说明”章节),本模式还是比较适合数据一致性要求较高的业务。
这种场景下的数据不一致问题,可在下次写操作触发,或缓存到期时进行修正,或者采用延时双删的机制,进一步加强数据一致性。
- 适合:热点数据访问场景
热点数据,访问频次高,仅在首次读取的时候基于数据库加载计算得出,后续能快速返回。
而冷数据启动,则必然经过一次数据库的加载计算,成本效益不高,且读取接口响应速度也较慢。
- 不适合:读取实时性要求非常高的场景
一旦缓存未命中(未读取过/发生了写操作/缓存过期等原因),由于数据采用 Lazy Loading策略,在下一次读取时才基于数据库进行加载&计算到缓存,这个过程会相对耗时久一些,不适合对读取实时性要求非常高的场景。
对于这种读取要求非常高的场景,可以考虑使用Write Through模式(同时更新缓存+数据库)或 Write Behind模式(仅更新缓存,后续批量刷数据库)。
- 不适合:扫描或大范围查询的前端场景
大量数据冷启动,造成扫描时刻服务器负载突增,且接口对外响应耗时长。在后台系统中,这种负载突增,容易引发非预期的连锁反应。
另外,这里特别指出前端场景,是因为后端扫描,一般对实时性要求不会很高,可以适当增加平滑处理。而前端某操作触发扫描或大范围查询时,由于查询的数据量大,那每个数据的查询要求就比较高了(从单数据层面来看,等同于上面的读取实时性要求非常高的场景)
- 不适合:写操作密集型场景
大量写操作,触发缓存条目删除,导致缓存失效,影响读操作性能。并且,大量写操作更新后端数据库,造成数据库压力大。
2、缓存模式之Read/Write Throuth
在前面介绍的 Cache Aside模式中,业务调用方,需要自行维护cache和db两个数据存储组件,对于业务方来说,过于繁琐。于是,Read/Write Through模式就应运而生。
Read/Write Through 模式(又叫读/写穿透缓存),可以理解为设计模式里的装饰器模式。通过一层代理装饰,业务方直接调用装饰器暴漏的读写方法,而无需关注底层使用的是一个存储组件,还是多个存储组件。
2.1 Read Through模式
2.1.1 模式说明
业务在读取数据时,调用存储代理层提供的读取接口,代理层先检查是否命中缓存
- 若命中缓存,直接返回cache数据
- 若未命中缓存,先将DB数据读取到cache,再返回数据
2.1.2 模式对比:Read Througth VS Cache Aside
两种模式下,读数据,底层逻辑一致。
差别在于:
Cache Aside 模式由业务方来处理缓存不同命中场景下的数据库与缓存的关系;
而Read Through模式,这些底层细节完全由代理层封装,业务方只是调用代理层的一个读取方法即可。
2.1.3 应用场景
在上面的”模式对比:Read Througth VS Cache Aside”里介绍过,两种模式下读操作底层逻辑是一致的,所以说,Read Throuth模式与Cache Aside模式下的读适用和不适用场景也是一致的,即:
- 适合:缓存值更新复杂度高的场景(这是写操作,不在这里讨论)
- 适合:对数据一致性要求较高的场景(这主要是根据写操作,来决定是否合适的)
- 适合:热点数据访问场景
- 不适合:读取实时性要求非常高的场景(这主要是根据写操作,来决定是否合适的)
- 不适合:扫描或大范围查询的前端场景
- 不适合:写操作密集型场景(这是写操作,不在这里讨论)
上面置灰删除的场景,并不是说Read Through模式不支持,而是这些场景并非只由读数据场景就能决定,而是要结合和写操作相关的缓存模式,看相应模式下是如何处理写操作下缓存与数据库数据的,才能综合决定这个组合而出的“读缓存模式+写缓存模式”是否适用或不适用某个场景。
2.2 Write Through模式
2.2.1 模式说明
业务在写入数据时,调用存储代理层提供的写入接口,代理层先检查是否命中缓存
- 若存在缓存,则先更新cache,再同步立即更新DB
- 若不存在缓存,则不更新cache,同步立即更新DB
注意:这里当不存在缓存时,不更新cache,只更新DB,是为了避免大量冷数据占用内存,提高内存的利用率。
2.2.2 模式对比:Write Through VS Cache Aside
两种模式,在未命中缓存时,底层操作一致,都是只更新DB;
但在命中缓存时,Cache Aside模式建议采用的是删除缓存,而Write Through模式则是采用双更新。
这里Write Through模式采用双更新,而不是用删除缓存,主要基于这两点考虑:
1)写操作时,将数据库和缓存数据都进行更新,这样后续读时,就可以快速命中缓存返回,提升读性能
2)由于存在Write Throuth统一代理层,业务方只是调用更新操作接口,不同业务当并发写相同key时,可以在代理层进行调度处理(如分布式锁或队列串行机制),减小 Cache Aside模式中提到的并发写操作带来数据不一致可能。
2.2.3 应用场景
在介绍Write Through模式场景前,先列一下 Cache Aside模式的场景应用内容(方便做下对比):
Cache Aside模式,
适合:缓存值更新复杂度高的场景
适合:对数据一致性要求较高的场景
适合:热点数据访问场景(这是读操作,不在这里讨论)
不适合:读取实时性要求非常高的场景
不适合:扫描或大范围查询的前端场景(这是读操作,不在这里讨论)
不适合:写操作密集型场景
接下来结合”模式对比:Write Througth VS Cache Aside“内容,分析Write Throuth模式的适用和不适用场景。
- 适合:对数据一致性要求较高的场景
相比于Cache Aside 的删除缓存条目,Write Through的双更新逻辑,数据一致性会稍差一些。但考虑到可以在代理层统一处理并发冲突问题,还是能满足一定的数据一致性要求。
- 适合:适合读取实时性高的场景
这个场景,Cache Aside模式因为Lazy Loading策略是不适合的,而Write Through模式是适合的,因为双更新逻辑,确保了在写操作命中缓存的场景下,会同时更新数据库和缓存,读操作能直接读取缓存快速返回。
- 不适合:缓存值更新复杂度高的场景
双更新逻辑,需要更新缓存值,若存在复杂业务逻辑运算来更新,写操作会存在性能问题。
- 不适合:写操作密集型场景
大量写操作更新后端数据库,造成数据库压力大。写操作密集型推荐使用Write Behind,能明显提高性能。
3、缓存模式之Write Behind
3.1 模式说明
Write Behind模式,又叫Write Back模式,异步后写缓存,只针对写操作。
读操作可以使用Cache Aside模式或Read Through模式。这两种模式在上面介绍过,两者的底层读操作行为是一致的,只是看逻辑处理部分是放在业务方,还是统一的代理层。
Write Behind模式,在更新数据的时候,只更新缓存,不更新数据库,之后会异步+批量更新数据库,即为Lazy Write策略。
从上面 Write Behind模式的写操作行为可以看出,Write Bebind模式的写操作性能很高。因为:
1)直接操作内存,而不用管相对较慢的数据库操作
2)批量更新数据库,批量操作性能比单条更新性能更好
3)异步操作,可将一段时间的操作进行归并处理,避免了中间态数据的反复写库
这种模式的弊端也很明显,写操作的高性能是延迟更新数据库带来的,那相应的该模式的缺点在于:
1)数据不是强一致性的,存在缓存比较新,但数据库里还是旧数据
2)数据可能丢失
3)逻辑复杂:需业务跟踪哪些数据发生变化了,需要更新到数据库,并且要选用合适的刷入的时机
3.2 模式对比:Write Through VS Write Behind
Write Behind模式只针对写操作,所以直接与Write Through进行对比。
两种模式,在写操作未命中缓存时,都是直接更新数据库,而无需管缓存;
在写操作命中缓存时,最终都会双写:立即写缓存+一定时机写数据库。
Write Behind与Write Through的区别,也主要就体现在这个”一定时机“上。
- Write Through是写完缓存,同步写数据库,既然是同步写,那就是单次操作
- Write Behind是写完缓存,异步批量写数据库
两者的写入数据库时机差别,也造成了两个模式应用特点上的区别:
- 从数据一致性角度考虑:同步写的Write Throuth模式,一致性更强
- 从写操作的性能角度考虑:异步批量写的Write Behind模式,性能更好
3.3 应用场景
Write Behind模式只针对写操作,所以应用场景下也直接与 Cache Aside、Write Through进行对比。
先列下Cache Aside、Write Through的适用和不适用场景,方便做对比。
Cache Aside模式,
适合:缓存值更新复杂度高的场景
适合:对数据一致性要求较高的场景
不适合:读取实时性要求非常高的场景
不适合:写操作密集型场景
Write Through模式,
适合:对数据一致性要求较高的场景
适合:适合读取实时性高的场景
不适合:缓存值更新复杂度高的场景
不适合:写操作密集型场景
接下来看下Write Behind 场景适用性:
- 适合:写操作密集型场景
大量写操作,并不会立即反馈到数据库,而是经过异步归并处理,再批量写入,数据库友好。
- 适合:适合读取实时性高的场景
写操作会触发立即缓存更新,相比Cache Aside模式,Write Behind对读操作友好。
- 不适合:缓存值更新复杂度高的场景
写操作会触发立即缓存更新,这里若是要进行复杂运算,写操作响应不够及时。
- 不适合:对数据一致性要求较高的场景
异步写入,Write Behind模式是几种缓存模式中,数据一致性较差的一种。
后语
不同缓存模式,并没有哪个更优哪个更劣的区分,只有适合和不适合。业务系统设计中,选用哪种缓存模式也不是非此即彼的。在一个系统中,经常是结合不同的业务场景要求,同时存在多组不同的缓存模式。
并且涉及到具体的业务场景,也并不一定说能直接对标到这些缓存模式的某个适用或不适用场景。每个业务都是多重属性的综合体,这里就牵扯到系统设计中的取与舍。关注业务最关键的指标,选用与此指标最配套的缓存模式组合。
- 上一篇: 浏览器缓存机制详解
- 下一篇: 详解页面静态资源的缓存策略,搞懂强缓存和协商缓存再做性能优化
猜你喜欢
- 2025-01-12 CDN+OpenResty 实现丝滑访问的登录态缓存站
- 2025-01-12 如何在Spring Boot中通过布隆过滤器防止缓存穿透问题?
- 2025-01-12 HTML5缓存机制浅析:移动端Web加载性能优化
- 2025-01-12 如何在 NGINX 中缓存内容
- 2025-01-12 如何解决服务器缓存过高
- 2025-01-12 白帽黑客贡献新的Web攻击方式,CDN缓存服务器成为数据泄露目标
- 2025-01-12 西部数据推出新款蓝盘机械硬盘:CMR技术,4TB 549元
- 2025-01-12 面试官:如何实现多级缓存?
- 2025-01-12 基于spring boot的注解缓存,自带轻量级缓存管理系统
- 2025-01-12 系统设计 | 缓存系统设计
你 发表评论:
欢迎- 531℃Oracle分析函数之Lag和Lead()使用
- 530℃几个Oracle空值处理函数 oracle处理null值的函数
- 529℃Oracle数据库的单、多行函数 oracle执行多个sql语句
- 516℃0497-如何将Kerberos的CDH6.1从Oracle JDK 1.8迁移至OpenJDK 1.8
- 513℃Oracle 12c PDB迁移(一) oracle迁移到oceanbase
- 502℃【数据统计分析】详解Oracle分组函数之CUBE
- 481℃最佳实践 | 提效 47 倍,制造业生产 Oracle 迁移替换
- 481℃Oracle有哪些常见的函数? oracle中常用的函数
- 最近发表
- 标签列表
-
- 前端设计模式 (75)
- 前端性能优化 (51)
- 前端模板 (66)
- 前端跨域 (52)
- 前端缓存 (63)
- 前端react (48)
- 前端aes加密 (58)
- 前端脚手架 (56)
- 前端md5加密 (54)
- 前端富文本编辑器 (47)
- 前端路由 (61)
- 前端数组 (73)
- 前端排序 (47)
- 前端定时器 (47)
- Oracle RAC (73)
- oracle恢复 (76)
- oracle 删除表 (48)
- oracle 用户名 (74)
- oracle 工具 (55)
- oracle 内存 (50)
- oracle 导出表 (57)
- oracle 中文 (51)
- oracle的函数 (57)
- 前端调试 (52)
- 前端登录页面 (48)
本文暂时没有评论,来添加一个吧(●'◡'●)