前言
缓存击穿有很多情况,主要原因是缓存失效,直接砸到db上。
这篇文章思路是在缓存失效时,只允许一个线程去初始化,这才是比较合理的方法,防止多个线程去一起初始化。当然还有很多骚操作,key保存不同的过期时间,不要同一时间砸到db上。
上代码
@Slf4j
@Service
public class TestServiceImpl implements com.yatsenglobal.cloud.service.lottery.TestService {
@Resource
private StringRedisTemplate stringRedisTemplate;
@Resource
private RedissonClient redissonClient;
@Resource
private ApplicationContext ctx;
@Override
public String getConfig() {
String value = stringRedisTemplate.opsForValue().get("dajitui123");
if (StrUtil.isNotBlank(value)) {
return value;
}
RLock lock = redissonClient.getLock("dajitui321");
Boolean isGetLock;
try {
isGetLock = lock.tryLock(10, TimeUnit.SECONDS);
} catch (InterruptedException e) {
throw new BusinessException();
}
try {
if (isGetLock) {
//查询数据
log.info("获取锁开始查询数据库...");
Thread.sleep(2000);
stringRedisTemplate.opsForValue().set("dajitui123", "123", 2, TimeUnit.SECONDS);
return "123";
} else {
return ctx.getBean(TestServiceImpl.class).getRedisValue();
}
} catch (InterruptedException e) {
throw new BusinessException();
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
@MyRetry(times = 2)
@Override
public String getRedisValue() throws InterruptedException {
log.info("开始获取值...");
String value = stringRedisTemplate.opsForValue().get("dajitui123");
if (StrUtil.isNotBlank(value)) {
return value;
} else {
Thread.sleep(2000 + new Random().nextInt(500));
throw new RetryBusinessException(ActivityErrorCodeEnum.AC5200003);
}
}
}
getRedisValue这个主要是在获取不到缓存值的时候,延迟时间去获取,当然这个时间是需要根据查询数据的时间做相应的调整~
流程图
获取缓存值->yes->返回
获取缓存值->no->获取锁->yes->读数据库,填缓存
获取缓存值->no->获取锁->no->通过重试,睡眠进行等待
总结
这里使用分布锁来实现缓存失效时只有一个线程去初始化,不然有很多砸到数据库。其次这个措施防止不了说数据库本来就没有的数据,所以各种情况要分开考虑
防止数据库不存在的数据被击穿
个人想法
不管你查什么东西,我都给你缓存起来,那就obk了。当我真正去生成或者修改数据的时候,删除缓存。再次查询的时候就存在了。