SAAS-HRM-day6(Redis缓存+无限级树)

  • 1. client和controller冲突解决(代码生成模板处理)
    • 1.1 问题发现
    • 1.2 问题解决
  • 2. 无限级树(课程类型树)
    • 2.1 场景分析
    • 2.2 后台实现--类型树查询
      • 2.2.1 方案分析
      • 2.2.2 方案实现(两种方案都有代码)
  • 3. 课程类型树优化方案
    • 3.1 为什么要优化?
    • 3.2 优化方案
  • 4. 课程类型后台缓存优化
    • 4.1 常见缓存实现方案
    • 4.2 交互图
    • 4.3 数据存储
    • 4.4 实现
      • 4.4.1 redis项目搭建步骤分析
      • 4.4.2 redis项目搭建步骤实现
      • 4.4.3 缓存服务实现
        • 4.4.3.1 redis那里接口实现
          • 4.4.3.1.1 步骤分析
          • 4.4.3.1.2 步骤实现
        • 4.4.3.2 其他项目接口调用
          • 4.4.3.2.1 步骤分析
          • 4.4.3.2.2 步骤实现
  • 5. 高级&面试题

1. client和controller冲突解决(代码生成模板处理)

1.1 问题发现

突然发现以前的client包下client那里的@RequestMapping注解的地址都有一个user的前缀。如下所示:

@FeignClient(value = "ZUUL-GATEWAY", configuration = FeignClientsConfiguration.class,
        fallbackFactory = CourseClientHystrixFallbackFactory.class)
@RequestMapping("/user/course" )
public interface CourseClient {

这时候我需要删除user,使client的@RequestMapping里面的参数与controller里面的@RequestMapping里面的参数相同。

这时候启动项目就会报错。但是错误信息很不明显,很难发现错误!这时候想要看到详细的日志错误信息有两种办法:第一种,把自己的日志配置文件取消掉,这时候启动项目报的错误就会比较详细!第二种:修改日志配置文件的日志等级,把等级调低,错误信息记录的就会更加详细!

修改日志以后,再启动项目就会看到详细的错误,如下所示:

Caused by: java.lang.IllegalStateException: Ambiguous mapping. Cannot map 'cn.wangningbo.hrm.client.CourseClient' method 
public abstract cn.wangningbo.hrm.util.AjaxResult cn.wangningbo.hrm.client.CourseClient.save(cn.wangningbo.hrm.domain.Course)
to {[/course/save],methods=[POST]}: There is already 'courseController' bean method
public cn.wangningbo.hrm.util.AjaxResult cn.wangningbo.hrm.web.controller.CourseController.save(cn.wangningbo.hrm.domain.Course) mapped.
    at org.springframework.web.servlet.handler.AbstractHandlerMethodMapping$MappingRegistry.assertUniqueMethodMapping(AbstractHandlerMethodMapping.java:581) ~[spring-webmvc-5.0.9.RELEASE.jar:5.0.9.RELEASE]
    at org.springframework.web.servlet.handler.AbstractHandlerMethodMapping$MappingRegistry.register(AbstractHandlerMethodMapping.java:545) ~[spring-webmvc-5.0.9.RELEASE.jar:5.0.9.RELEASE]
    at org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.registerHandlerMethod(AbstractHandlerMethodMapping.java:267) ~[spring-webmvc-5.0.9.RELEASE.jar:5.0.9.RELEASE]
    at org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.lambda$detectHandlerMethods$1(AbstractHandlerMethodMapping.java:252) ~[spring-webmvc-5.0.9.RELEASE.jar:5.0.9.RELEASE]
    at java.util.LinkedHashMap.forEach(LinkedHashMap.java:684) ~[na:1.8.0_111]
    at org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.detectHandlerMethods(AbstractHandlerMethodMapping.java:250) ~[spring-webmvc-5.0.9.RELEASE.jar:5.0.9.RELEASE]
    at org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.initHandlerMethods(AbstractHandlerMethodMapping.java:219) ~[spring-webmvc-5.0.9.RELEASE.jar:5.0.9.RELEASE]
    at org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.afterPropertiesSet(AbstractHandlerMethodMapping.java:189) ~[spring-webmvc-5.0.9.RELEASE.jar:5.0.9.RELEASE]
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping.afterPropertiesSet(RequestMappingHandlerMapping.java:136) ~[spring-webmvc-5.0.9.RELEASE.jar:5.0.9.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1758) ~[spring-beans-5.0.9.RELEASE.jar:5.0.9.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1695) ~[spring-beans-5.0.9.RELEASE.jar:5.0.9.RELEASE]
    ... 50 common frames omitted

这个错误产生的原因:由于修改了模块hrm_course_interface下的client包的@RequestMapping("/course" )与hrm_course_service模块下controller的@RequestMapping("/course" )相同,启动项目就会在client端产生一个本地代理对象,而产生本地代理对象的地址就是/course,这样最终就会和引用进来的controller的地址/course产生冲突。

1.2 问题解决

在二级子模块course下新建一个三级子模块,取名client,这里面就是专用于存放client的。

这时候二级子模块course下就有了三个子模块,分别是client、interface、service。把原来interface模块里面的client包整个就全部剪切掉放到client模块里!这时候会报错,要导包!也要依赖于interface!

        
            cn.wangningbo.hrm
            hrm_basic_util
            1.0-SNAPSHOT
        
        
        
