SAAS-HRM-day6(Redis緩存+無限級樹)

1. client和controller沖突解決(代碼生成模板處理)

1.1 問題發(fā)現(xiàn)

突然發(fā)現(xiàn)以前的client包下client那里的@RequestMapping注解的地址都有一個user的前綴。如下所示:

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

這時候我需要刪除user,使client的@RequestMapping里面的參數(shù)與controller里面的@RequestMapping里面的參數(shù)相同。

這時候啟動項目就會報錯。但是錯誤信息很不明顯,很難發(fā)現(xiàn)錯誤!這時候想要看到詳細的日志錯誤信息有兩種辦法:第一種,把自己的日志配置文件取消掉,這時候啟動項目報的錯誤就會比較詳細!第二種:修改日志配置文件的日志等級,把等級調(diào)低,錯誤信息記錄的就會更加詳細!

修改日志以后,再啟動項目就會看到詳細的錯誤,如下所示:

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

這個錯誤產(chǎn)生的原因:由于修改了模塊hrm_course_interface下的client包的@RequestMapping("/course" )與hrm_course_service模塊下controller的@RequestMapping("/course" )相同,啟動項目就會在client端產(chǎn)生一個本地代理對象,而產(chǎn)生本地代理對象的地址就是/course,這樣最終就會和引用進來的controller的地址/course產(chǎn)生沖突。

1.2 問題解決

在二級子模塊course下新建一個三級子模塊,取名client,這里面就是專用于存放client的。

這時候二級子模塊course下就有了三個子模塊,分別是client、interface、service。把原來interface模塊里面的client包整個就全部剪切掉放到client模塊里!這時候會報錯,要導包!也要依賴于interface!

        <dependency>
            <groupId>cn.wangningbo.hrm</groupId>
            <artifactId>hrm_basic_util</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <!--不能直接依賴starter,有自動配置,而消費者是不需要額。-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!--不能全部引入mybatis-plus,這是要做數(shù)據(jù)庫操作,這里是不需要的,只需引入核心包解決錯誤而已-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus</artifactId>
            <version>2.2.0</version>
        </dependency>
        <!--客戶端feign支持-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <!--依賴于interface 公共代碼-->
        <dependency>
            <groupId>cn.wangningbo.hrm</groupId>
            <artifactId>hrm_course_interface</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

簡單分析這樣做的目的:我自己不可能調(diào)用自己的服務,我如果需要調(diào)用自己直接調(diào)用自己的service調(diào)用自己的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 后臺實現(xiàn)--類型樹查詢

2.2.1 方案分析

  1. 遞歸-(不采納-->因為發(fā)送很多條sql效率低)
  2. 循環(huán)-(采納)

2.2.2 方案實現(xiàn)(兩種方案都有代碼)

