Redis学习之缓存实现及缓存更新
什么是缓存?
缓存就是数据交换的缓冲区(称作Cache [ kæʃ ] ),是存贮数据的临时地方,一般读写性能较高。
为什么需要缓存?
提前准备好数据,便于更快地读写。
缓存是把双刃剑,要权衡利弊。
优点:降低后端负载
提高读写效率,降低响应时间
缺点:数据一致性成本 代码维护成本 运维成本
实现
关键流程
1.暂无缓存,从数据库读,然后设置(更新)缓存
2.已有缓存,直接读缓存
代码示例
/** * 根据id查询商铺信息 * @param id 商铺id * @return 商铺详情数据 */ @GetMapping("/{id}") public Result queryShopById(@PathVariable("id") Long id) { return shopService.queryById(id); }
ShopServiceImpl
@Override public Result queryById(Long id) { String key = CACHE_SHOP_KEY + id; // 1.从redis查询商铺缓存 String shopJson = stringRedisTemplate.opsForValue().get(key); // 2.判断是否存在 if (StrUtil.isNotBlank(shopJson)) { // 3.存在,直接返回 Shop shop = JSONUtil.toBean(shopJson, Shop.class); return Result.ok(shop); } // 判断命中的是否是空值 if (shopJson != null) { // 返回一个错误信息 return Result.fail("店铺不存在!"); } // 4.不存在,根据id查询数据库 Shop shop = getById(id); if (shop == null){ // 将空值写入redis stringRedisTemplate.opsForValue().set(key,"",CACHE_NULL_TTL, TimeUnit.MINUTES); // 5.不存在返回错误 return Result.fail("店铺不存在!"); } // 6.存在,写入redis stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(shop),CACHE_SHOP_TTL, TimeUnit.MINUTES); // 7.返回 return Result.ok(shop); }
缓存更新
几种常用策略,一般会选择主动更新+超时提出兜底。
由于我们的缓存的数据源来自于数据库,而数据库的数据是会发生变化的,因此,如果当数据库中数据发生变化,而缓存却没有同步,此时就会有一致性问题存在,其后果是:
用户使用缓存中的过时数据,就会产生类似多线程数据安全问题,从而影响业务,产品口碑等
主动更新缓存的几种方式
1.Cache Aside Pattern 人工编码方式:缓存调用者在更新完数据库后再去更新缓存,也称之为双写方案。(以数据库的数据为准,业务层自己来控制缓存的写入)
2.Read/Write Through Pattern : 由系统本身完成,数据库与缓存的问题交由系统本身去处理.(使用现成的数据写入服务,让服务来维护数据库和缓存的一致性,我门只用写数据即可)
3.Write Behind Caching Pattern :调用者只操作缓存,其他线程去异步处理数据库,实现最终一致。(先更新缓存,通过异步线程定期将缓存的数据持久化(同步道数据库中))
操作缓存和数据库时有三个问题需要考虑: 1.删除缓存还是更新缓存? 更新缓存:每次更新数据库都更新缓存,无效写操作较多 删除缓存:更新数据库时让缓存失效,查询时再更新缓存
2.多线程情况下,如何保证缓存与数据库的操作的同时成功或失败? 1.单体系统:将缓存与数据库操作放在一个事务 2.分布式系统:利用TCC等分布式事务方案
3.先操作缓存还是先操作数据库?
下图为两种方式在多线程下可能出现的问题
两种方式都不能百分百保证一致性,但【先操作数据库,再删除缓存】,出现问题的几率低,可以配合延时双删保证缓存一定被删除。
实现
核心思路如下:
修改ShopController中的业务逻辑,满足下面的需求:
根据id查询店铺时,如果缓存未命中,则查询数据库,将数据库结果写入缓存,并设置超时时间
根据id修改店铺时,先修改数据库,再删除缓存
修改重点代码1:修改ShopServiceImpl的queryById方法
设置redis缓存时添加过期时间
修改重点代码2
代码分析:通过之前的淘汰,我们确定了采用删除策略,来解决双写问题,当我们修改了数据之后,然后把缓存中的数据进行删除,查询时发现缓存中没有数据,则会从mysql中加载最新的数据,从而避免数据库和缓存不一致的问题
@Transactional @Override 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(); }
本文作者:万事胜意k
本文链接:https://www.cnblogs.com/ysk0904/p/17717756.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步