首页 > 其他分享 >商品查询业务之解决商品查询的缓存穿透、缓存雪崩、缓存击穿问题(封装工具类)

商品查询业务之解决商品查询的缓存穿透、缓存雪崩、缓存击穿问题(封装工具类)

时间:2023-09-13 23:59:42浏览次数:50  
标签:缓存 return String 查询 商品 key null id

商品查询业务之解决商品查询的缓存穿透、缓存雪崩、缓存击穿问题(封装工具类)

核心思路如下:

在原来的逻辑中,我们如果发现这个数据在mysql中不存在,直接就返回404了,这样是会存在缓存穿透问题的

现在的逻辑中:如果这个数据不存在,我们不会返回404 ,还是会把这个数据写入到Redis中,并且将value设置为空,欧当再次发起查询时,我们如果发现命中之后,判断这个value是否是null,如果是null,则是之前写入的数据,证明是缓存穿透数据,如果不是,则直接返回数据。

image-20230913222156008

小总结:

缓存穿透产生的原因是什么?

  • 用户请求的数据在缓存中和数据库中都不存在,不断发起这样的请求,给数据库带来巨大压力

缓存穿透的解决方案有哪些?

  • 缓存null值
  • 布隆过滤
  • 增强id的复杂度,避免被猜测id规律
  • 做好数据的基础格式校验
  • 加强用户权限校验
  • 做好热点参数的限流

解决缓存雪崩:

image-20230913222316802

解决缓存击穿:

image-20230913222357760

image-20230913222430910

image-20230913222446408

image-20230913222457300

image-20230913222518542

封装Redis工具类

把解决缓存穿透、缓存雪崩、缓存击穿的代码封装成函数,到时调用就行了

封装代码

@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)));
    // 写入Redis
    stringRedisTemplate.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) {
        // 将空值写入redis
        stringRedisTemplate.opsForValue().set(key, "", CACHE_NULL_TTL, TimeUnit.MINUTES);
        // 返回错误信息
        return null;
    }
    // 6.存在,写入redis
    this.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) {
            // 将空值写入redis
            stringRedisTemplate.opsForValue().set(key, "", CACHE_NULL_TTL, TimeUnit.MINUTES);
            // 返回错误信息
            return null;
        }
        // 6.存在,写入redis
        this.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);
}
}

在ShopServiceImpl 中调用

@Resource
private CacheClient cacheClient;

@Override
public 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);
}

标签:缓存,return,String,查询,商品,key,null,id
From: https://www.cnblogs.com/ahui-blog/p/17701117.html

相关文章

  • 执行一条 SQL 查询语句,期间发生了什么?
    执行一条SQL查询语句,期间发生了什么?连接器:建立连接,身份验证查询缓存:已经在mysql8.0被删除解析sql词法分析,解析关键词语法分析,根据词法分析得出的关键词判断语法是否有问题建立语法树执行sql预处理,检查表名和表字段是否存在,将select*的*转为全部字段优化,对sql语......
  • 使用R语言查询某物种所有通路及通路内的基因
    使用R语言查询某物种所有通路及通路内的基因,这里使用Y书的clusterProfiler包。这里以人类为例查询所有通路及通路内的基因:library(R.utils)R.utils::setOption("clusterProfiler.download.method","auto")hsa_kegg<-clusterProfiler::download_KEGG("hsa") 这里使用的......
  • MySql数据库中,对于同一个表,如果直接把查询结果赋值给待更新字段,则会出现锁表的情况。
    MySql数据库中,对于同一个表,如果直接把查询结果赋值给待更新字段,则会出现锁表的情况。原因是:mysql在from子句中遇到子查询时,先执行子查询并将结果放到一个临时表中,我们通常称它为“派生表”;临时表是没有索引、无法加锁的。update时,会锁表,此时不能再select。所以会报错,此时如果将......
  • 亚马逊API接口解析,实现按关键字搜索商品
    要解析亚马逊API接口并实现按关键字搜索商品,你需要按照以下步骤进行操作:了解亚马逊开发者中心:访问亚马逊开发者中心,并了解相关的API文档、开发者指南和规定。注册开发者账号:在亚马逊开发者中心上注册一个开发者账号,并创建一个应用,获取到API权限。获取API密钥:为了使用亚马逊API接......
  • 锁表查询,转载 https://www.toutiao.com/article/7275538336188695099/?channel=&sourc
    Oracle死锁与慢查询总结 查看死锁SELECTs.sid"会话ID",s.lockwait"等待锁",s.event"等待的资源/事件",--最近等待或正在等待的资源/事件DECODE(lo.locked_mode,0,'尚未获得锁',1,NULL,2,'行共享锁',3,'行排它锁',4,'共享表锁',5,�......
  • SpringBoot+Mybatis三级查询
    一、概述现有一个需求:查询视频详情。对应三张表,视频表、章节列表、集列表。一个视频对应多个章节,一个章节对应多集视频。请根据视频id查询视频详情,并把视频的章节列表,章节中的集列表都带出来。SpringBoot和MyBatis-plus说明:<!--根模块继承了SpringBoot,子模块也跟着继......
  • 银行卡发卡银行查询
    起始卡号发卡银行卡类型使用类型103农业银行金穗借记卡借记卡18572昆明农联社金碧卡借记卡255136建行255646建行303光大银行阳光卡借记卡356390中信银行信用卡中心(63020000)中信JCB美元卡贷记卡356391中信银行信用卡中心(63020000)中信JCB美元卡贷记卡356392中信银行信用卡中心(63......
  • 基于微信小程序的公交信息在线查询系统
    随着信息技术在管理上越来越深入而广泛的应用,管理信息系统的实施在技术上已逐步成熟。本文介绍了微信小程序公交信息在线查询系统的开发全过程。通过分析微信小程序公交信息在线查询系统信息管理的不足,创建了一个计算机管理微信小程序公交信息在线查询系统信息的方案。文章介绍了微......
  • DSL实现自动补全查询
         ......
  • 多表创建 外键以及多表查询
    1.多表创建的介绍表关系的创建一共有三种:一对一一对多多对多"""如何判断表关系:换位思考法"""#一对多以图书表和出版社表为例先站在图书表的角度问:1.一本图书能否有多个出版社出版?答:不能站在出版社的角度问:1.一个出版社能否......