簡單邏輯分析:前端發(fā)送一個請求到后臺,需要獲取到一個類型樹!

  1. controller
    //類型樹
    @RequestMapping(value = "/treeData",method = RequestMethod.GET)
    public List<CourseType> treeData(){
        //數(shù)據(jù)庫中0就是頂級
        return courseTypeService.queryTypeTree(0L);
    }
  1. IService
    List<CourseType> queryTypeTree(Long pid);
  1. CourseType.java里面新建屬性用來存放兒子
    @TableField(exist = false) //用來存放兒子
    private List<CourseType> children = new ArrayList<>();
  1. ServiceImpl
    @Override
    public List<CourseType> queryTypeTree(Long pid) {
        //遞歸
//        return getCourseTypesRecursion(pid);
        // 循環(huán)
        return getCourseTypesLoop(pid);
    }

    /**
     * 循環(huán)
     * @param pid
     * @return
     */
    private List<CourseType> getCourseTypesLoop(Long pid) {
        List<CourseType> result = new ArrayList<>();
        //1 查詢所有類型
        List<CourseType> allTypes = courseTypeMapper.selectList(null);
        //建立id和CourseType的關聯(lián)關系
        Map<Long, CourseType> 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<CourseType> getCourseTypesRecursion(Long pid) {
        // 方案1:遞歸-自己調(diào)用自己,要有出口
        List<CourseType> children = courseTypeMapper.selectList(new EntityWrapper<CourseType>().eq("pid", pid));
        // 出口
        if (children == null || children.size() < 1)
            return null;
        for (CourseType child : children) {
            // 自己調(diào)用自己
            List<CourseType> courseTypes = queryTypeTree(child.getId());
            child.setChildren(courseTypes);
        }
        return children;
    }

3. 課程類型樹優(yōu)化方案

3.1 為什么要優(yōu)化?

每次使用都要從數(shù)據(jù)庫查詢一次,這樣就會存在一些問題

使用的地方和問題:

  1. 后臺管理管理員的頁面

    課程類型樹,在后面添加課程時會反復使用。就算每個人使用時只查詢一次,如果人比較多.也要對數(shù)據(jù)庫進行頻繁操作

  2. 前臺用戶使用的頁面

    緩存還不夠優(yōu)化,如果一億并發(fā),就會訪問redis一億次.對緩存服務器也是一種壓力.

3.2 優(yōu)化方案

  1. 后臺管理員

    緩存:用內(nèi)存查詢替換數(shù)據(jù)庫磁盤查詢.(應用場景:經(jīng)常查詢,很少修改的數(shù)據(jù))

  2. 前端用戶

    頁面靜態(tài)化:以不發(fā)請求靜態(tài)頁面代替要發(fā)請求靜態(tài)頁面或者動態(tài)頁面.沒有對后臺數(shù)據(jù)獲取.

    不能用緩存,還是會高并發(fā)訪問緩存中數(shù)據(jù).

4. 課程類型后臺緩存優(yōu)化

4.1 常見緩存實現(xiàn)方案

  1. jpa,mybatis二級緩存,默認情況下,不支持集群環(huán)境使用.
  2. 中央緩存:redis/memcached

4.2 交互圖

[圖片上傳失敗...(image-65fe02-1569803130923)]

4.3 數(shù)據(jù)存儲

  1. 數(shù)據(jù)存放:List<CourseType>
    • 把對象轉換為json字符串,以json字符串方式進行存儲.
    • jedis.set(“courseTypes”,jsonStr)
  2. 數(shù)據(jù)獲?。簀son字符串
    • jedis.get(“courseTypes”)
    • 把json字符串轉換為java對象進行返回

java對象和json字符串之間相互轉換?常見json框架-json-lib,jackson,gson,fastjson(阿里巴巴)。相互對比以后發(fā)現(xiàn)阿里巴巴的fastjson最好!

4.4 實現(xiàn)

4.4.1 redis項目搭建步驟分析

  1. 創(chuàng)建項目
  2. 導包
  3. 配置
  4. 入口類
  5. 日志
  6. 網(wǎng)關
  7. swagger
  8. 測試swagger頁面

4.4.2 redis項目搭建步驟實現(xiàn)

  1. 創(chuàng)建項目
    項目結構:
  • hrm_parent
    • hrm_basic_parent
      • hrm_basic_redis_client
      • hrm_basic_redis_common
      • hrm_basic_redis_service
  1. 導包

common

<!--不能直接依賴starter,有自動配置,而消費者是不需要額。-->

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>

client

        <dependency>
            <groupId>cn.wangningbo.hrm</groupId>
            <artifactId>hrm_basic_util</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>cn.wangningbo.hrm</groupId>
            <artifactId>hrm_basic_redis_common</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <!--不能直接依賴starter,有自動配置,而消費者是不需要額。-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!--客戶端feign支持-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

        <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson  調(diào)用者需要轉換-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.58</version>
        </dependency>

service

<dependency>
            <groupId>cn.wangningbo.hrm</groupId>
            <artifactId>hrm_basic_redis_common</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!-- Eureka 客戶端依賴 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

        <!--引入swagger支持-->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.9.2</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger-ui -->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.9.2</version>
        </dependency>

        <!--配置中心支持-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-config</artifactId>
        </dependency>

        <!--redis客戶端-jedis-->
        <!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>2.9.0</version>
        </dependency>
  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

<?xml version="1.0" encoding="UTF-8"?>
<!--
scan:當此屬性設置為true時,配置文件如果發(fā)生改變,將會被重新加載,默認值為true。
scanPeriod:設置監(jiān)測配置文件是否有修改的時間間隔,如果沒有給出時間單位,默認單位是毫秒當scan為true時,此屬性生效。默認的時間間隔為1分鐘。
debug:當此屬性設置為true時,將打印出logback內(nèi)部日志信息,實時查看logback運行狀態(tài)。默認值為false。
-->
<configuration scan="false" scanPeriod="60 seconds" debug="false">
    <!-- 定義日志的根目錄 -->
    <property name="LOG_HOME" value="/hrm/" />
    <!-- 定義日志文件名稱 -->
    <property name="appName" value="hrm-redis"></property>
    <!-- ch.qos.logback.core.ConsoleAppender 表示控制臺輸出 -->
    <appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
        <!--
        日志輸出格式:
            %d表示日期時間,
            %thread表示線程名,
            %-5level:級別從左顯示5個字符寬度
            %logger{50} 表示logger名字最長50個字符,否則按照句點分割。 
            %msg:日志消息,
            %n是換行符
        -->
        <layout class="ch.qos.logback.classic.PatternLayout">
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
        </layout>
    </appender>

    <!-- 滾動記錄文件,先將日志記錄到指定文件,當符合某個條件時,將日志記錄到其他文件 -->  
    <appender name="appLogAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 指定日志文件的名稱
           /hrm/hrm-course/hrm-course.log
        -->
        <file>${LOG_HOME}/${appName}/${appName}.log</file>
        <!--
        當發(fā)生滾動時,決定 RollingFileAppender 的行為,涉及文件移動和重命名
        TimeBasedRollingPolicy: 最常用的滾動策略,它根據(jù)時間來制定滾動策略,既負責滾動也負責出發(fā)滾動。
        -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!--
            滾動時產(chǎn)生的文件的存放位置及文件名稱 %d{yyyy-MM-dd}:按天進行日志滾動 
            %i:當文件大小超過maxFileSize時,按照i進行文件滾動
            -->
            <fileNamePattern>${LOG_HOME}/${appName}/${appName}-%d{yyyy-MM-dd}-%i.log</fileNamePattern>
            <!-- 
            可選節(jié)點,控制保留的歸檔文件的最大數(shù)量,超出數(shù)量就刪除舊文件。假設設置每天滾動,
            且maxHistory是365,則只保存最近365天的文件,刪除之前的舊文件。注意,刪除舊文件是,
            那些為了歸檔而創(chuàng)建的目錄也會被刪除。
            -->
            <MaxHistory>365</MaxHistory>
            <!-- 
            當日志文件超過maxFileSize指定的大小是,根據(jù)上面提到的%i進行日志文件滾動 注意此處配置SizeBasedTriggeringPolicy是無法實現(xiàn)按文件大小進行滾動的,必須配置timeBasedFileNamingAndTriggeringPolicy
            -->
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
        </rollingPolicy>
        <!-- 日志輸出格式: -->     
        <layout class="ch.qos.logback.classic.PatternLayout">
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [ %thread ] - [ %-5level ] [ %logger{50} : %line ] - %msg%n</pattern>
        </layout>
    </appender>

    <!-- 
        logger主要用于存放日志對象,也可以定義日志類型、級別
        name:表示匹配的logger類型前綴,也就是包的前半部分
        level:要記錄的日志級別,包括 TRACE < DEBUG < INFO < WARN < ERROR
        additivity:作用在于children-logger是否使用 rootLogger配置的appender進行輸出,
        false:表示只用當前l(fā)ogger的appender-ref,true:
        表示當前l(fā)ogger的appender-ref和rootLogger的appender-ref都有效
    -->
    <!-- hibernate logger -->
    <logger name="cn.wangningbo" level="debug" />
    <!-- Spring framework logger -->
    <logger name="org.springframework" level="debug" additivity="false"></logger>



    <!-- 
    root與logger是父子關系,沒有特別定義則默認為root,任何一個類只會和一個logger對應,
    要么是定義的logger,要么是root,判斷的關鍵在于找到這個logger,然后判斷這個logger的appender和level。 
    -->
    <root level="info">
        <appender-ref ref="stdout" />
        <appender-ref ref="appLogAppender" />
    </root>
</configuration> 
  1. 網(wǎng)關

在網(wǎng)關的配置文件里面配置一下

zuul:
  routes: 
    redis.serviceId: hrm-redis # 服務名
    redis.path: /redis/** # 把redis打頭的所有請求都轉發(fā)給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", "", "wang_ning_bo163@163.com"))
                .version("1.0")
                .build();
    }

}
  1. 測試swagger頁面

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

4.4.3 緩存服務實現(xiàn)

4.4.3.1 redis那里接口實現(xiàn)

4.4.3.1.1 步驟分析
  1. client模塊
  2. service模塊
4.4.3.1.2 步驟實現(xiàn)
  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<RedisClient> {
    @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的包已經(jīng)導入過了!

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 創(chuàng)建連接池配置對象
        JedisPoolConfig config = new JedisPoolConfig();
        //2 進行配置-四個配置
        config.setMaxIdle(1);//最小連接數(shù)
        config.setMaxTotal(11);//最大連接數(shù)
        config.setMaxWaitMillis(10 * 1000L);//最長等待時間
        config.setTestOnBorrow(true);//測試連接時是否暢通
        //3 通過配置對象創(chuàng)建連接池對象
        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測試:自身和網(wǎng)關
    postman測試接口

4.4.3.2 其他項目接口調(diào)用

4.4.3.2.1 步驟分析
  1. 導包
  2. 入口掃描
  3. service
  4. cache
4.4.3.2.2 步驟實現(xiàn)
  1. 導包
        <!--內(nèi)部調(diào)用redis模塊接口-->
        <dependency>
            <groupId>cn.wangningbo.hrm</groupId>
            <artifactId>hrm_basic_redis_client</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
  1. 入口掃描

    入口類那里配置一下,內(nèi)部調(diào)用要打注解@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

簡單業(yè)務邏輯分析:由于課程類型樹是很常用的但又很少修改的數(shù)據(jù),所以要把課程類型樹放入redis緩存之中!當查詢課程類型樹的時候先去redis緩存中獲取,如果緩存中有,就直接拿走使用,如果緩存中沒有,就去數(shù)據(jù)庫中查詢,查詢完數(shù)據(jù)以后先放入緩存中,再返回給查詢者!對課程類型表進行增刪改的時候,也要同步到redis緩存中!

改造獲取課程類型樹的方法

    @Autowired
    private CourseTypeCache courseTypeCache;

    @Override
    public List<CourseType> queryTypeTree(Long pid) {
        // 從redis緩存中獲取課程類型樹
        List<CourseType> courseTypes = courseTypeCache.getCourseTypes();
        // 判斷redis中有沒有獲取到課程類型樹
        if (courseTypes==null||courseTypes.size()<1){
            // 進入到了if里面就說明緩存中沒有,要從db庫中獲取課程類型樹
            return getCourseTypesLoop(pid);//調(diào)用循環(huán)的方式獲取課程類型樹
        }
        // 返回redis緩存中的課程類型樹
        return courseTypes;
    }

改造對課程類型表的添加、修改、刪除方法!操作這些的時候也要同步操作redis緩存

    @Override
    public boolean insert(CourseType entity) {
        courseTypeMapper.insert(entity);
        // 重新查詢一下課程類型樹,更新到redis緩存
        List<CourseType> courseTypes = queryTypeTree(0L);
        courseTypeCache.setCourseTypes(courseTypes);
        return true;
    }

    @Override
    public boolean deleteById(Serializable id) {
        courseTypeMapper.deleteById(id);
        // 重新查詢一下課程類型樹,更新到redis緩存
        List<CourseType> courseTypes = queryTypeTree(0L);
        courseTypeCache.setCourseTypes(courseTypes);
        return true;
    }

    @Override
    public boolean updateById(CourseType entity) {
        courseTypeMapper.updateById(entity);
        // 重新查詢一下課程類型樹,更新到redis緩存
        List<CourseType> 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獲取數(shù)據(jù)
     *
     * @return
     */
    public List<CourseType> getCourseTypes() {
        String redisData = redisClient.get(TYPETREEDATA_IN_REDIS);
        return JSONArray.parseArray(redisData, CourseType.class);
    }

    /**
     * 設置數(shù)據(jù)到redis
     *
     * @param courseTypesDb
     */
    public void setCourseTypes(List<CourseType> courseTypesDb) {
        String jsonStr = JSONArray.toJSONString(courseTypesDb);
        redisClient.set(TYPETREEDATA_IN_REDIS, jsonStr);
    }
}

