系列博客目录
文章目录
- 系列博客目录
- 缓存更新策略
- 总结
- 案例:给查询商铺的缓存添加超时剔除和主动更新的策略
说到解决数据库与缓存一致性的问题,其实就是要解决缓存更新的问题。
缓存更新策略
业务场景:
- 低一致性需求:使用内存淘汰机制。例如店铺类型的查询缓存
- 高一致性需求:主动更新,并以超时剔除作为兜底方案。例如店铺详情查询的缓存、优惠券的缓存。
我们一般使用主动更新,因为我们项目中存在券。
主动更新的策略有以下几种
这三种缓存策略各有优缺点,具体如下:
-
Cache Aside Pattern(缓存旁路模式)
- 定义:应用程序自己负责从缓存加载数据,缓存失效时才会从数据库加载。写入操作首先写入数据库,然后将数据更新到缓存。
缺点:
- 缓存一致性问题:如果数据库发生变化,缓存不一定同步更新。可能会出现读取旧数据的情况,除非应用程序做额外的缓存失效管理。
- 复杂性增加:程序需要管理缓存的读取和写入,以及缓存失效的机制,增加了开发和运维的复杂度。
- 缓存穿透:如果缓存失效并且数据库不存在该数据,频繁的缓存查询会对数据库造成压力。
-
Read/Write Through Pattern(读写穿透模式)
- 定义:所有的读操作都直接通过缓存来完成,如果缓存中没有,则从数据库加载并放入缓存;写操作首先写入缓存,然后同步到数据库。
缺点:
- 性能瓶颈:每次写操作都需要同步到数据库,可能会引起性能瓶颈,尤其在写频繁的场景下,数据库负载可能会很高。
- 数据库延迟:在写操作时,缓存和数据库必须保持一致,可能导致写入延迟,尤其在网络或数据库响应慢时更为明显。
- 缓存一致性问题:写操作先更新缓存,再更新数据库,如果缓存和数据库没有及时同步,可能会导致缓存数据不准确。
-
Write Behind Caching Pattern(写后缓存模式)
- 定义:写操作先写入缓存,然后异步地将数据写入数据库。即应用程序无需等待数据库写入操作完成。
缺点:
- 数据丢失风险:由于写入操作是异步的,如果缓存宕机或系统崩溃,可能导致数据没有及时写入数据库,造成数据丢失。
- 延迟性:虽然写操作速度较快,但因为数据库更新是异步的,可能会导致在短时间内查询到的数据不一致,无法保证实时性。
- 实现复杂性:需要保证缓存和数据库之间的最终一致性,通常需要额外的机制来保证数据的可靠性,如持久化缓存或重试机制。
总结来说,这三种模式的缺点主要体现在缓存一致性、系统复杂度和性能瓶颈上。在选择缓存策略时,需要根据应用场景权衡它们的优缺点。
第一种是要程序员自己编写一些代码的。企业中一般都是使用方案一,编码过程中需要考虑几个问题。
操作缓存和数据库时有三个问题需要考虑:
-
删除缓存还是更新缓存?
- 更新缓存:每次更新数据库都更新缓存,无效写操作较多
- 删除缓存:更新数据库时让缓存失效,查询时再更新缓存
-
如何保证缓存与数据库的操作的同时成功或失败?
- 单体系统,将缓存与数据库操作放在一个事务
- 分布式系统,利用TCC等分布式事务方案
-
先操作缓存还是先操作数据库?
- 先删除缓存,再操作数据库
- 先操作数据库,再删除缓存
考虑上述第3点时,会产生线程安全问题,如下:
如上图所示,此时数据库中是新数据20,但是缓存依旧是旧数据10。这种情况的概率比较大,因为写入缓存的速度很快,相比写数据库。
如上图所示,左边写入缓存的数据是旧数据,右边会导致数据库中的数据为新数据,导致了缓存与数据库的数据的不一致。这种情况发生概率很低,因为写缓存的速度一般快于写(更新)数据库。
所以我们应该先操作数据库,再更新缓存。
总结
缓存更新策略的最佳实践方案:
1.低一致性需求:使用Redis自带的内存淘汰机制
2.高一致性需求:主动更新,并以超时剔除作为兜底方案
- 读操作:
缓存命中则直接返回
缓存未命中则查询数据库,并写入缓存,设定超时时间 - 写操作:
先写数据库,然后再删除缓存
要确保数据库与缓存操作的原子性
案例:给查询商铺的缓存添加超时剔除和主动更新的策略
修改ShopController中的业务逻辑,满足下面的需求:
- 根据id查询店铺时,如果缓存未命中,则查询数据库,将数据库结果写入缓存,并设置超时时间根据id
2. 修改店铺时,先修改数据库,再删除缓存
@Override
@Transactional
public Result update(Shop shop) {Long id = shop.getId();if(id == null){return Result.fail("店铺id不能为空");}// 1.更新数据库updateById(shop);// 2.删除缓存stringRedisTemplate.delete(CACHE_SHOP_KEY + shop.getId());return Result.ok();}