Reids实战之查询缓存

对于一些不常变动却查询频率高的数据,对此进行数据库的访问会降低查询效率,此时可以使用redis缓存来解决,案例:查询商品信息

一 实现思路


可能出缓存穿透问题,以及缓存和数据库数据不一致的问题,解决思路:
(1)缓存空对象
(2)更新数据时,删除缓存

二 代码实现

(1)contreoller层

@RestController
@RequestMapping("/shop")
public class ShopController {@Resourcepublic IShopService shopService;/*** 根据id查询商铺信息* @param id 商铺id* @return 商铺详情数据*/@GetMapping("/{id}")public Result queryShopById(@PathVariable("id") Long id) {return shopService.queryById(id);}/*** 新增商铺信息* @param shop 商铺数据* @return 商铺id*/@PostMappingpublic Result saveShop(@RequestBody Shop shop) {// 写入数据库shopService.save(shop);// 返回店铺idreturn Result.ok(shop.getId());}/*** 更新商铺信息* @param shop 商铺数据* @return 无*/@PutMappingpublic Result updateShop(@RequestBody Shop shop) {// 写入数据库return shopService.update(shop);}
}

(2)service层

@Service
public class ShopServiceImpl extends ServiceImpl<ShopMapper, Shop> implements IShopService {@Resourceprivate StringRedisTemplate stringRedisTemplate;@Resourceprivate CacheClient cacheClient;@Overridepublic Result queryById(Long id) {// 解决缓存穿透Shop shop = cacheClient.queryWithPassThrough(CACHE_SHOP_KEY, id, Shop.class, this::getById, CACHE_SHOP_TTL, TimeUnit.MINUTES);// 互斥锁解决缓存击穿// Shop shop = cacheClient//         .queryWithMutex(CACHE_SHOP_KEY, id, Shop.class, this::getById, CACHE_SHOP_TTL, TimeUnit.MINUTES);// 逻辑过期解决缓存击穿// Shop shop = cacheClient//         .queryWithLogicalExpire(CACHE_SHOP_KEY, id, Shop.class, this::getById, 20L, TimeUnit.SECONDS);if (shop == null) {return Result.fail("店铺不存在!");}// 7.返回return Result.ok(shop);}@Override@Transactionalpublic 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();}}

(3)缓存工具类

@Slf4j
@Component
public class CacheClient {private final StringRedisTemplate stringRedisTemplate;private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);public CacheClient(StringRedisTemplate stringRedisTemplate) {this.stringRedisTemplate = stringRedisTemplate;}public void set(String key, Object value, Long time, TimeUnit unit) {stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(value), time, unit);}public void setWithLogicalExpire(String key, Object value, Long time, TimeUnit unit) {// 设置逻辑过期RedisData redisData = new RedisData();redisData.setData(value);redisData.setExpireTime(LocalDateTime.now().plusSeconds(unit.toSeconds(time)));// 写入RedisstringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(redisData));}public <R,ID> R queryWithPassThrough(String keyPrefix, ID id, Class<R> type, Function<ID, R> dbFallback, Long time, TimeUnit unit){String key = keyPrefix + id;// 1.从redis查询商铺缓存String json = stringRedisTemplate.opsForValue().get(key);// 2.判断是否存在if (StrUtil.isNotBlank(json)) {// 3.存在,直接返回return JSONUtil.toBean(json, type);}// 判断命中的是否是空值if (json != null) {// 返回一个错误信息return null;}// 4.不存在,根据id查询数据库R r = dbFallback.apply(id);// 5.不存在,返回错误if (r == null) {// 将空值写入redisstringRedisTemplate.opsForValue().set(key, "", CACHE_NULL_TTL, TimeUnit.MINUTES);// 返回错误信息return null;}// 6.存在,写入redisthis.set(key, r, time, unit);return r;}public <R, ID> R queryWithLogicalExpire(String keyPrefix, ID id, Class<R> type, Function<ID, R> dbFallback, Long time, TimeUnit unit) {String key = keyPrefix + id;// 1.从redis查询商铺缓存String json = stringRedisTemplate.opsForValue().get(key);// 2.判断是否存在if (StrUtil.isBlank(json)) {// 3.存在,直接返回return null;}// 4.命中,需要先把json反序列化为对象RedisData redisData = JSONUtil.toBean(json, RedisData.class);R r = JSONUtil.toBean((JSONObject) redisData.getData(), type);LocalDateTime expireTime = redisData.getExpireTime();// 5.判断是否过期if(expireTime.isAfter(LocalDateTime.now())) {// 5.1.未过期,直接返回店铺信息return r;}// 5.2.已过期,需要缓存重建// 6.缓存重建// 6.1.获取互斥锁String lockKey = LOCK_SHOP_KEY + id;boolean isLock = tryLock(lockKey);// 6.2.判断是否获取锁成功if (isLock){// 6.3.成功,开启独立线程,实现缓存重建CACHE_REBUILD_EXECUTOR.submit(() -> {try {// 查询数据库R newR = dbFallback.apply(id);// 重建缓存this.setWithLogicalExpire(key, newR, time, unit);} catch (Exception e) {throw new RuntimeException(e);}finally {// 释放锁unlock(lockKey);}});}// 6.4.返回过期的商铺信息return r;}public <R, ID> R queryWithMutex(String keyPrefix, ID id, Class<R> type, Function<ID, R> dbFallback, Long time, TimeUnit unit) {String key = keyPrefix + id;// 1.从redis查询商铺缓存String shopJson = stringRedisTemplate.opsForValue().get(key);// 2.判断是否存在if (StrUtil.isNotBlank(shopJson)) {// 3.存在,直接返回return JSONUtil.toBean(shopJson, type);}// 判断命中的是否是空值if (shopJson != null) {// 返回一个错误信息return null;}// 4.实现缓存重建// 4.1.获取互斥锁String lockKey = LOCK_SHOP_KEY + id;R r = null;try {boolean isLock = tryLock(lockKey);// 4.2.判断是否获取成功if (!isLock) {// 4.3.获取锁失败,休眠并重试Thread.sleep(50);return queryWithMutex(keyPrefix, id, type, dbFallback, time, unit);}// 4.4.获取锁成功,根据id查询数据库r = dbFallback.apply(id);// 5.不存在,返回错误if (r == null) {// 将空值写入redisstringRedisTemplate.opsForValue().set(key, "", CACHE_NULL_TTL, TimeUnit.MINUTES);// 返回错误信息return null;}// 6.存在,写入redisthis.set(key, r, time, unit);} catch (InterruptedException e) {throw new RuntimeException(e);}finally {// 7.释放锁unlock(lockKey);}// 8.返回return r;}private boolean tryLock(String key) {Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);return BooleanUtil.isTrue(flag);}private void unlock(String key) {stringRedisTemplate.delete(key);}
}

Redis实战之查询缓存相关推荐

