Cache-Aside Pattern
时间:2022-07-24
本文章向大家介绍Cache-Aside Pattern,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。
Cache-Aside Pattern
一. 背景和问题
缓存已经成为了几乎所有应用系统的必备要素。使用缓存可以有效提高系统的读性能,相比于直接读取数据库,吞吐量有了很大的提高。但是,在实际生产环境中,很难保证缓存与数据库中数据的完全一致。程序应采取某种策略,尽可能地保证缓存中的数据是最新的,并且可以检测到缓存中数据失效,并提供相应的解决方案。
简单来说,Cache-Aside Pattern的提出是为了尽可能地解决缓存与数据库的数据不一致问题。
二. 解决方案
大多数的商用缓存系统都提供了下面的功能:
- 访问数据时,首先尝试从缓存中获取。如果缓存命中,则直接返回。
- 如果缓存未命中,则查询数据库。
- 将从数据库中查询到的结果放入缓存中,并返回。
- 缓存中任何数据的更新,都会自动同步到数据库。
如果所使用的缓存没有提供这些功能,则需要应用系统自己去实现,实现时就可以基于Cache-Aside Pattern。
三. Cache-Aside Pattern
Cache-Aside Pattern分为读操作和写操作两种。
- 读操作 原理如下图:
流程:
- 首先从缓存中查询数据,如果缓存命中则直接返回。
- 缓存未命中,则去数据库中读取。
- 将从数据库中读取的结果的副本放入到缓存中,并返回。
- 写操作
流程:
- 首先更新数据库。
- 然后删除缓存中的数据。
四. 一些思考
- 为什么是删除缓存,而不是更新缓存?主要基于以下两点考量:
- 数据更新后,可能不会有大量的访问。如果每次更新数据后都更新缓存,可能会造成大量不必要的计算开销。因此,这里采用一种lazy的思想,每次更新数据时仅仅是删除缓存,只有在真正读请求到来时才进行缓存的更新。
- 在高并发场景下,并发地更新缓存可能会造成缓存可数据库中数据不一致的问题。
- 写操作的流程十分关键!一定要先更新数据库,再删除缓存。如果先删除缓存,就会存在一个很小的窗口期,使得客户端查询时无法命中缓存,而去读数据库,然而此时数据库中的数据还未更新,就会从数据库中加载到旧的数据并放入缓存中,最终导致缓存数据被污染。
- 缓存的过期策略 许多缓存系统都会对缓存数据设置一定的过期策略。使用Cache-Aside Pattern时,一定要合理地设置过期策略。如果过期时间太短,可能导致大量请求涌入数据库。相反,如果过期时间太长,有可能导致缓存中数据的大量失效。使用缓存的一个原则,就是尽量缓存那些相对静态的、频繁被读取的数据。
- **Cache-Aside Pattern并无法完全保证数据库和缓存的数据一致性。**当某条数据被修改时,在数据库中会立即更新,但是缓存中的更新会在下次读取数据时才会发生,
五. 应用场景
- 适用场景:
- 应用程序所使用的缓存系统并没有提供前文所述的缓存系统的那些功能。
- 加载资源的需求是不可预测的。该模式使得系统可以按需加载数据,而不需提前预设哪些数据可能需要被获取。
- 不适用场景: 所缓存的数据集是静态的。
六. 使用示例
/**
* @Author: ZhangShenao
* @Date: 2019/5/15 11:07
* @Description:演示Cache-Aside Pattern
*/
@Service
public class CacheAsidePatternService {
@Autowired
private CacheService cacheService;
@Autowired
private DataDao dataDao;
//读操作
public Data getData(String key) {
//1. 读缓存,如果命中则直接返回
Data data = cacheService.loadDataFromCache(key);
if (data != null) {
return data;
}
//2. 缓存未命中,读数据库
data = dataDao.loadDataFromDB(key);
//3. 将读取到的数据放入缓存
cacheService.putData(key,data);
return data;
}
//写操作
public void updateData(String key,Data data){
//1. 更新数据库
dataDao.updateData(key, data);
//2. 删除缓存
cacheService.evictData(key);
}
}
七. 参考资料
- 快速完成(图片旋转,查看原图)
- Jsp中格式化时间戳的常用标签
- 反射+自定义注解---实现Excel数据列属性和JavaBean属性的自动映射
- 后台模板管理系统___左侧菜单数据的异步加载
- Shiro眼皮下玩ajax,玩出302 Found
- 对于JSONObject,我只是临时抱佛脚
- 总结切面编程AOP的注解式开发和XML式开发
- SpringMVC注解@RequestMapping之produces属性导致的406错误
- SpringBoot集成MyBatis的分页插件PageHelper(回头草)
- SpringBoot整合Mybatis之进门篇
- Tomcat和Java Virtual Machine的性能调优总结
- 一次浴火重生的MySQL优化(EXPLAIN命令详解)
- 简单聊聊不可或缺的Nginx反向代理服务器--实现负载均衡【上篇】
- Java设计模式之适配器设计模式(项目升级案例)
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- C#记事本项目开发,一个可以实现批量操作的记事本!【附源码】
- Python 库引用问题:name 'json' is not defined,原因及解决办法
- Java集合类之Collection接口,集合的“爸爸”接口了解一下?
- Python 微信机器人-下载微信接收到的语音、图片等资源
- Python源文件打包成可执行的exe应用,给你的代码变个身!
- Python 技术篇-ffmpeg.exe的安装及配置
- Python 库配置问题-"Couldn't find ffmpeg or avconv - defaulting to ffmpeg, but may not work",原因及解决办法
- 小白都能看懂的简单爬虫入门案例剖析(爬虫入门看它就够了!)
- Python 技术篇-音频mp3格式转wav格式,高保真
- Python3 模块
- Python 微信机器人-向好友发送名片、转发名片
- iOS多线程:GCD使用介绍
- 适用于各语言的二分查找算法,你get到了嘛?
- Theme preview
- “抽象类”到底抽不抽象?实例对比一看便知!