            org.springframework.boot
            spring-boot-starter-web
        
        
            org.springframework.boot
            spring-boot-starter-test
            test
        
        
        
            com.baomidou
            mybatis-plus
            2.2.0
        
        
        
            org.springframework.cloud
            spring-cloud-starter-openfeign
        
        
        
            cn.wangningbo.hrm
            hrm_course_interface
            1.0-SNAPSHOT
        

简单分析这样做的目的:我自己不可能调用自己的服务,我如果需要调用自己直接调用自己的service调用自己的Mapper即可,所以service不需要依赖于client模块,service只需要依赖于interface即可!client是对外暴露服务给其他模块用的!

最终模块存放情况:

  1. client模块:client
  2. interface模块:domain、query
  3. service模块:config、mapper、service、util、web\controller

最终依赖情况:client依赖于interface,service依赖于interface

2. 无限级树(课程类型树)

2.1 场景分析

  1. 后台管理页面类型树
  2. 前台用户高级搜索时候类型树的选择

2.2 后台实现--类型树查询

2.2.1 方案分析

  1. 递归-(不采纳-->因为发送很多条sql效率低)
  2. 循环-(采纳)

2.2.2 方案实现(两种方案都有代码)

简单逻辑分析:前端发送一个请求到后台,需要获取到一个类型树!

  1. controller
    //类型树
    @RequestMapping(value = "/treeData",method = RequestMethod.GET)
    public List treeData(){
        //数据库中0就是顶级
        return courseTypeService.queryTypeTree(0L);
    }
  1. IService
    List queryTypeTree(Long pid);
  1. CourseType.java里面新建属性用来存放儿子
    @TableField(exist = false) //用来存放儿子
    private List children = new ArrayList<>();
  1. ServiceImpl
    @Override
    public List queryTypeTree(Long pid) {
        //递归
//        return getCourseTypesRecursion(pid);
        // 循环
        return getCourseTypesLoop(pid);
    }

    /**
     * 循环
     * @param pid
     * @return
     */
    private List getCourseTypesLoop(Long pid) {
        List result = new ArrayList<>();
        //1 查询所有类型
        List allTypes = courseTypeMapper.selectList(null);
        //建立id和CourseType的关联关系
        Map allTypesDto = new HashMap<>();
        for (CourseType allType : allTypes) {
            allTypesDto.put(allType.getId(), allType);
        }
        //2 遍历判断是否是第一级  pid为传入id,
        for (CourseType type : allTypes) {
            Long pidTmp = type.getPid();
            //2.1 是。直接加入返回列表
            if (pidTmp.longValue() == pid.longValue()) {
                result.add(type);
            } else {
                //2.2 不是。要把自己作为父亲儿子
                //方案:通过pid获取父亲。通过map获取
                CourseType parent = allTypesDto.get(pidTmp);
                //获取父亲儿子集合,把自己加进去
                parent.getChildren().add(type);
            }
        }
        return result;
    }

