2.4 缓存穿透
2.5 缓存雪崩
2.6 缓存击穿
代码
@Override
public 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
@Component
public 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;
}