2.4 缓存穿透


2.5 缓存雪崩

2.6 缓存击穿




代码
@Overridepublic Result queryById(Long id) throws JsonProcessingException { String key = CacheConst.SHOP_FIELD + id; String shopCache = stringRedisTemplate.opsForValue().get(key); if (StrUtil.isNotBlank(shopCache)) { Shop shop = JSONUtil.toBean(shopCache, Shop.class); log.debug("走缓存 {}", shop); return Result.ok(shop); } String lockKey = CacheConst.CACHE_LOCK_SHOP+ id; System.out.println(lockKey); int count = 10; try { // 获取锁失败接着查询缓存看看其他进程有没有更新 while ( !this.tryLock(lockKey)) { if (count == 0) {break;} shopCache = stringRedisTemplate.opsForValue().get(key); log.info("获取锁失败 {}",shopCache); if (StrUtil.isNotBlank(shopCache)) { Shop shop = JSONUtil.toBean(shopCache, Shop.class); log.debug("走缓存 {}", shop); return Result.ok(shop); } TimeUnit.MILLISECONDS.sleep(500L); count--; } log.info("获取锁成功"); Shop shop = getById(id); if (shop == null) { Shop obj = new Shop(); obj.setId(id); stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(obj),CacheConst.NULL_TTL,CacheConst.NULL_UNIT); return Result.fail("店铺不存在"); } stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop)); stringRedisTemplate.expire(key, CacheConst.CACHE_TTL, CacheConst.CACHE_TTL_UNIT); return Result.ok(shop); } catch (InterruptedException e) { throw new RuntimeException(e); } finally { this.unlock(lockKey); }}private boolean tryLock(String lockKey){ boolean lock = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "1", 10, TimeUnit.SECONDS); return lock;}private boolean unlock(String lockKey){ boolean unlock = stringRedisTemplate.delete(lockKey); return unlock;}

String key = CacheConst.SHOP_FIELD + id;String shopCache = stringRedisTemplate.opsForValue().get(key);//todo 未命中直接返回if (StrUtil.isBlank(shopCache)) { log.debug("未命中==> {}", shopCache); return Result.fail("店铺不存在");}RedisData shopData = JSONUtil.toBean(shopCache, RedisData.class);JSONObject data = (JSONObject) shopData.getData();Shop shop = JSONUtil.toBean(data, Shop.class);// todo 命中判断是否过期if (shopData.getExpire() .isAfter(LocalDateTime.now())) { // todo 未过期 log.debug("未过期 {}", shopData); return Result.ok(shop);}// todo 过期String lockKey = CacheConst.CACHE_LOCK_SHOP + id;if (this.tryLock(lockKey)) { // todo 开启独立线程更新缓存刷新过期时间 System.err.println("刷新"); config.refreshRedis(lockKey,1000,getById(id),s->{ this.unlock(lockKey); System.err.println("解锁"); });}System.err.println("过期返回");//todo 未获取锁 返回过期return Result.ok(data);
2.7 缓存封装工具
@Slf4j@Componentpublic class CacheClient { private final StringRedisTemplate redisTemplate; public CacheClient(StringRedisTemplate redisTemplate) { this.redisTemplate = redisTemplate; } public void set(String key, Object value, long expireTime, TimeUnit unit) { String json = JSONUtil.toJsonStr(value); redisTemplate.opsForValue().set(key, json, expireTime, unit); } // 逻辑过期时间 public void setWithLogicalExpire(String key, Object value, long expireTime, TimeUnit unit) { RedisData redisData = new RedisData(); redisData.setData(value); redisData.setExpireTime(LocalDateTime.now().plusSeconds(unit.toSeconds(expireTime))); redisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(redisData)); } /** * 防止穿透 */ public <R, ID> R getWithPassThrough(String keyPrefix, ID id, Class<R> type, Function<ID, R> callback, long cacheTime, TimeUnit unit ) { String key = keyPrefix + id; String json = redisTemplate.opsForValue().get(key); // todo redis获取缓存 if (StrUtil.isNotBlank(json)) { R bean = JSONUtil.toBean(json, type); return bean; } // todo 判断是否为空值 "" null // 理解为序列化不了的 “” if (json != null) return null; // todo 查询数据库 R r = callback.apply(id); // todo 防止击穿 if (r == null) { redisTemplate.opsForValue().set(key, "", cacheTime, unit); return null; } // todo 存入redis this.set(key, r, cacheTime, unit); return r; } /** * 防止击穿逻辑锁 */ public <R, ID> R getWithLogicalExpire(String keyPrefix, ID id, Class<R> type, String lockKeyPrefix, Function<ID, R> dbcallback) { String key = keyPrefix + id; String typeCache = redisTemplate.opsForValue().get(key); //todo 未命中直接返回 if (StrUtil.isBlank(typeCache)) { log.debug("未命中==> {}", typeCache); return null; } RedisData redisData = JSONUtil.toBean(typeCache, RedisData.class); JSONObject data = (JSONObject) redisData.getData(); R r = JSONUtil.toBean(data, type); // todo 命中判断是否过期 if (redisData.getExpireTime().isAfter(LocalDateTime.now())) { // todo 未过期 log.debug("未过期 {}", redisData); return r; } // todo 过期 String lockKey = lockKeyPrefix + id; // todo 尝试获取锁 if (this.tryLock(lockKey)) { // todo 开启独立线程更新缓存刷新过期时间 System.err.println("刷新"); config.refreshRedis(lockKey, 10, dbcallback.apply(id), s -> { this.unlock(lockKey); System.err.println("解锁"); }); } System.err.println("过期返回"); return r; } /** * 缓存击穿互斥锁 */ public <R, ID> R getMutuallyExclusive(String keyPrefix, ID id, Class<R> type, String lockKeyPrefix, Function<ID, R> function, long cacheTime, TimeUnit cacheUnit ) { String key = keyPrefix + id; String cacheJson = redisTemplate.opsForValue().get(key); if (StrUtil.isNotBlank(cacheJson)) { R r = JSONUtil.toBean(cacheJson, type); log.debug("走缓存 {}", r); return r; } if (cacheJson != null) return null; String lockKey = lockKeyPrefix + id; int count = 10; try { // 获取锁失败接着查询缓存看看其他进程有没有更新 while (!this.tryLock(lockKey)) { if (count == 0) { return null; } cacheJson = redisTemplate.opsForValue().get(key); log.info("获取锁失败 {}", cacheJson); if (StrUtil.isNotBlank(cacheJson)) { R r = JSONUtil.toBean(cacheJson, type); log.debug("走缓存 {}", r); return r; } TimeUnit.MILLISECONDS.sleep(500); count--; } log.info("获取锁成功"); R r = function.apply(id); if (r == null) { redisTemplate.opsForValue().set(key, "", cacheTime, cacheUnit); return null; } redisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(r), cacheTime, cacheUnit); return r; } catch (InterruptedException e) { throw new RuntimeException(e); } finally { log.debug("释放锁"); this.unlock(lockKey); } } private boolean tryLock(String lockKey) { boolean lock = redisTemplate.opsForValue().setIfAbsent(lockKey, "1", 10, TimeUnit.SECONDS); return lock; } private boolean unlock(String lockKey) { boolean unlock = redisTemplate.delete(lockKey); return unlock; } @Autowired private MybatisConfig config;}