开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 4 天,点击查看活动详情
1.前言
我们首先要明白一件事,Redis的缓存为什么需要更新。Redis的作用是将数据库里经常被查询的数据放到内存里面,以此来提高请求的访问速度。如果数据库里的数据已经更新了,而redis里缓存的数据没有更新的话,那么便会造成用户请求获取的数据是不正确的,这就是Redis缓存为什么需要更新的原因。Redis缓存更新及时的话,那么Redis的缓存数据将和数据库保存的数据保持一致。
本篇内容来源是我本人最近学习的黑马课程的redis课程,在此记录下来。
2.缓存更新策略
| 内存淘汰 | 超时剔除 | 主动更新 | |
|---|---|---|---|
| 说明 | Redis本身有内存淘汰的机制,内存不足的时候会淘汰部分数据,等待下次查询时再更新redis缓存,但是你不清楚它会淘汰哪些数据。 | 超时剔除,就是在设置缓存的时候,同时设置key的过期时间 | 在业务代码里更新数据库的时候,更新缓存 |
| 一致性 | 差 | 一般 | 好 |
| 维护成本 | 无 | 低 | 高 |
采用何种缓存更新策略需要看业务场景:
- 低一致性需求:使用内存淘汰机制。比如旅游网址上面的旅游路线,美食类型等不需要经常变化的数据。
- 高一致性需求:使用主动更新,并以超时剔除作为兜底。例如店铺详情查询的缓存。
- 只使用超时剔除的需求:典型的是短信登录验证码。给验证码一个有效期。
3.主动更新策略
主动更新有三种策略:
- 由缓存调用者在更新数据库的时候同时更新缓存(推荐)。
- 将缓存与数据库结合为一个服务,由服务来维护一致性。调用者调用该服务,无需关心缓存一致性问题。但是这种开发难度大,在网上也很难找到现成的例子。
- 调用者只操作缓存,由其它线程异步的将缓存数据持久化到数据库,保证最终一致。
根据上面的分析,我们最终采用第一种方案。如果采用第一种方案,那么我们有三个问题需要考虑:
- 删除缓存还是更新缓存?
- 如何保证缓存与数据库的操作同时成功或失败?
- 先操作缓存还是先操作数据库?
答案是:
- 删除缓存。 每次更新数据库的时候,让缓存失效,再次查询的时候再更新缓存。原因是:如果每次更新数据库,你都要去更新缓存,那么会造成很多无效的写操作,毕竟你更新的缓存可能不会有人那么快来访问,倒不如删了,等有人访问的时候再去更新缓存。
- 考虑保证缓存与数库的操作同时成功或失败的方法要取决于我们的系统是单体的还是分布式的。单体系统:将缓存与数据库放在同一个事务。分布式系统:利用TCC等分布式事务方案。
- 先操作数据库,再删缓存。这种操作不是说不会出现线程安全问题,而是这种方案它出问题的概率比先删缓存,再操作数据库低。先删缓存,再操作数据库更容易出现缓存和数据库不一致的情况。采用先操作数据库,再删缓存这种方案它也有可能出现缓存和数据库不一致的情况,虽然概率低,但是还是有可能发生的,所以我们可以让缓存加个过期时间,过期了就删掉。
4.主动更新代码实践
- 下面代码是以更新商铺信息为例子讲解的。查询商铺信息的代码有兴趣的话可以参考这篇文章。
- 更新缓存的操作一般都是在查询接口里实现的,比如查询店铺。当在redis缓存中查询不到店铺信息,那么便会到数据库中查询,如果查询到了就将数据返回,并更新到缓存中。这里是更新商铺,根据我们前面的分析,我们先更新数据库,然后删除缓存。
- RedisConstants.CACHE_SHOP_KEY + id中的RedisConstants.CACHE_SHOP_KEY是我们定义的商铺id的前缀,写成这样是为了好管理。
@PutMapping
public Result updateShop(@RequestBody Shop shop) {
// 写入数据库
//shopService.updateById(shop);
return shopService.update(shop);
}
@Resource
private StringRedisTemplate stringRedisTemplate;
@Override
public Result update(Shop shop) {
Long id = shop.getId();
if (id == null){
return Result.fail("店铺id不能为空");
}
//1.更新数据库
updateById(shop);
//2.删除缓存
stringRedisTemplate.delete(RedisConstants.CACHE_SHOP_KEY + id);
return null;
}