定义
缓存中的旧数据与数据库不一致。
缓存更新策略的类型
1.内存淘汰,利用redis的内存淘汰机制,当内存不足时自动淘汰部分数据。下次查询时更新缓存。redis默认开启了此机制。这种保证数据的一致性差。
2.超时剔除,给缓存数据添加TTL时间,到期后自动删除缓存,下次查询时更新缓存。一致性一般。
3.主动更新,自己编写业务逻辑,在修改数据库的同时更新缓存。一致性好。
根据业务场景选择
1.低一致性需求,可以使用内存淘汰机制。例如店铺类型的查询的缓存。
2.高一致性需求,主动更新,并以超时剔除作为兜底方案。例如店铺详情查询的缓存。
主动更新策略
1.由缓存的调用者,在更新数据库的同时更新缓存。(推荐)
2.缓存和数据库做为一个服务,由服务来维护一致性。调用者调用该服务,无需关心缓存的一致性问题。
3.调用者只操作缓存,由其他线程异步的将缓存数据持久化到数据库,保证最终一致。(好处:如果对缓存做了多次更新操作,只需要将最后一次更新操作同步到数据库,提升了性能。缺点:如果一段时间内对缓存执行了上千次操作还没来得及将数据同步到数据库,那么这一段时间缓存与数据库的数据是不一致的。所以可靠性和一致性都会存在问题。)
更新数据库的同时更新缓存
需要注意的考虑的问题
1.删除缓存还是更新缓存?
更新缓存:每次更新数据库都要更新缓存,无效写操作太多。
删除缓存:更新数据库时让缓存失效,查询时再更新缓存。(推荐)
2.如果保证缓存和数据库操作的同时成功和同时失败?
单体项目:将缓存和数据库操作放在一个事务中。
分布式系统:利用TCC等分布式事务方案。
3.先操作缓存还是先操作数据库?
先删缓存,再操作数据库
考虑多线程情况下的特殊情况:线程一删除缓存后,还没来得即更新数据库中的数据。此时线程来了发现缓存中没有数据就查询数据库然后将数据写回缓存。然后线程1才执行更新数据库的操作,就造成了缓存和数据库数据的不一致。这种情况发生的概率高,因为线程一删除缓存很快,更新数据库很慢。 线程二查询缓存、查询数据库、写入缓存操作都很快。
先操作数据库,再删除缓存
这种情况发生的概率很低。
满足一些条件:
1.多个线程并行进行。
2.其中一个线程查询数据的时候,恰好缓存失效。同时会去查询数据库并将数据写入缓存。这些操作都很快,都在微妙级别。就在查询数据库和写入缓存之间,突然来了一个线程先去更新数据库,然后删除缓存。更新数据库肯定比较慢的。所以这种情况的可能性极低。因为缓存的写速度远远大于对数据库的操作。
3.发生概率低,不代表不会发生。万一发生了,我们可以给缓存设置一个超时时间,过一段时间,缓存数据失效,就会跟数据库中的数据保持一致。超时剔除作为兜底方案。
读操作
缓存命中则直接返回,缓存未命中则查数据库,并写入缓存,设定超时时间。
写操作
先写数据库,然后再删除缓存。确保数据库与缓存操作的原子性。
@Override
public Result queryById(Long id) {
String key = RedisConstants.CACHE_SHOP_KEY + id;
//从redis中查询商铺缓存
String shopJsonStr = stringRedisTemplate.opsForValue().get(key);
//redis中有数据直接返回
if(StrUtil.isNotBlank(shopJsonStr)) {
Shop shop = JSONUtil.toBean(shopJsonStr, Shop.class);
return Result.ok(shop);
}
//redis中没有数据,继续查询数据库
Shop shop = getById(id);
if(ObjectUtil.isNull(shop)) {
//数据库没有查询到数据,返回错误
return Result.fail("店铺不存在");
}
//数据库中查询到数据,存入redis,再返回数据;设置超时时间
stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop), RedisConstants.CACHE_SHOP_TTL, TimeUnit.MINUTES);
return Result.ok(shop);
}
读操作设置超时时间
@Override
@Transactional
public Result updateShop(Shop shop) {
if(shop.getId() == null) {
return Result.fail("店铺id不能为空,更新失败");
}
//1.更新数据库
updateById(shop);
//2.删除缓存
stringRedisTemplate.delete(RedisConstants.CACHE_SHOP_KEY + shop.getId());
return Result.ok();
}
{
"id":1,
"area":"北京",
"openHours":"10:00-22:00",
"sold":4215,
"address":"北京王府井19号",
"comments": 3035,
"avgPrice": 80,
"score": 37,
"name": "画画茶餐厅",
"typeId": 1
}