Redis 缓存穿透、击穿问题( 二 )

在获取锁失败时,证明已有线程在重建缓存,使当前线程休眠并重试(递归实现) 。
代码中需要注意的是synchronized关键字的使用,在获取到锁的时候,在判断下缓存是否存在(失效)double-check,该关键字锁的是当前对象 。在其关键字{}中是同步处理 。
推荐博客:https://blog.csdn.net/u013142781/article/details/51697672
然后进行测试代码,进行压力测试(jmeter),首先去除缓存中的值,模拟缓存失效 。
设置1000个线程,多线程执行间隔5s 。

Redis 缓存穿透、击穿问题

文章插图

Redis 缓存穿透、击穿问题

文章插图
所有的请求都是成功的,其qps大约在200,其吞吐量还是比较可观的 。然后看下缓存是否成功(只查询一次数据库);
Redis 缓存穿透、击穿问题

文章插图
逻辑过期:思路分析:
当用户开始查询redis时,判断是否命中,如果没有命中则直接返回空数据,不查询数据库,而一旦命中后,将value取出,判断value中的过期时间是否满足,如果没有过期,则直接返回redis中的数据,如果过期,则在开启独立线程后直接返回之前的数据,独立线程去重构数据,重构完成后释放互斥锁 。
Redis 缓存穿透、击穿问题

文章插图
封装数据:这里我们采用新建实体类来实现
12345678910/** * @author xbhog * @describe: * @date@Datapublic class RedisData {private LocalDateTime expireTime;private Object data;}使得过期时间和数据有关联关系,这里的数据类型是Object,方便后续不同类型的封装 。
123456789101112131415161718192021222324252627282930313233343536373839public Shop queryWithLogicalExpire( Long id ) {String key = CACHE_SHOP_KEY + id;// 1.从redis查询商铺缓存String json = stringRedisTemplate.opsForValue().get(key);// 2.判断是否存在if (StrUtil.isBlank(json)) {// 3.存在,直接返回return null;}// 4.命中,需要先把json反序列化为对象RedisData redisData = https://www.isolves.com/it/sjk/Redis/2023-01-31/JSONUtil.toBean(json, RedisData.class);Shop shop = JSONUtil.toBean((JSONObject) redisData.getData(), Shop.class);LocalDateTime expireTime = redisData.getExpireTime();// 5.判断是否过期if(expireTime.isAfter(LocalDateTime.now())) {// 5.1.未过期,直接返回店铺信息return shop;}// 5.2.已过期,需要缓存重建// 6.缓存重建// 6.1.获取互斥锁String lockKey = LOCK_SHOP_KEY + id;boolean isLock = tryLock(lockKey);// 6.2.判断是否获取锁成功if (isLock){exectorPool().execute(() -> {try {//重建缓存this.saveShop2Redis(id, 20L);} catch (Exception e) {throw new RuntimeException(e);} finally {unLock(lockKey);}});}// 6.4.返回过期的商铺信息return shop;}当前的执行流程跟互斥锁基本相同,需要注意的是,在获取锁成功后,我们将缓存重建放到线程池中执行,来异步实现 。
线程池代码:
12345678910111213141516/** * 线程池的创建 * @return */private static ThreadPoolExecutor exectorPool(){ThreadPoolExecutor executor = new ThreadPoolExecutor(5,//根据自己的处理器数量+1Runtime.getRuntime().availableProcessors()+1,2L,TimeUnit.SECONDS,new LinkedBlockingDeque<>(3),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());return executor;}缓存重建代码:1234567891011121314/** * 重建缓存 * @param id 重建ID * @param l 过期时间 */public void saveShop2Redis(Long id, long l){//查询店铺信息Shop shop = getById(id);//封装逻辑过期时间RedisData redisData = https://www.isolves.com/it/sjk/Redis/2023-01-31/new RedisData();redisData.setData(shop);redisData.setExpireTime(LocalDateTime.now().plusSeconds(l));stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY+id,JSONUtil.toJsonStr(redisData));}测试条件:100线程,1s线程间隔时间,缓存失效时间10s 。测试环境:缓存中存在对应的数据,并且在缓存快失效之前修改数据库中的数据,造成缓存与数据库不一致,通过执行压测,来查看相关线程返回的数据情况 。
Redis 缓存穿透、击穿问题

文章插图

Redis 缓存穿透、击穿问题

文章插图
从上述两张图中可以看到,在前几个线程执行过程中店铺name为102,当执行时间从19-20的时候店铺name发生变化为105,满足逻辑过期异步执行缓存重建的需求.?


推荐阅读