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);
    }
}
相关推荐
李慕婉学姐1 分钟前
Springboot微信小程序在线考试系统w47h61gy(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
数据库·spring boot·微信小程序
啊吧怪不啊吧7 分钟前
SQL之表的查改(下)
大数据·数据库·sql
工具人55551 小时前
adb disable-verity
数据库·数据仓库·adb
白露与泡影2 小时前
Redis:我是如何与客户端进行通信的
数据库·redis·缓存
一只小bit6 小时前
MySQL 索引:从聚簇到普通索引,如何加快查询效率?
数据库·mysql·oracle
洛克大航海8 小时前
解锁 PySpark SQL 的强大功能:有关 App Store 数据的端到端教程
linux·数据库·sql·pyspark sql
XueminXu10 小时前
ClickHouse数据库的表引擎
数据库·clickhouse·log·表引擎·mergetree·special·integrations
冒泡的肥皂10 小时前
MVCC初学demo(二
数据库·后端·mysql
代码程序猿RIP10 小时前
【Redis 】Redis 详解以及安装教程
数据库·etcd
小生凡一10 小时前
redis 大key、热key优化技巧|空间存储优化|调优技巧(一)
数据库·redis·缓存