5. 高級&面試題

  1. 使用緩存好處?
    • 減輕數(shù)據(jù)庫壓力
    • 提高訪問速度,增強用戶體驗
  2. 我們緩存數(shù)據(jù)很多的時候怎么辦? 使用redis集群
    • (集群方式1)主從復制-解決單個主故障
    • (集群方式2)哨兵模式-每個節(jié)點數(shù)據(jù)都是一樣
    • (集群方式3)redis-cluster: 單點故障,高并發(fā),大量數(shù)據(jù)
  3. 緩存穿透怎么解決?
    • 產(chǎn)生原因:高并發(fā)訪問數(shù)據(jù)庫中不存在數(shù)據(jù),放入緩存的數(shù)據(jù)也沒有,擊穿緩存每次都要查詢數(shù)據(jù)庫.
    • 解決辦法有很多種,我列舉一下兩種
      • (1)最常見的則是采用布隆過濾器,將所有可能存在的數(shù)據(jù)哈希到一個足夠大的bitmap中,一個一定不存在的數(shù)據(jù)會被這個bitmap攔截掉,從而避免了對底層數(shù)據(jù)庫的查詢壓力。
      • (2)另外也有一個更為簡單粗暴的方法,如果一個查詢返回的數(shù)據(jù)為空(不管是數(shù)據(jù)不存在,還是系統(tǒng)故障),仍然把這個空結果進行緩存,但它的過期時間會很短,最長不超過五分鐘。