    /**
     * 递归
     * @param pid
     * @return
     */
    private List getCourseTypesRecursion(Long pid) {
        // 方案1:递归-自己调用自己,要有出口
        List children = courseTypeMapper.selectList(new EntityWrapper().eq("pid", pid));
        // 出口
        if (children == null || children.size() < 1)
            return null;
        for (CourseType child : children) {
            // 自己调用自己
            List courseTypes = queryTypeTree(child.getId());
            child.setChildren(courseTypes);
        }
        return children;
    }

3. 课程类型树优化方案

3.1 为什么要优化?

每次使用都要从数据库查询一次,这样就会存在一些问题

使用的地方和问题:

  1. 后台管理管理员的页面

    课程类型树,在后面添加课程时会反复使用。就算每个人使用时只查询一次,如果人比较多.也要对数据库进行频繁操作

  2. 前台用户使用的页面

    缓存还不够优化,如果一亿并发,就会访问redis一亿次.对缓存服务器也是一种压力.

3.2 优化方案

  1. 后台管理员

    缓存:用内存查询替换数据库磁盘查询.(应用场景:经常查询,很少修改的数据)

  2. 前端用户

    页面静态化:以不发请求静态页面代替要发请求静态页面或者动态页面.没有对后台数据获取.

    不能用缓存,还是会高并发访问缓存中数据.

4. 课程类型后台缓存优化

4.1 常见缓存实现方案

  1. jpa,mybatis二级缓存,默认情况下,不支持集群环境使用.
  2. 中央缓存:redis/memcached

4.2 交互图

[图片上传失败...(image-65fe02-1569803130923)]

4.3 数据存储

  1. 数据存放:List
    • 把对象转换为json字符串,以json字符串方式进行存储.
    • jedis.set(“courseTypes”,jsonStr)
  2. 数据获取:json字符串
    • jedis.get(“courseTypes”)
    • 把json字符串转换为java对象进行返回

java对象和json字符串之间相互转换?常见json框架-json-lib,jackson,gson,fastjson(阿里巴巴)。相互对比以后发现阿里巴巴的fastjson最好!

4.4 实现

4.4.1 redis项目搭建步骤分析

  1. 创建项目
  2. 导包
  3. 配置
  4. 入口类
  5. 日志
  6. 网关
  7. swagger
  8. 测试swagger页面

4.4.2 redis项目搭建步骤实现

  1. 创建项目
    项目结构:
  • hrm_parent
    • hrm_basic_parent
      • hrm_basic_redis_client
      • hrm_basic_redis_common
      • hrm_basic_redis_service
  1. 导包

common




    org.springframework.boot
    spring-boot-starter-web



    org.springframework.boot
    spring-boot-starter-test
    test

client

        
            cn.wangningbo.hrm
            hrm_basic_util
            1.0-SNAPSHOT
        
        
            cn.wangningbo.hrm
            hrm_basic_redis_common
            1.0-SNAPSHOT
        
        
        
            org.springframework.boot
            spring-boot-starter-web
        

        
            org.springframework.boot
            spring-boot-starter-test
            test
        

        
        
            org.springframework.cloud
            spring-cloud-starter-openfeign
        

        
        
            com.alibaba
            fastjson
            1.2.58
        

service


            cn.wangningbo.hrm
            hrm_basic_redis_common
            1.0-SNAPSHOT
        

        
            org.springframework.boot
            spring-boot-starter-web
        
        
            org.springframework.boot
            spring-boot-starter-test
            test
        
        
        
            org.springframework.cloud
            spring-cloud-starter-netflix-eureka-client
        

        
        
            io.springfox
            springfox-swagger2
            2.9.2
        
        
        
            io.springfox
            springfox-swagger-ui
            2.9.2
        

        
        
            org.springframework.cloud
            spring-cloud-starter-config
        

        
        
        
            redis.clients
            jedis
            2.9.0
        
  1. 配置

service那里application.yml

server:
  port: 9005
spring:
  application:
    name: hrm-redis
eureka:
  client:
    service-url:
      defaultZone: http://localhost:7001/eureka
  instance:
    prefer-ip-address: true
  1. 入口类

service那里的入口类

package cn.wangningbo.hrm;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

@SpringBootApplication
@EnableEurekaClient
public class Redis9005Application {
    public static void main(String[] args) {
        SpringApplication.run(Redis9005Application.class, args);
    }
}
  1. 日志

resources下存放一个logback日志的配置文件就行,名字固定logback-spring.xml




    
    
    
    
    
    
        
        
