背景:实习中mt让用延时双删处理DB和redis数据一致性,但是面试说不出来,脑子里一团浆糊,这篇从旁路缓存、延时双删的角度把数据库缓存不一致的情况全列举一遍,并探讨这个方案的合理性
需要知道的知识
- 在遭遇读写并发时需要用到此内容,在公司中的场景是如连续两次修改用户名,显示的还是第一次修改的结果。其实可以理解为写-写-读并发了
- 旁路缓存:针对写操作,先更新数据库记录,再删除缓存。
- 延时双删:针对写操作,更新数据库记录->删除缓存,延时一会再删除一次缓存
- 缓存的写入远远快于数据库的写入
- 读操作:先读取缓存中数据,缓存中没有再读数据库,并重建缓存
- 不论读操作还是写操作,都是可以被打断的,如写操作更新完数据库,还未删除缓存时候,可能会插入一段读操作,造成问题。
旁路缓存和换个顺序的旁路缓存
经典旁路缓存可以很容易的画出来问题所在:
此时再读,就会读到20,但是数据库中是30,这种情况其实还好,因为重建缓存并不慢
下面这种就比较糟糕了,也是实际中不会考虑使用的,就是先删除缓存,再写数据库:
如上,更新DB是件很慢的事,所以这种情况出问题的概率是比第一种要大的。
这里也是第一个面试考点:为什么不更新缓存而要删除
1)删除一个数据,相比更新一个数据更加轻量级,不需要判断缓存中有没有对应数据。
2)在实际业务中,缓存的数据可能不是直接来自数据库表,也许来自多张底层数据表的聚合。
为什么先删库,后删缓存:
因为可以大幅降低不一致时间,且如果更新数据库失败,缓存并没有删除,只需要回滚数据库即可,反之我们数据库和缓存都需要回滚,而缓存的回滚是我们不愿意进行的。
时序图如图:
而写库和删缓存之间的时间是很少的,所以后删缓存一定是效率较高的
延时双删
解决的就上面经典旁路缓存的问题:
这样原先的缓存DB不一致时间为"重建缓存为20"~缓存失效。
现在的不一致时间为"重建缓存为20"~删除缓存
大大降低了不一致的时间
代码如下:
@PutMapping("/updateTitle/{id}")
public ResponseResult updateTitle(@RequestBody String title,@PathVariable Integer id) throws InterruptedException {
//删缓存
redisCache.deleteObject(id.toString());
//更新库
Article article = articleService.getById(id);
article.setTitle(title);
articleService.updateById(article);
//下发定时任务
//再次删缓存
delayJob(id);
return ResponseResult.okResult();
}
public void delayJob(Integer id) {
Timer timer = new Timer();
TimerTask task = new TimerTask() {
@Override
public void run() {
if (!executed) {
System.out.println("执行定时任务");
redisCache.deleteObject(id.toString());
}
}
};
timer.schedule(task,2000);
}