我上面的那個課程類型樹就存在緩存穿透的問題!下面進行解決!

改造獲取課程類型樹的方法

    @Override
    public List<CourseType> queryTypeTree(Long pid) {
        // 從redis緩存中獲取課程類型樹
        List<CourseType> courseTypes = courseTypeCache.getCourseTypes();
        // 判斷redis中有沒有獲取到課程類型樹
        if (courseTypes == null || courseTypes.size() < 1) {
            // 進入到了if里面就說明緩存中沒有,要從db庫中獲取課程類型樹 // 調(diào)用循環(huán)的方式獲取課程類型樹
            List<CourseType> courseTypesDb = getCourseTypesLoop(pid);
            // 判斷數(shù)據(jù)庫中是否查到了數(shù)據(jù)
            if (courseTypesDb == null || courseTypesDb.size() < 1)
                // 如果數(shù)據(jù)庫中沒有查到,就返回一個空回去 // 并設置一個很短的過期時間,我這里過期時間為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. 緩存擊穿怎么解決?
    • 產(chǎn)生原因:一些key同時過期,又來高并發(fā)訪問. 直接高并發(fā)訪問數(shù)據(jù)庫
    • 解決辦法:讓熱點數(shù)據(jù)永遠不過期
  2. 緩存雪崩怎么解決?
    • 產(chǎn)生原因:一堆key同時過期
    • 解決辦法有很多種,我列舉以下兩種:
      • (1)設置過期時間不一致
      • (2)熱點數(shù)據(jù)永遠不過期
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴謹 對...
    cosWriter閱讀 11,666評論 1 32
  • 1 Redis介紹1.1 什么是NoSql為了解決高并發(fā)、高可擴展、高可用、大數(shù)據(jù)存儲問題而產(chǎn)生的數(shù)據(jù)庫解決方...
    克魯?shù)吕?/span>閱讀 5,726評論 0 36
  • 1.1 資料 ,最好的入門小冊子,可以先于一切文檔之前看,免費。 作者Antirez的博客,Antirez維護的R...
    JefferyLcm閱讀 17,320評論 1 51
  • 包含的重點內(nèi)容:JAVA基礎JVM 知識開源框架知識操作系統(tǒng)多線程TCP 與 HTTP架構設計與分布式算法數(shù)據(jù)庫知...
    消失er閱讀 4,554評論 1 10
  • 劉云裸著體 專心玩手機 群中播眼球 樣子有點痞 小心得感冒 免得去就醫(yī) 快來拜師傅 送你棉大衣
    保衛(wèi)中華閱讀 164評論 0 0

友情鏈接更多精彩內(nèi)容