            %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n
        
    

      
    
        
        ${LOG_HOME}/${appName}/${appName}.log
        
        
            
            ${LOG_HOME}/${appName}/${appName}-%d{yyyy-MM-dd}-%i.log
            
            365
            
            
                100MB
            
        
             
        
            %d{yyyy-MM-dd HH:mm:ss.SSS} [ %thread ] - [ %-5level ] [ %logger{50} : %line ] - %msg%n
        
    

    
    
    
    
    



    
    
        
        
    
 
  1. 网关

在网关的配置文件里面配置一下

zuul:
  routes: 
    redis.serviceId: hrm-redis # 服务名
    redis.path: /redis/** # 把redis打头的所有请求都转发给hrm-redis
  1. swagger.服务端配置
package cn.wangningbo.hrm.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

@Configuration
@EnableSwagger2
public class Swagger2 {

    @Bean
    public Docket createRestApi() {
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .select()
                //对外暴露服务的包,以controller的方式暴露,所以就是controller的包.
                .apis(RequestHandlerSelectors.basePackage("cn.wangningbo.hrm.controller"))
                .paths(PathSelectors.any())
                .build();
    }


    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("中央缓存api")
                .description("中央缓存接口文档说明")
                .contact(new Contact("wangningbo", "", "[email protected]"))
                .version("1.0")
                .build();
    }

}
  1. 测试swagger页面

    http://localhost:9005/swagger-ui.html
    http://localhost:9527/swagger-ui.html

4.4.3 缓存服务实现

4.4.3.1 redis那里接口实现

4.4.3.1.1 步骤分析
  1. client模块
  2. service模块
4.4.3.1.2 步骤实现
  1. client模块
package cn.wangningbo.hrm.client;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.cloud.openfeign.FeignClientsConfiguration;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

@FeignClient(value = "HRM-REDIS", configuration = FeignClientsConfiguration.class,
        fallbackFactory = RedisClientFallbackFactory.class)//服务提供者的名字
@RequestMapping("/cache")
public interface RedisClient {
    @PostMapping
    void set(@RequestParam("key") String key, @RequestParam("value") String value);

    @GetMapping
    String get(@RequestParam("key") String key);
}
package cn.wangningbo.hrm.client;

import feign.hystrix.FallbackFactory;
import org.springframework.stereotype.Component;

@Component
public class RedisClientFallbackFactory implements FallbackFactory {
    @Override
    public RedisClient create(Throwable throwable) {
        return new RedisClient() {
            @Override
            public void set(String key, String value) {

            }

            @Override
            public String get(String key) {
                return null;
            }
        };
    }
}
  1. service模块

这里是使用jedis操作redis!jedis的包已经导入过了!

redis配置文件:redis.properties

redis.host=127.0.0.1
redis.port=6379
redis.password=root
redis.timeout=3000

操作redis的工具类

package cn.wangningbo.hrm.util;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

import java.io.IOException;
import java.util.Properties;

/**
 * 获取连接池对象
 */
public enum RedisUtils {
    INSTANCE;
    static JedisPool jedisPool = null;

    static {
        //1 创建连接池配置对象
        JedisPoolConfig config = new JedisPoolConfig();
        //2 进行配置-四个配置
        config.setMaxIdle(1);//最小连接数
        config.setMaxTotal(11);//最大连接数
        config.setMaxWaitMillis(10 * 1000L);//最长等待时间
        config.setTestOnBorrow(true);//测试连接时是否畅通
        //3 通过配置对象创建连接池对象
        Properties properties = null;
        try {
            properties = new Properties();
            properties.load(RedisUtils.class.getClassLoader().getResourceAsStream("redis.properties"));
        } catch (IOException e) {
            e.printStackTrace();
        }
        String host = properties.getProperty("redis.host");
        String port = properties.getProperty("redis.port");
        String password = properties.getProperty("redis.password");
        String timeout = properties.getProperty("redis.timeout");
        jedisPool = new JedisPool(config, host, Integer.valueOf(port), Integer.valueOf(timeout), password);
    }

    //获取连接
    public Jedis getSource() {
        return jedisPool.getResource();
    }

