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

背景

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

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

常见缓存策略

对于缓存常见的策略,主要有以下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

相关推荐
捂月1 小时前
Spring Boot 深度解析:快速构建高效、现代化的 Web 应用程序
前端·spring boot·后端
瓜牛_gn1 小时前
依赖注入注解
java·后端·spring
Estar.Lee2 小时前
时间操作[取当前北京时间]免费API接口教程
android·网络·后端·网络协议·tcp/ip
喜欢猪猪2 小时前
Django:从入门到精通
后端·python·django
一个小坑货2 小时前
Cargo Rust 的包管理器
开发语言·后端·rust
bluebonnet272 小时前
【Rust练习】22.HashMap
开发语言·后端·rust
uhakadotcom2 小时前
如何实现一个基于CLI终端的AI 聊天机器人?
后端
Iced_Sheep3 小时前
干掉 if else 之策略模式
后端·设计模式
XINGTECODE3 小时前
海盗王集成网关和商城服务端功能golang版
开发语言·后端·golang
程序猿进阶3 小时前
堆外内存泄露排查经历
java·jvm·后端·面试·性能优化·oom·内存泄露