【系统设计】- 常见缓存策略&一致性保障

背景

对于一个后端服务而言,当整体的用户体量到达一定规模,且整体资源难以扩充的情况下,增加缓存势必是一个比较好的选择,一方面加入缓存之后,对整体的资源消耗可以适当减少,因为将一部分的请求压力分担到了服务层,另一方面引入缓存对于接口的性能也会有一个大的提升

但是,在接口引入了一个缓存层之后,缓存层同数据源之间的一致性保证变成了一个新的问题,如何保证二者的一致性是今天要探讨的问题

常见缓存策略

对于缓存常见的策略,主要有以下5个: cache-aside、read-through、write-through、write-behind、write-around,下面分别对这5种策略的应用场景及一致性保障方案进行阐述,希望可以供社区的小伙伴们参考 😊

cache-aside

策略概述

读请求: 如果发现命中缓存,直接返回缓存数据,若未命中则查询数据库更新缓存之后返回

写请求: 先更新数据库,然后删除缓存

对于上图,有一些细节点这里进行简单的讨论


为什么要删除缓存而不是更新缓存?

这里主要出于两方面的考虑: 安全和性能

性能: 一些缓存的结果是需要经过计算/表间的级联得来的,如果在写的过程中重新计算,对性能会产生一定的影响

安全: 如果是更新缓存的话,可能会存在一些数据写错的风险,如下

理论上读请求最终读到的应该是写请求2更新的数据,但是实际上最终得到的是写请求1更新的数据


为什么先更新数据库,而不是先删除缓存?

首先如果是先删除缓存的话,可能会放大"缓存击穿"的风险,另外这样做也会有缓存和数据源不一致的问题,如下

同样是并发的情况,当写请求删除缓存之后,读请求使用老的源信息更新了缓存数据,但实际上最终写入源的是一个新的数据,此时两侧还是会不一致


适用场景

cache-aside适用于读多写少的场景,例如用户信息、新闻报道等变化较少的信息,这种方式一旦写入缓存,整体的更新频率较低

不一致情况

按照现有策略,cache-aside还是会存在数据不一致的风险,例如下面的情况

写请求删除缓存的时机晚于读请求更新缓存的时机,不过这种情况出现的概率较小,需要满足读请求在写请求读数据库之后&读请求的执行时间晚于写请求

除了这种情况,还会存在另一种不一致的情况,如下

读请求读取在写请求执行中,要想防止,需要在写入的key维度加互斥锁,不过业务需要在此做一个折中,因为增加锁必然会带来一定的性能损耗&机器资源消耗

补偿措施

对于cache-aside模式,在服务运行过程中,或多或少必然会存在一些不一致的情况,例如缓存删除失败,除了给缓存增加TTL进行缓解外,还可以通过一些其它的方式进行弥补


删除重试机制

直接在写请求维度重试的话,对整体的性能会有一定的影响,可以增加一个重试队列,将删除失败的key写入队列中,异步对删除失败的key进行二次删除


基于数据库日志增量解析、订阅&消费

利用阿里开源组件cacal/一些其它的binlog解析中间件,在对应的client端编写缓存删除的逻辑,在写请求删除缓存之后,基于增量的binlog日志补偿数据更新时可能的缓存删除失败问题,在绝大多数场景下,可以解决数据的不一致问题,需要注意的是binlog日志的时效性,如果binlog比较落后,应该被丢弃掉,而不是作为判断是否删除缓存的依据

read-through

策略概述

read-through意为读穿透模式,整体流程和cache-aside是类似的,不同点在于read-through中多了一个访问控制层,读请求只会与访问控制层进行交互,业务层的逻辑更加简洁

适用场景

读穿透同样适用于读多写少,数据更新频率低的场景

write-through

策略概述

write-through意为写穿透模式,整体的流程同cache-aside有一些区别,在写入数据时是更新缓存,而不是删除缓存

适用场景

写穿透所有的写操作都会经过缓存,一般更新缓存和更新数据库在一个事务中,当使用write-through的时候,一般配合read-through进行使用

write-behind

策略概述

write-behind意为异步回写模式,与write-through/read-through相同,在write-behind中,也存在一个访问控制层,不同的是write-behind在处理写请求的时候,只会写到缓存中,之后再异步刷新的数据库中

适用场景

write-behind适用于写操作比较频繁的场景,例如秒杀扣减库存,这样的策略下,写请求的延迟降低,减轻了数据库的压力,具有较好的吞吐性,但是数据库和缓存的一致性较弱,例如当更新数据还未写到数据库,直接从数据库查询数据是落后于缓存的

write-around

策略概述

write-around适用于一些非核心业务,这类业务对于一致性的要求较弱,可以选择在cache-aside的基础上给缓存增加一个过期时间,不做任何的删除/更新缓存的操作

结语

解决缓存同数据库之间的一致性,存在多种策略,需要评估当前的场景选择合理的方案,例如在读多写少的场景下,可以考虑采用cache-aside策略+消费数据库日志补偿的方案,在写多的场景下可以考虑write-through的方案,写多的极端场景下可以考虑write-behind

相关推荐
码事漫谈4 分钟前
为什么调试模式不崩溃,打包后却崩溃了?
后端
智驭未来掌门人35 分钟前
利用FFmpeg快速实现一个RTSP推流服务器
后端
垚垚学技术_聚焦云原生1 小时前
K8s Pod 完整生命周期详解
后端
JavaGuide1 小时前
Github 史诗级故障,与此同时,Cursor 版「GitHub」正式上线!
前端·后端
不一样的少年_1 小时前
修了 Bug、做了重构,为什么老板还是觉得你没产出?
前端·后端·程序员
水深火乐1 小时前
安全地生成验证码、密码和 Token
后端
小强19881 小时前
SQL Server 慢查询怎么定位?一套从“卡”到“快”的完整排查流程
后端
水深火乐1 小时前
interface和any
后端
步行cgn1 小时前
MyBatis Error evaluating expression ‘ids‘. Return value (3) was not iterable 错误详
java·后端
神奇小汤圆2 小时前
Spring Boot + LangChain4j实现RAG——从零搭建企业级知识库问答系统
后端