缓存穿透,缓存雪崩,使用互斥锁解决缓存击穿

一、缓存穿透

1. isNotBlank()方法和isNotEmpty()方法的区别

在常见的 Java 编程语言中,isNotBlank() 和 isNotEmpty() 方法通常用于判断字符串是否为空。它们通常用于 Apache Commons StringUtils 库中。

isNotBlank() 方法用于判断一个字符串是否有值并且不是全由空格组成。
如果字符串为 null,或者为空字符串 “”,或者只包含空格字符,则 isNotBlank() 方法会返回 false。
如果字符串不为 null 且长度大于 0,并且至少包含一个非空格字符,则 isNotBlank() 方法会返回 true。

isNotEmpty() 方法用于判断一个字符串是否不为空。
如果字符串为 null,或者为空字符串 “”,则 isNotEmpty() 方法会返回 false。
如果字符串不为 null 且长度大于 0,则 isNotEmpty() 方法会返回 true。

因此,两者的区别在于对空格字符的处理。isNotBlank() 方法会额外检查字符串中是否有非空格字符,而 isNotEmpty() 方法仅判断字符串是否为空或为 null。

2. 缓存穿透

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

3. 缓存雪崩

在这里插入图片描述

4. 缓存击穿的解决

在这里插入图片描述
可以通过和setnx一样的思路
在这里插入图片描述

5 ctrl+alt+T,可以快速生成包围方式:

在这里插入图片描述

6 //模拟重建延时

        Thread.sleep(200);这是 Java Thread 类中的一个静态方法。它使当前线程暂停(或休眠)指定的时间。
        下面就是我们的互斥锁的使用和定义了。

7. 代码 (使用互斥锁)

package com.hmdp.service.impl;

import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.hmdp.dto.Result;
import com.hmdp.entity.Shop;
import com.hmdp.mapper.ShopMapper;
import com.hmdp.service.IShopService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;

import java.util.concurrent.TimeUnit;

import static com.hmdp.utils.RedisConstants.CACHE_SHOP_KEY;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author 虎哥
 * @since 2021-12-22
 */
@Service
public class ShopServiceImpl extends ServiceImpl<ShopMapper, Shop> implements IShopService {

    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Override
    public Result queryById(Long id) {//首先,根据id在redis中查询店铺缓存
        //缓存穿透
       // Shop shop = queryWithPassThrough(id);

        //用互斥锁解决缓存击穿
        Shop shop = queryWithMutex(id);
        if (shop == null) {
            return Result.fail("店铺不存在");
        }

        //7.然后返回。
        return Result.ok(shop);
    }

    public Shop queryWithMutex(Long id){//互斥锁
        String key =CACHE_SHOP_KEY + id;    //定义一个key,包装id和一个字段名
        //1.从redis中查询商铺缓存
        String shopJson = stringRedisTemplate.opsForValue().get(key);//存的是一个对象可以用哈希,也可以用String,这里用hash演示
        //2.判断是否存在
        if (StrUtil.isNotBlank(shopJson)) {//isNotBlank只有里面有字符的时候才是true,null和空或者/n都为空false
            //3.存在,直接返回
            //把JSON对象转化为shop对象
            return JSONUtil.toBean(shopJson, Shop.class);
        }
        //判断命中的是否是空值
        if (shopJson != null) {
            //返回一个错误信息
            return null;
        }
        //4.开始实现缓存重建
        //4.1 获取互斥锁
        String lockKey = "lock:shop:" + id;    //定义一个key,包装id和一个字段名
        Shop shop = null;
        try {
            boolean isLock = tryLock(lockKey);
            //4.2 判断是否获取成功
            if(isLock){
                //4.3 失败,则休眠并重试
                Thread.sleep(50);
             return    queryWithMutex(id);
            }
            //4.3 失败,则休眠并重试
            //4.4不存在,根据id查询数据库
            shop = getById(id);
            //模拟重建延时
            Thread.sleep(200);
            //5.不存在,返回错误
            if (shop == null) {
                //将空值,写入redis
                stringRedisTemplate.opsForValue().set(key,"null",2L, TimeUnit.MINUTES);
                //返回错误信息
                return null;
            }
            //6.存在,把数据写入redis,
            stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(shop),30L, TimeUnit.MINUTES);//
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }finally {
            //7.释放互斥锁
            unlock(lockKey);
        }