    //关闭资源
    public void closeSource(Jedis jedis) {
        if (jedis != null) {
            jedis.close();
        }

    }

    /**
     * 设置字符值
     *
     * @param key
     * @param value
     */
    public void set(String key, String value) {
        Jedis jedis = getSource();
        jedis.set(key, value);
        closeSource(jedis);
    }

    /**
     * 设置
     *
     * @param key
     * @param value
     */
    public void set(byte[] key, byte[] value) {
        Jedis jedis = getSource();
        jedis.set(key, value);
        closeSource(jedis);
    }

    /**
     * @param key
     * @return
     */
    public byte[] get(byte[] key) {
        Jedis jedis = getSource();
        try {
            return jedis.get(key);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            closeSource(jedis);
        }
        return null;

    }

    /**
     * 设置字符值
     *
     * @param key
     */
    public String get(String key) {
        Jedis jedis = getSource();
        try {
            return jedis.get(key);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            closeSource(jedis);
        }

        return null;

    }
}

controller

package cn.wangningbo.hrm.controller;

import cn.wangningbo.hrm.util.RedisUtils;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/cache")
public class RedisController {
    @PostMapping
    public void set(@RequestParam("key") String key, @RequestParam("value") String value) {
        RedisUtils.INSTANCE.set(key, value);
    }

    @GetMapping
    public String get(@RequestParam("key") String key) {
        return RedisUtils.INSTANCE.get(key);
    }
}
  1. 测试

    启动redis服务端命令:D:\soft\redis>redis-server.exe redis.windows.conf

    swagger测试:自身和网关
    postman测试接口

4.4.3.2 其他项目接口调用

4.4.3.2.1 步骤分析
  1. 导包
  2. 入口扫描
  3. service
  4. cache
4.4.3.2.2 步骤实现
  1. 导包
        
        
            cn.wangningbo.hrm
            hrm_basic_redis_client
            1.0-SNAPSHOT
        
  1. 入口扫描

    入口类那里配置一下,内部调用要打注解@EnableFeignClients和@MapperScan("cn.wangningbo.hrm.mapper")

package cn.wangningbo.hrm;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.openfeign.EnableFeignClients;

@SpringBootApplication
@EnableEurekaClient
@MapperScan("cn.wangningbo.hrm.mapper")
@EnableFeignClients
public class Course9002Application {
    public static void main(String[] args) {
        SpringApplication.run(Course9002Application.class, args);
    }
}
  1. service

简单业务逻辑分析:由于课程类型树是很常用的但又很少修改的数据,所以要把课程类型树放入redis缓存之中!当查询课程类型树的时候先去redis缓存中获取,如果缓存中有,就直接拿走使用,如果缓存中没有,就去数据库中查询,查询完数据以后先放入缓存中,再返回给查询者!对课程类型表进行增删改的时候,也要同步到redis缓存中!

改造获取课程类型树的方法

    @Autowired
    private CourseTypeCache courseTypeCache;

    @Override
    public List queryTypeTree(Long pid) {
        // 从redis缓存中获取课程类型树
        List courseTypes = courseTypeCache.getCourseTypes();
        // 判断redis中有没有获取到课程类型树
        if (courseTypes==null||courseTypes.size()<1){
            // 进入到了if里面就说明缓存中没有,要从db库中获取课程类型树
            return getCourseTypesLoop(pid);//调用循环的方式获取课程类型树
        }
        // 返回redis缓存中的课程类型树
        return courseTypes;
    }

改造对课程类型表的添加、修改、删除方法!操作这些的时候也要同步操作redis缓存

    @Override
    public boolean insert(CourseType entity) {
        courseTypeMapper.insert(entity);
        // 重新查询一下课程类型树,更新到redis缓存
        List courseTypes = queryTypeTree(0L);
        courseTypeCache.setCourseTypes(courseTypes);
        return true;
    }

    @Override
    public boolean deleteById(Serializable id) {
        courseTypeMapper.deleteById(id);
        // 重新查询一下课程类型树,更新到redis缓存
        List courseTypes = queryTypeTree(0L);
        courseTypeCache.setCourseTypes(courseTypes);
        return true;
    }

