03.利用Redis实现缓存功能---解决缓存穿透版

学习目标:

提示:学习如何利用Redis实现添加缓存功能解决缓存穿透版


学习产出:

缓存穿透讲解图

解决方案:

  1. 采用缓存空对象
  2. 采用布隆过滤器
    解决方案流程图

1. 准备pom环境

java 复制代码
		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
            <version>5.1.47</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.3</version>
        </dependency>
        <!--hutool-->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.7.17</version>
        </dependency>

2. 配置ThreadLocal和过滤器

java 复制代码
public class UserHolder {
    private static final ThreadLocal<UserDTO> tl = new ThreadLocal<>();

    public static void saveUser(UserDTO user){
        tl.set(user);
    }

    public static UserDTO getUser(){
        return tl.get();
    }

    public static void removeUser(){
        tl.remove();
    }
}
java 复制代码
@Configuration
public class MvcConfig implements WebMvcConfigurer {
    @Autowired
    private StringRedisTemplate redis;
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor()).excludePathPatterns("/user/code","/user/login","/blog/hot","/shop/**","/shop-type/**","/voucher/**").order(2);
        registry.addInterceptor(new RefreshTokenInterceptor(redis)).addPathPatterns("/**").order(1);
    }
}
---------------------------------------------
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {

    //controller执行之前
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //1.判断是否需要拦截ThreadLocal
        if (UserHolder.getUser()==null) {
            response.setStatus(401);
            return false;
        }
        //7.放行
        return true;
    }
    //渲染后返回给前台数据前
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        //移除用户,避免内存泄露
        UserHolder.removeUser();
    }
}
---------------------------------------------------
@Slf4j
public class RefreshTokenInterceptor implements HandlerInterceptor {
    //这个对象不是由spring管理的所以不能用注解自动注入

    private StringRedisTemplate redis;

    public RefreshTokenInterceptor(StringRedisTemplate redis) {
        this.redis = redis;
    }

    //controller执行之前
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //1.获取请求头中的token
        String token = request.getHeader("authorization");
        if (StrUtil.isBlank(token)) {
            return true;
        }
        //2.基于token获取redis中的用户
        //通过key取到hash中的map集合数据
        Map<Object, Object> userMap = redis.opsForHash().entries("login:token:" + token);
        //3.判断用户是否存在
        if (userMap.isEmpty()) {
            return true;
        }
        //5.将查询到的hash数据转为userDto对象
        UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);
        //6.存在,保存用户信息到ThreadLocal中
        UserHolder.saveUser(userDTO);
        //7.刷新token有效期
        redis.expire(LOGIN_USER_KEY + token, 30, TimeUnit.MINUTES);
        log.info("我是第一个拦截器当前拦截所有请求的用户为,线程为{},{}",UserHolder.getUser(),Thread.currentThread());
        //8.放行
        return true;
    }

3. Controller层:负责接收请求和向下分配

java 复制代码
@RestController
@RequestMapping("/shop")
public class ShopController {
    @Resource
    public IShopService shopService;
    /**
     * 根据id查询商铺信息
     * @param id 商铺id
     * @return 商铺详情数据
     */
    @GetMapping("/{id}")
    public Result queryShopById(@PathVariable("id") Long id) {
        return Result.ok(shopService.queryShopById(id));
    }
}

4. Service层:负责业务的处理逻辑

java 复制代码
@Service
@Slf4j
public class ShopServiceImpl extends ServiceImpl<ShopMapper, Shop> implements IShopService {
    @Resource
    private StringRedisTemplate redis;
    
    public Result queryShopById(Long id) {
        //1.从Redis查询数据缓存
        String shopCache = redis.opsForValue().get("cache:shop:" + id);
        //2.判断是否存在 当shopCache为""时返回false
        if (StrUtil.isNotBlank(shopCache)) {
            //3.存在,直接返回
            Shop shop = JSONUtil.toBean(shopCache, Shop.class);
            return Result.ok(shop);
        }
        //判断命中的是否是空值
        if (shopCache!=null) {
            return Result.fail(" 店铺信息不存在 ");
        }
        //4.不存在,根据id查询数据库
        Shop shop = getById(id);
        if (ObjectUtil.isEmpty(shop)) {
            // 解决缓存穿透
            redis.opsForValue().set("cache:shop:" + id,"",2,TimeUnit.MINUTES);
            //5.不存在,返回错误
            return Result.fail("当前商户不存在");
        }
        //6.存在,写入redis
        redis.opsForValue().set("cache:shop:"+id,JSONUtil.toJsonStr(shop));
        redis.expire("cache:shop:"+id,30,TimeUnit.MINUTES);
        //7.返回
        return Result.ok(shop);
    }
}
相关推荐
明月看潮生15 分钟前
青少年编程与数学 01-011 系统软件简介 18 Navicat数据库管理工具
数据库·青少年编程·系统软件·编程与数学
眸笑丶39 分钟前
.NET Core 数据库连接字符串加密与解密
数据库·oracle·.netcore
layman05283 小时前
openEuler 虚拟机中 Shell 脚本实现自动化备份与清理实践
数据库·oracle·自动化
奔跑的小十一9 小时前
JDBC接口开发指南
java·数据库
熊大如如9 小时前
PostgreSQL 创建只读账户
数据库·postgresql
在未来等你9 小时前
SQL进阶之旅 Day 29:NoSQL结合使用策略
redis·sql·mongodb·elasticsearch·postgresql·nosql·hybrid-database
东阳马生架构10 小时前
商品中心—1.B端建品和C端缓存的技术文档一
缓存·商品系统
尽兴-11 小时前
达梦数据库CASE WHEN条件
数据库·oracle·达梦
遗忘妳11 小时前
PostgreSQL初体验
数据库·postgresql
运维老司机13 小时前
Redis 安装实践:基于鲲鹏 ARM 架构 Ubuntu 环境
arm开发·redis·架构