        //8.然后返回。
        return shop;
    }

    public Shop queryWithPassThrough(Long id){
        String key =CACHE_SHOP_KEY + id;    //定义一个key,包装id和一个字段名
        //1.从redis中查询商铺缓存
        String shopJson = stringRedisTemplate.opsForValue().get(key);//存的是一个对象可以用哈希,也可以用String,这里用hash演示
        //2.判断是否存在
        if (StrUtil.isNotBlank(shopJson)) {//isNotBlank只有里面有字符的时候才是true,null和空或者/n都为空false
            //3.存在,直接返回
  //把JSON对象转化为shop对象
            return JSONUtil.toBean(shopJson, Shop.class);
        }
        //判断命中的是否是空值
        if (shopJson != null) {
            //返回一个错误信息
            return null;
        }
        //4.不存在,根据id查询数据库
        Shop shop = getById(id);
        //5.不存在,返回错误
        if (shop == null) {
            //将空值,写入redis
            stringRedisTemplate.opsForValue().set(key,"null",2L, TimeUnit.MINUTES);
            //返回错误信息
            return null;
        }
        //6.存在,把数据写入redis,
        stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(shop),30L, TimeUnit.MINUTES);//
        //7.然后返回。
        return shop;
    }

    //拿到锁
    private boolean tryLock(String key) {
        Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);//相当于setnx
    return BooleanUtil.isTrue(flag);//判断是否成功,因为直接返回可能会导致拆箱
    }
    //释放锁
    private void unlock(String key){
        stringRedisTemplate.delete(key);
    }
    @Override
    @Transactional
    public Result update(Shop shop) {
        Long id = shop.getId();
        if (id == null) {
            return Result.fail("店铺id不能为空");
        }
        //1.更新数据库
        updateById(shop);
        //2.删除缓存
        stringRedisTemplate.delete(CACHE_SHOP_KEY + id);
        //3.返回结果
        return Result.ok();
    }
}

在这里插入图片描述
在这里插入图片描述

  • 11
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
缓存穿透缓存雪崩缓存击穿是常见的缓存问题,它们的解决办法如下: 1. 缓存穿透 缓存穿透指的是查询一个一定不存在的数据,由于缓存不命中,查询会落到数据库上,这将给数据库造成巨大压力。解决办法如下: - 布隆过滤器:在缓存层之前加入布隆过滤器,可以快速判断出一个key是否存在于缓存中,从而避免对不存在的key进行数据库查询。 - 缓存空对象:将不存在的key也存入缓存,但是value设置为null或者一个空对象,这样下次请求相同的key就会直接命中缓存,而不会落到数据库上。 2. 缓存雪崩 缓存雪崩指的是缓存中大量的key同时失效,导致所有请求都落到数据库上,造成数据库瞬间压力过大而崩溃。解决办法如下: - 设置不同的过期时间:将过期时间分散开来,避免大量的key同时失效。 - 缓存数据预热:在系统启动时,将一些常用的数据预先加载到缓存中,避免在某一时刻缓存大量失效。 3. 缓存击穿 缓存击穿指的是一个热点key失效,导致大量请求落到数据库上,造成数据库瞬间压力过大而崩溃。解决办法如下: - 加互斥锁使用互斥锁,避免多个线程同时查询数据库,可以保证只有一个线程去查询数据库,其他线程等待查询结果。 - 设置热点数据永不过期:将一些热点数据设置成永不过期,避免在失效时造成缓存击穿

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值