专业编程教程与实战项目分享平台

网站首页 > 技术文章 正文

「缓存系列」常用缓存模式有哪些?不同业务场景适合用哪种模式?

ins518 2025-01-12 15:33:14 技术文章 35 ℃ 0 评论

缓存,分布式系统架构中一个永远绕不开的话题。

如何在一个业务系统设计中,根据不同业务场景特点,选用适合的缓存模式,是我们晋升系统架构师的一项必修能力,也是大厂面试中高频知识点。

这篇文章,会包含以下几方面内容:

  • 为什么需要缓存?
  • 常用缓存模式有哪些?分别适用什么场景?
  • 互联网大厂业务应用举例

说明:

这篇文章洋洋洒洒写的内容比较多,担心一下子过多内容,给阅读和理解带来负担。

写完模式说明和场景应用分析后,决定将第三部分内容(互联网大厂业务应用举例)放到下一姊妹篇:头部互联网大厂业务之缓存模式应用。计划一周后完成这篇姊妹文章。


为什么需要缓存?

在分布式系统架构设计中,缓存是不可或缺的一环,尤其是对于那些需要应对高并发请求的服务(如面向客户端提供功能的后端服务)。

适合引入缓存的场景可以有很多,在众多场景中,有两大场景我认为是最为必要来引入缓存的。


基于“性能”角度考虑,支撑高并发或实时响应

在微服务体系中,单服务承担着责任有限的服务能力。在对外提供服务时,不可避免的会从上游获取数据。这里的上游,可能是最靠后端的数据库,也可能是其他后端服务所提供的接口。

数据库同时要为众多业务提供数据存取服务,并且像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模式是几种缓存模式中,数据一致性较差的一种。


    后语

    不同缓存模式,并没有哪个更优哪个更劣的区分,只有适合和不适合。业务系统设计中,选用哪种缓存模式也不是非此即彼的。在一个系统中,经常是结合不同的业务场景要求,同时存在多组不同的缓存模式。

    并且涉及到具体的业务场景,也并不一定说能直接对标到这些缓存模式的某个适用或不适用场景。每个业务都是多重属性的综合体,这里就牵扯到系统设计中的取与舍。关注业务最关键的指标,选用与此指标最配套的缓存模式组合。

    Tags:

    本文暂时没有评论,来添加一个吧(●'◡'●)

    欢迎 发表评论:

    最近发表
    标签列表