    @Override
    public boolean updateById(CourseType entity) {
        courseTypeMapper.updateById(entity);
        // 重新查询一下课程类型树,更新到redis缓存
        List courseTypes = queryTypeTree(0L);
        courseTypeCache.setCourseTypes(courseTypes);
        return true;
    }
  1. cache
package cn.wangningbo.hrm.cache;

import cn.wangningbo.hrm.client.RedisClient;
import cn.wangningbo.hrm.domain.CourseType;
import com.alibaba.fastjson.JSONArray;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.List;

@Component
public class CourseTypeCache {
    @Autowired
    private RedisClient redisClient;

    private static final String TYPETREEDATA_IN_REDIS = "typetreedata_in_redis";

    /**
     * 从redis获取数据
     *
     * @return
     */
    public List getCourseTypes() {
        String redisData = redisClient.get(TYPETREEDATA_IN_REDIS);
        return JSONArray.parseArray(redisData, CourseType.class);
    }

    /**
     * 设置数据到redis
     *
     * @param courseTypesDb
     */
    public void setCourseTypes(List courseTypesDb) {
        String jsonStr = JSONArray.toJSONString(courseTypesDb);
        redisClient.set(TYPETREEDATA_IN_REDIS, jsonStr);
    }
}

5. 高级&面试题

  1. 使用缓存好处?
    • 减轻数据库压力
    • 提高访问速度,增强用户体验
  2. 我们缓存数据很多的时候怎么办? 使用redis集群
    • (集群方式1)主从复制-解决单个主故障
    • (集群方式2)哨兵模式-每个节点数据都是一样
    • (集群方式3)redis-cluster: 单点故障,高并发,大量数据
  3. 缓存穿透怎么解决?
    • 产生原因:高并发访问数据库中不存在数据,放入缓存的数据也没有,击穿缓存每次都要查询数据库.
    • 解决办法有很多种,我列举一下两种
      • (1)最常见的则是采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被这个bitmap拦截掉,从而避免了对底层数据库的查询压力。
      • (2)另外也有一个更为简单粗暴的方法,如果一个查询返回的数据为空(不管是数据不存在,还是系统故障),仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。

我上面的那个课程类型树就存在缓存穿透的问题!下面进行解决!

改造获取课程类型树的方法

    @Override
    public List queryTypeTree(Long pid) {
        // 从redis缓存中获取课程类型树
        List courseTypes = courseTypeCache.getCourseTypes();
        // 判断redis中有没有获取到课程类型树
        if (courseTypes == null || courseTypes.size() < 1) {
            // 进入到了if里面就说明缓存中没有,要从db库中获取课程类型树 // 调用循环的方式获取课程类型树
            List courseTypesDb = getCourseTypesLoop(pid);
            // 判断数据库中是否查到了数据
            if (courseTypesDb == null || courseTypesDb.size() < 1)
                // 如果数据库中没有查到,就返回一个空回去 // 并设置一个很短的过期时间,我这里过期时间为5分钟
                courseTypesDb = new ArrayList<>();
            // 把查询的结果放入缓存中
            courseTypeCache.setCourseTypes(courseTypesDb);
            return courseTypesDb;
        }
        // 返回redis缓存中的课程类型树
        return courseTypes;
    }

改造redis服务的controller层的set方法,把那些为"[]""的value设置为5分钟后过期,否则就设置永不过期

package cn.wangningbo.hrm.controller;

import cn.wangningbo.hrm.util.RedisUtils;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/cache")
public class RedisController {
    @PostMapping
    public void set(@RequestParam("key") String key, @RequestParam("value") String value) {
        if (value.equals("[]"))
            RedisUtils.INSTANCE.getSource().setex(key, 5 * 60, value);
        else
            RedisUtils.INSTANCE.set(key, value);
    }

    @GetMapping
    public String get(@RequestParam("key") String key) {
        return RedisUtils.INSTANCE.get(key);
    }
}
  1. 缓存击穿怎么解决?
    • 产生原因:一些key同时过期,又来高并发访问. 直接高并发访问数据库
    • 解决办法:让热点数据永远不过期
  2. 缓存雪崩怎么解决?
    • 产生原因:一堆key同时过期
    • 解决办法有很多种,我列举以下两种:
      • (1)设置过期时间不一致
      • (2)热点数据永远不过期

你可能感兴趣的:(SAAS-HRM-day6(Redis缓存+无限级树))