  1. Spring Boot之基于Redis实现MyBatis查询缓存解决方案

    转载自 Spring Boot之基于Redis实现MyBatis查询缓存解决方案 1. 前言 MyBatis是Java中常用的数据层ORM框架,笔者目前在实际的开发中,也在使用MyBatis.本文主要 ...

  2. 怎么查询redis缓存的数据_阿里开发十年写出这份「Redis简明教程」+「Redis实战」请你查收...

    Redis是啥?用Redis官方的话来说就是: Redis is an open source (BSD licensed), in-memory data structure store, used ...

  3. 黑马点评Redis实战(短信登录;商户查询缓存)

    黑马点评 通过一个类似于大众点评的项目了解学习redis在实战项目中的使用,下面是项目中会涉及到的模块: 一.导入黑马点评项目 导入springboot项目,导入sql脚本到数据库,开启nginx,更 ...

  4. Redis入门到实战(实战篇)缓存更新、穿透、雪崩、击穿!

    Redis基础篇 Java面试宝典-redis 实战篇Redis 开篇导读 亲爱的小伙伴们大家好,马上咱们就开始实战篇的内容了,相信通过本章的学习,小伙伴们就能理解各种redis的使用啦,接下来咱们来 ...

  5. 猿创征文 | 微服务 Spring Boot 整合Redis 实战开发解决高并发数据缓存

