核心思路:相较于原来从缓存中查询不到数据后直接查询数据库而言,现在的方案是 进行查询之后,如果从缓存没有查询到数据,则进行互斥锁的获取,获取互斥锁后,判断是否获得到了锁,如果没有获得到,则休眠,过一会再进行尝试,直到获取到锁为止,才能进行查询
如果获取到了锁的线程,再去进行查询,查询后将数据写入redis,再释放锁,返回数据,利用互斥锁就能保证只有一个线程去执行操作数据库的逻辑,防止缓存击穿

核心思路就是利用redis的setnx方法来表示获取锁,该方法含义是redis中如果没有这个key,则插入成功,返回1,在stringRedisTemplate中返回true, 如果有这个key则插入失败,则返回0,在stringRedisTemplate返回false,我们可以通过true,或者是false,来表示是否有线程成功插入key,成功插入的key的线程我们认为他就是获得到锁的线程。
- //获取锁
- 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);
- }
操作代码:
- public Shop queryWithMutex(Long id) {
- String key = CACHE_SHOP_KEY + id;
- // 1、从redis中查询商铺缓存
- String shopJson = stringRedisTemplate.opsForValue().get("key");
- // 2、判断是否存在
- if (StrUtil.isNotBlank(shopJson)) {
- // 存在,直接返回
- return JSONUtil.toBean(shopJson, Shop.class);
- }
- //判断命中的值是否是空值
- if (shopJson != null) {
- //返回一个错误信息
- return null;
- }
- // 4.实现缓存重构
- //4.1 获取互斥锁
- String lockKey = "lock:shop:" + id;
- Shop shop = null;
- try {
- boolean isLock = tryLock(lockKey);
- // 4.2 判断否获取成功
- if(!isLock){
- //4.3 失败,则休眠重试
- Thread.sleep(50);
- return queryWithMutex(id);
- }
- //4.4 成功,根据id查询数据库
- shop = getById(id);
- // 5.不存在,返回错误
- if(shop == null){
- //将空值写入redis
- stringRedisTemplate.opsForValue().set(key,"",CACHE_NULL_TTL,TimeUnit.MINUTES);
- //返回错误信息
- return null;
- }
- //6.写入redis
- stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(shop),CACHE_NULL_TTL,TimeUnit.MINUTES);
-
- }catch (Exception e){
- throw new RuntimeException(e);
- }
- finally {
- //7.释放互斥锁
- unlock(lockKey);
- }
- return shop;
- }