一、缓存更新策略
当数据在后端存储中发生变化时,需要更新Redis缓存以确保缓存中的数据与后端存储保持一致。以下是常见的缓存更新策略:
(1)主动更新: 在数据发生变化时,应用程序主动通知Redis更新缓存。这可以通过发布订阅机制、消息队列等方式实现。主动更新确保缓存数据及时更新,但增加了系统的复杂性。
(2)定时刷新: 周期性地刷新缓存,无论数据是否发生变化。虽然能够保持一定的实时性,但可能会导致不必要的缓存刷新,增加了系统负担。
(3)Lazy Load(懒加载): 在缓存失效时,不立即更新,而是等到下一次访问时再重新加载最新数据。这样可以避免在高并发情况下频繁地刷新缓存,但可能导致部分用户看到的是旧数据。
二、缓存失效策略
缓存中的数据可能因为多种原因失效,例如数据过期、被手动删除等。以下是缓存失效策略的常见选择:
(1)TTL(Time-To-Live): 为缓存设置过期时间,超过该时间则自动失效。Redis中使用EXPIRE命令或在存储数据时设置过期时间实现。
(2)LRU(Least Recently Used): 根据最近使用的数据来淘汰。当缓存空间不足时,会优先淘汰最近最少使用的数据。
(3)LFU(Least Frequently Used): 根据数据的访问频率来淘汰。使用一个计数器来记录数据被访问的次数,淘汰访问频率最低的数据。
(4)手动失效: 当数据发生变化时,通过程序手动删除缓存,强制缓存失效。
对于静态数据,可以采用TTL策略;对于动态数据,可以考虑LRU或LFU策略。
三、案例分析
当数据更新时,保证数据库和缓存的一致性,会遇到是先删除缓存,再操作数据库还是先操作数据库,再删除缓存的问题。这两种方案各有优缺点。
(1)先删除缓存,再操作数据库:
①优势:
通过先删除缓存再操作数据库,确保了在并发情况下,不会查询到过期或失效的缓存数据,从而保持了强一致性。
②劣势:
并发操作: 在极端情况下,如果线程一想更新数据库,线程一先删除缓存,而另一个线程而在此之后查询缓存,没有命中,于是查询数据库,并将以前删除的数据再次存入缓存。然而这时线程一还未更新完数据,这就造成了数据的不一致性。
(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+id);
return Result.ok();
}
在进入更新操作之前,首先检查店铺对象中的ID是否为空。如果ID为空,表示店铺ID无效,此时返回一个包含错误信息的失败结果。
然后更新数据库,调用 updateById(shop) 方法来更新数据库中的店铺信息。这里使用的是MyBatis框架提供的 updateById 方法,该方法用于根据主键ID更新数据库中的记录。
最后删除缓存,使用名为stringRedisTemplate操作String类型数据的Redis模板删除缓存中的相关店铺数据。这里使用了CACHE_SHOP_KEY +id作为缓存的键,CACHE_SHOP_KEY 是一个常量,为cache:shop,用于标识店铺缓存的前缀。通过删除缓存,确保下一次查询店铺信息时,可以从数据库中重新加载最新的数据到缓存中。
如图,使用PostMan测试工具,通过PUT来更新数据,将name由原先的103茶餐厅修改为106茶餐厅。如图19,响应为200OK,说明PUT成功更新数据。
如图,查看控制台,可以看到调用了代码中的update方法,name改为了106茶餐厅。
如下图,实现了先更新数据库,再删除缓存。