    文章目录 一.什么是 缓存? ⛅为什么用缓存? ⚡如何使用缓存 二.实现一个商家缓存 ⌛环境搭建 ♨️核心源码 ✅测试接口 三.采用 微服务 Spring Boot 注解开启缓存 ✂️@CacheEn ...

  6. 【Caffeine进阶】Redis+Caffeine 两级缓存实战,性能爆缸

    往期回顾 博主前面发过一篇[缓存框架Caffeine]初级篇,主要介绍了Caffeine的入门级使用!地址https://blog.csdn.net/Number_oneEngineer/articl ...

  7. redis有值查询返回null_Redis缓存穿透、缓存并发、热点缓存之最佳招式

    一.前言 我们在用缓存的时候,不管是Redis或者Memcached,基本上会通用遇到以下三个问题: 缓存穿透 缓存并发 缓存失效 缓存穿透 注: 上面三个图会有什么问题呢? 我们在项目中使用缓存通常 ...

  8. springboot controller 分页查询_Spring Boot实战分页查询附近的人:Redis+GeoHash+Lua

    前言 最近在做社交的业务,用户进入首页后需要查询附近的人: 项目状况:前期尝试业务阶段: 特点: 快速实现(不需要做太重,满足初期推广运营即可) 快速投入市场去运营 收集用户的经纬度: 用户在每次启动 ...

  9. redis 查询缓存_Redis缓存总结:淘汰机制、缓存雪崩、数据不一致....

    在实际的工作项目中, 缓存成为高并发.高性能架构的关键组件 ,那么Redis为什么可以作为缓存使用呢?首先可以作为缓存的两个主要特征: 在分层系统中处于内存/CPU具有访问性能良好, 缓存数据饱和,有 ...

最新文章

  1. asp mysql 更新数据_Asp更新数据库的几种方法
  2. linux select read阻塞_linux下的IO模型详解
  3. 晋升新一线的合肥,跨平台的.NET氛围究竟如何?
  4. 信用更正和贷方剩余数量
  5. 设计模式六大原则你都知道吗?
  6. 浅析ASP.NET 2.0的用户密码加密机制
  7. vue hot true 不起作用_Vue + Flask 小知识(二)
  8. 软件工程导论 07章软件测试
  9. HTML5矢量实现文件上传进度条
  10. python设计模式-模板方法模式 1
  11. 解决nginx访问php文件变成下载
  12. kali攻击139端口_入侵445端口-永恒之蓝漏洞利用-Metasploit
  13. Linux下配置JSHOP2环境
  14. HSI、HSV、RGB、CMY、CMYK、HSL、HSB、Ycc、XYZ、Lab、YUV颜色模型
  15. 那些值得一读再读的好书
  16. 盗版升级win10仍是盗版
  17. 【我与bug那些事】Vue 点击选项(有相应分数)实现分数相加【思路】
  18. 会声会影编辑html,会声会影视频声音编辑
  19. 飞腾CPU服务器系统安装问题
  20. 队友代码拜读(文末附福利呦)

热门文章

  1. 代码审计之PHP常用函数总结
  2. Mac谷歌(chrome)浏览器快捷键
  3. C语言题目——通讯录(静态存储+动态存储)
  4. springboot 整合Swagger及美化
  5. 吴恩达深度学习编程作业报错解决方法汇总
  6. 地信遥感专业(本科、研究生小白适用)的网站资源分享(持续更新)
  7. 【避坑】minio临时凭证STS实现上传,下载
  8. FDTD 中石墨烯材料如何设置
  9. 位运算(3)-- 高级运用
  10. 【五天时间】Qt从入门到实战:第一天