当前位置: 首页 > article >正文

SpringCloud学习笔记【尚硅谷2024版】

文章目录
  • 一、笔记内容技术选型
  • 二、Spring Cloud介绍
    • 1.为什么需要Spring Cloud?
    • 2.相关组件介绍
  • 三、单体项目构建
    • 1.SpringBoot单体服务
      • 1.1 项目构建
      • 1.2 MyBatis逆向工程
      • 1.3 编写业务逻辑
      • 1.4 整合Swager3
      • 1.5 统一返回结果Result
      • 1.6 优化时间格式
      • 1.7 异常处理
      • 1.8 编写订单模块【模块构建参考上述步骤】
      • 1.9 服务调用RestTemplate
      • 1.10 重复代码抽取
    • 2.问题引入
  • 四、Consul服务注册和发现
    • 1.基本介绍
    • 2.下载运行
    • 3.服务注册与发现
    • 4.服务配置
    • 5.动态刷新
    • 6.配置数据持久化
  • 六、LoadBlancer负载均衡
    • 1.基本介绍
    • 2.基本使用
    • 3.基本原理
    • 4.负载均衡算法
    • 5.负载均衡算法切换
  • 七、OpenFeign服务接口调用
    • 1.基本介绍
    • 2.能干什么
    • 3.基本使用
    • 4.最佳实践
    • 5.超时控制
      • 5.1 全局配置
      • 5.2 指定配置
    • 6.重试机制
    • 7.连接池
    • 8.请求/响应压缩
    • 9.日志打印
  • 八、CircuitBreaker断路器
    • 1.基本介绍
    • 2.CircuitBreaker和Resilience4的关系
    • 3.CircuitBreaker的实现原理
  • 九、Resilience4J
    • 1.基本介绍
    • 2.基本功能
      • 2.1 熔断【服务熔断+服务降级】
        • 2.1.1 断路器3大状态
        • 2.1.2 断路器状态转换
      • 2.2 限速
      • 2.3 隔离
  • 五、面试题
    • 1.常见的注册中心和他们的特点
    • 2.客户端负载均衡和服务器负载均衡有什么区别?

一、笔记内容技术选型

技术

版本

Java

jdk17+

boot

3.2.0

cloud

2023.0.0

cloud alibaba

2022.0.0.0-RC2

Maven

3.9+

MySQL

8.0+

二、Spring Cloud介绍

1.为什么需要Spring Cloud?

传统的单体架构足以满足中小型项目的需求,但是如果对于一个用户量庞大的系统就会出现各种问题。

例如:如果只有一个支付系统,那么系统崩溃了整个系统就运作不了了。

而分布式系统解决了这个问题,它允许系统以集群的形式部署,形成负载均衡,尽量减少系统崩溃带来的问题。

2.相关组件介绍

在2019年之前,使用的大部分技术都是Netflix提供的,但是由于开发SpringCloud的相关技术不挣钱,因此Netflix就暂停开发相关技术了,虽然他提供的那些技术依旧可以使用,但是已经不推荐了。

因此该笔记只学习新的架构,对于老的技术栈,如果老项目中需要用到,请去B站继续学习

image-20240529170240440

  • 注册与发现
    1. Eureka【Netflix最后的火种,不推荐】
    2. Consul【推荐使用】
    3. Etcd【可以使用】
    4. Nacos【推荐使用,发音:呐扣丝,阿里巴巴提供的】
  • 服务调用和负载均衡
    1. Ribbon【Netflix提供的,建议直接弃用】
    2. OpenFeign
    3. LoadBalancer
  • 分布式事务
    1. Seata【推荐使用,阿里巴巴的】
    2. LCN
    3. Hmily
  • 服务熔断和降级
    1. Hystrix【已经停更了,不推荐】
    2. Circuit Breaker【这只一套规范,使用的是它的实现类】
    3. Resilience4J【CircuitBreaker的实现类,可以使用】
    4. Sentinel【阿里巴巴的,推荐使用】
  • 服务链路追踪
    1. Sleuth+Zipkin【逐渐被替代了,不推荐】
    2. Micrometer Tracing【推荐使用】
  • 服务网关
    1. Zuul【不推荐使用】
    2. Gate Way
  • 分布式配置管理
    1. Config+Bus【不推荐了】
    2. Consul
    3. Nacos

三、单体项目构建

需求说明:下订单,调用支付接口

要求:

1.先做一个通用的boot微服务

2.逐步引入cloud组件,最后编程cloud架构

1.SpringBoot单体服务

1.1 项目构建
  1. 新建一个Maven工程,除了pom.xml.idea其他的东西都删了

    image-20240529171225327

  2. 检查项目的编码格式,统一为UTF-8

    image-20240529171413352

  3. 检查注解支撑是否打开

    image-20240529171529930

  4. 检查java编译版本

    image-20240529171753282

  5. 父工程的pom文件导入依赖,然后刷新

        <properties>
            <maven.compiler.source>17</maven.compiler.source>
            <maven.compiler.target>17</maven.compiler.target>
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
            <hutool.version>5.8.22</hutool.version>
            <druid.version>1.1.20</druid.version>
            <mybatis.springboot.version>3.0.3</mybatis.springboot.version>
            <mysql.version>8.0.11</mysql.version>
            <swagger3.version>2.2.0</swagger3.version>
            <mapper.version>4.2.3</mapper.version>
            <fastjson2.version>2.0.40</fastjson2.version>
            <persistence-api.version>1.0.2</persistence-api.version>
            <spring.boot.test.version>3.1.5</spring.boot.test.version>
            <spring.boot.version>3.2.0</spring.boot.version>
            <spring.cloud.version>2023.0.0</spring.cloud.version>
            <spring.cloud.alibaba.version>2022.0.0.0-RC2</spring.cloud.alibaba.version>
        </properties>
    
        <dependencyManagement>
            <dependencies>
                <!--springboot 3.2.0-->
                <dependency>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-parent</artifactId>
                    <version>${spring.boot.version}</version>
                    <type>pom</type>
                    <scope>import</scope>
                </dependency>
                <!--springcloud 2023.0.0-->
                <dependency>
                    <groupId>org.springframework.cloud</groupId>
                    <artifactId>spring-cloud-dependencies</artifactId>
                    <version>${spring.cloud.version}</version>
                    <type>pom</type>
                    <scope>import</scope>
                </dependency>
                <!--springcloud alibaba 2022.0.0.0-RC2-->
                <dependency>
                    <groupId>com.alibaba.cloud</groupId>
                    <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                    <version>${spring.cloud.alibaba.version}</version>
                    <type>pom</type>
                    <scope>import</scope>
                </dependency>
                <!--SpringBoot集成mybatis-->
                <dependency>
                    <groupId>org.mybatis.spring.boot</groupId>
                    <artifactId>mybatis-spring-boot-starter</artifactId>
                    <version>${mybatis.springboot.version}</version>
                </dependency>
                <!--Mysql数据库驱动8 -->
                <dependency>
                    <groupId>mysql</groupId>
                    <artifactId>mysql-connector-java</artifactId>
                    <version>${mysql.version}</version>
                </dependency>
                <!--SpringBoot集成druid连接池-->
                <dependency>
                    <groupId>com.alibaba</groupId>
                    <artifactId>druid-spring-boot-starter</artifactId>
                    <version>${druid.version}</version>
                </dependency>
                <!--通用Mapper4之tk.mybatis-->
                <dependency>
                    <groupId>tk.mybatis</groupId>
                    <artifactId>mapper</artifactId>
                    <version>${mapper.version}</version>
                </dependency>
                <!--persistence-->
                <dependency>
                    <groupId>javax.persistence</groupId>
                    <artifactId>persistence-api</artifactId>
                    <version>${persistence-api.version}</version>
                </dependency>
                <!-- fastjson2 -->
                <dependency>
                    <groupId>com.alibaba.fastjson2</groupId>
                    <artifactId>fastjson2</artifactId>
                    <version>${fastjson2.version}</version>
                </dependency>
                <!-- swagger3 调用方式 http://你的主机IP地址:5555/swagger-ui/index.html -->
                <dependency>
                    <groupId>org.springdoc</groupId>
                    <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
                    <version>${swagger3.version}</version>
                </dependency>
                <!--hutool-->
                <dependency>
                    <groupId>cn.hutool</groupId>
                    <artifactId>hutool-all</artifactId>
                    <version>${hutool.version}</version>
                </dependency>
                <!-- spring-boot-starter-test -->
                <dependency>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-test</artifactId>
                    <version>${spring.boot.test.version}</version>
                    <scope>test</scope>
                </dependency>
            </dependencies>
        </dependencyManagement>
    
  6. 建库建表,表名t_pay

    create database db2024;
    use db2024;
    DROP TABLE IF EXISTS `t_pay`;
    CREATE TABLE `t_pay` (
      `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
      `pay_no` VARCHAR(50) NOT NULL COMMENT '支付流水号',
      `order_no` VARCHAR(50) NOT NULL COMMENT '订单流水号',
      `user_id` INT(10) DEFAULT '1' COMMENT '用户账号ID',
      `amount` DECIMAL(8,2) NOT NULL DEFAULT '9.9' COMMENT '交易金额',
      `deleted` TINYINT(4) UNSIGNED NOT NULL DEFAULT '0' COMMENT '删除标志,默认0不删除,1删除',
      `create_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
      `update_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
      PRIMARY KEY (`id`)
    
    ) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='支付交易表';
    INSERT INTO t_pay(pay_no,order_no) VALUES('pay17203699','6544bafb424a');
    SELECT * FROM t_pay;
    
1.2 MyBatis逆向工程

本次使用Mapper4,可以不用写单表操作了

  1. 在父工程下面创建一个子模块,给子模块导入依赖

    说明:这个工程只是为了暂时存储生成的代码,等到业务工程使用的时候,会将对应的类复制过去

    <dependencies>
            <!--Mybatis 通用mapper tk单独使用,自己独有+自带版本号-->
            <dependency>
                <groupId>org.mybatis</groupId>
                <artifactId>mybatis</artifactId>
                <version>3.5.13</version>
            </dependency>
            <!-- Mybatis Generator 自己独有+自带版本号-->
            <dependency>
                <groupId>org.mybatis.generator</groupId>
                <artifactId>mybatis-generator-core</artifactId>
                <version>1.4.2</version>
            </dependency>
            <!--通用Mapper-->
            <dependency>
                <groupId>tk.mybatis</groupId>
                <artifactId>mapper</artifactId>
            </dependency>
            <!--mysql8.0-->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
            </dependency>
            <!--persistence-->
            <dependency>
                <groupId>javax.persistence</groupId>
                <artifactId>persistence-api</artifactId>
            </dependency>
            <!--hutool-->
            <dependency>
                <groupId>cn.hutool</groupId>
                <artifactId>hutool-all</artifactId>
            </dependency>
            <!--lombok-->
            <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>
                <exclusions>
                    <exclusion>
                        <groupId>org.junit.vintage</groupId>
                        <artifactId>junit-vintage-engine</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>
        </dependencies>
    
        <build>
            <resources>
                <resource>
                    <directory>${basedir}/src/main/java</directory>
                    <includes>
                        <include>**/*.xml</include>
                    </includes>
                </resource>
                <resource>
                    <directory>${basedir}/src/main/resources</directory>
                </resource>
            </resources>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                    <configuration>
                        <excludes>
                            <exclude>
                                <groupId>org.projectlombok</groupId>
                                <artifactId>lombok</artifactId>
                            </exclude>
                        </excludes>
                    </configuration>
                </plugin>
                <plugin>
                    <groupId>org.mybatis.generator</groupId>
                    <artifactId>mybatis-generator-maven-plugin</artifactId>
                    <version>1.4.2</version>
                    <configuration>
                        <configurationFile>${basedir}/src/main/resources/generatorConfig.xml</configurationFile>
                        <overwrite>true</overwrite>
                        <verbose>true</verbose>
                    </configuration>
                    <dependencies>
                        <dependency>
                            <groupId>mysql</groupId>
                            <artifactId>mysql-connector-java</artifactId>
                            <version>8.0.33</version>
                        </dependency>
                        <dependency>
                            <groupId>tk.mybatis</groupId>
                            <artifactId>mapper</artifactId>
                            <version>4.2.3</version>
                        </dependency>
                    </dependencies>
                </plugin>
            </plugins>
        </build>
    
  2. 在子模块的resources下新建文件config.properties,将内容改成自己的

    #t_pay表包名
    package.name=com.atguigu.cloud
    
    # mysql8.0
    jdbc.driverClass = com.mysql.cj.jdbc.Driver
    jdbc.url= jdbc:mysql://localhost:3306/db2024?characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true
    jdbc.user = root
    jdbc.password =123456
    
  3. 在子模块的resources下新建文件generatorConfig.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE generatorConfiguration
            PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
            "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
    
    <generatorConfiguration>
        <properties resource="config.properties"/>
    
        <context id="Mysql" targetRuntime="MyBatis3Simple" defaultModelType="flat">
            <property name="beginningDelimiter" value="`"/>
            <property name="endingDelimiter" value="`"/>
    
            <plugin type="tk.mybatis.mapper.generator.MapperPlugin">
                <property name="mappers" value="tk.mybatis.mapper.common.Mapper"/>
                <property name="caseSensitive" value="true"/>
            </plugin>
    
            <jdbcConnection driverClass="${jdbc.driverClass}"
                            connectionURL="${jdbc.url}"
                            userId="${jdbc.user}"
                            password="${jdbc.password}">
            </jdbcConnection>
    
            <javaModelGenerator targetPackage="${package.name}.entities" targetProject="src/main/java"/>
    
            <sqlMapGenerator targetPackage="${package.name}.mapper" targetProject="src/main/java"/>
    
            <javaClientGenerator targetPackage="${package.name}.mapper" targetProject="src/main/java" type="XMLMAPPER"/>
    
            <table tableName="t_pay" domainObjectName="Pay">
                <generatedKey column="id" sqlStatement="JDBC"/>
            </table>
        </context>
    </generatorConfiguration>
    
  4. 双击运行Maven中的插件

    image-20240529174957727

1.3 编写业务逻辑
  1. 创建一个业务逻辑模块cloud-provider-payment8001

  2. 给模块导入依赖

    <dependencies>
        <!--SpringBoot通用依赖模块-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <!--SpringBoot集成druid连接池-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
        </dependency>
        <!-- Swagger3 调用方式 http://你的主机IP地址:5555/swagger-ui/index.html -->
        <dependency>
            <groupId>org.springdoc</groupId>
            <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
        </dependency>
        <!--mybatis和springboot整合-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
        </dependency>
        <!--Mysql数据库驱动8 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <!--persistence-->
        <dependency>
            <groupId>javax.persistence</groupId>
            <artifactId>persistence-api</artifactId>
        </dependency>
        <!--通用Mapper4-->
        <dependency>
            <groupId>tk.mybatis</groupId>
            <artifactId>mapper</artifactId>
        </dependency>
        <!--hutool-->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
        </dependency>
        <!-- fastjson2 -->
        <dependency>
            <groupId>com.alibaba.fastjson2</groupId>
            <artifactId>fastjson2</artifactId>
        </dependency>
        <!--lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.28</version>
            <scope>provided</scope>
        </dependency>
        <!--test-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>3.2.0</version>
            </plugin>
        </plugins>
    </build>
    
  3. 编写yaml配置文件

    server:
      port: 8001
    
    # ==========applicationName + druid-mysql8 driver===================
    spring:
      application:
        name: cloud-payment-service
    
      datasource:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://localhost:3306/db2024?characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true
        username: root
        password: 123456
    
    # ========================mybatis===================
    mybatis:
      mapper-locations: classpath:mapper/*.xml
      type-aliases-package: com.atguigu.cloud.entities
      configuration:
        map-underscore-to-camel-case: true
    
  4. 创建启动类

    @SpringBootApplication
    @MapperScan("com.atguigu.cloud.mapper") //import tk.mybatis.spring.annotation.MapperScan;
    public class Main8001 {
        public static void main(String[] args) {
            SpringApplication.run(Main8001.class, args);
        }
    }
    
  5. 将逆向工程生成的代码拷贝到业务工程中,删除原本逆向工程中生成的代码

  6. 编写Service、Controller层的增删改查方法

  7. 启动项目,测试接口

1.4 整合Swager3

注解

标注位置

@Tag

Controller类

@Operation

方法上

@Schema

model层的bean和bean的方法上

  1. 添加依赖

    <dependency>
       <groupId>org.springdoc</groupId>
       <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
       <version>${swagger3.version}</version>
    </dependency>
    
  2. Controller加上@Tag注解

    @Tag(name ="支付模块")
    
  3. Controller的方法上加@Operation注解

    @Operation(summary="查询所有订单")
    
  4. 编写配置类,配置Swagger

    @Configuration
    public class SwaggerConfiguration {
        @Bean
        public GroupedOpenApi PayApi()
        {
            //以/pay开头的请求都是支付模块
            return GroupedOpenApi.builder().group("支付微服务模块").pathsToMatch("/pay/**").build();
        }
        @Bean
        public GroupedOpenApi OtherApi()
        {
            //以/other开头的都是其他模块的请求
            return GroupedOpenApi.builder().group("其它微服务模块").pathsToMatch("/other/**", "/others").build();
        }
        
        @Bean
        public OpenAPI docsOpenApi()
        {
            return new OpenAPI()
                    .info(new Info().title("cloud2024")
                            .description("通用设计rest")
                            .version("v1.0"))
                    .externalDocs(new ExternalDocumentation()
                            .description("www.atguigu.com")
                            .url("https://yiyan.baidu.com/"));
        }
    }
    
  5. 启动项目,访问swagger的地址,调试接口

    localhost:8001/swagger-ui/index.html
    
1.5 统一返回结果Result
  1. 定义一个枚举类,用于状态码的返回【枚举类的书写方法1.举值2.构造3.遍历】

    @Getter
    public enum ReturnCodeEnum {
    
        //1.举值
        RC999("999", "操作XXX失败"),
        RC200("200", "success"),
        RC201("201", "服务开启降级保护,请稍后再试!"),
        RC202("202", "热点参数限流,请稍后再试!"),
        RC203("203", "系统规则不满足要求,请稍后再试!"),
        RC204("204", "授权规则不通过,请稍后再试!"),
        RC403("403", "无访问权限,请联系管理员授予权限"),
        RC401("401", "匿名用户访问无权限资源时的异常"),
        RC404("404", "404页面找不到的异常"),
        RC500("500", "系统异常,请稍后重试"),
        RC375("375", "数学运算异常,请稍后重试"),
        INVALID_TOKEN("2001", "访问令牌不合法"),
        ACCESS_DENIED("2003", "没有权限访问该资源"),
        CLIENT_AUTHENTICATION_FAILED("1001", "客户端认证失败"),
        USERNAME_OR_PASSWORD_ERROR("1002", "用户名或密码错误"),
        BUSINESS_ERROR("1004", "业务逻辑异常"),
        UNSUPPORTED_GRANT_TYPE("1003", "不支持的认证模式");
    
    
        //2.构造
        private final String code;//自定义状态码,对应前面枚举的第一个参数
        private final String message;//自定义信息,对应前面枚举的第二个参数
        ReturnCodeEnum(String code, String message) {
            this.code = code;
            this.message = message;
        }
    
        //3.遍历
        public static ReturnCodeEnum getReturnCodeEnum(String code) {
            //传入一个状态码,如果有,就返回整个枚举信息,如果没有就返回空
            for (ReturnCodeEnum element : ReturnCodeEnum.values()) {
                if (element.getCode().equalsIgnoreCase(code)) {
                    return element;
                }
            }
            return null;
        }
    }
    
  2. 定义统一返回类Result

    @Data
    @Accessors(chain = true)
    public class ResultData<T> {
    
        private String code;
        private String message;
        private T data;
        private long timestamp;//调用方法的时间戳
        
        public ResultData() {
            this.timestamp = System.currentTimeMillis();
        }
    
        public static <T> ResultData<T> success(T data) {
            ResultData<T> resultData = new ResultData<>();
            resultData.setCode(ReturnCodeEnum.RC200.getCode());
            resultData.setMessage(ReturnCodeEnum.RC200.getMessage());
            resultData.setData(data);
            return resultData;
        }
    
        public static <T> ResultData<T> fail(String code, String message) {
            ResultData<T> resultData = new ResultData<>();
            resultData.setCode(code);
            resultData.setMessage(message);
    
            return resultData;
        }
    }
    
  3. 修改原来接口的返回值

    @Operation(summary = "添加支付记录")
        @PostMapping(value = "/pay/add")
        public ResultData<String> addPay(@RequestBody Pay pay) {
            System.out.println(pay.toString());
            int add = payService.add(pay);
            return ResultData.success("添加成功"+add+"条记录");
        }
    
1.6 优化时间格式

有两种解决方式:

  • 方式一:在实体类的时间属性上加@JsonFormat注解

    @JsonFormat(pattern = "yyyy-MM-dd HH-mm-ss" ,timezone = "GMT+8")
    private Date createTime;
    
  • 方式二:SpringBoot项目在yml中进行配置

    spring:
      jackson:
        date-format: yyyy-MM-dd HH-mm-ss
        time-zone: GMT+8
    
1.7 异常处理
@RestControllerAdvice
public class GlobalExceptionHandler {
    //注解的参数是处理的异常信息类型,什么都不加就是全局异常处理
    //@ExceptionHandler(SQlException.class)这个就是专门处理sql异常
    @ExceptionHandler()
    public ResultData<String> globalException(Exception e){
        e.printStackTrace();
        return ResultData.fail(ReturnCodeEnum.RC500.getCode(),ReturnCodeEnum.RC500.getMessage());
    }
}
1.8 编写订单模块【模块构建参考上述步骤】

这个模块的controller使用http请求调用pay模块的方法就行。

因此将entities、utils包中的代码复制过去即可,然后编写controller。

@RestController
public class OrderController {
    private String url="http://localhost:8001";
    //使用httpclient调用pay模块的相关接口
    @GetMapping("/consumer/pay/add")
    public ResultData addOrder(PayDTO payDTO) throws IOException {
        //创建httpclient客户端
        CloseableHttpClient aDefault = HttpClients.createDefault();
        //创建一个post请求
        HttpPost httpPost = new HttpPost(url+"/pay/add");
        //将本方法的参收构建为json字符串
        String jsonString = JSON.toJSONString(payDTO);
        //将json字符串构建为StringEntity
        StringEntity stringEntity = new StringEntity(jsonString);
        //设置请求头和编码格式
        stringEntity.setContentType("application/json");
        stringEntity.setContentEncoding("UTF-8");
        //将参数传入post请求
        httpPost.setEntity(stringEntity);
        //httpclient客户端执行请求
        CloseableHttpResponse execute = aDefault.execute(httpPost);
        //获取响应实体
        HttpEntity entity = execute.getEntity();
        //将实体转化为json字符串
        String string = EntityUtils.toString(entity);
        //将字符串转化为json对象
        JSONObject jsonObject = JSON.parseObject(string);
        //从对象中获取对应的参数
        String code = (String) jsonObject.get("code");
        String data = (String) jsonObject.get("data");
        if (code.equals("200")) {
            return ResultData.success("调用成功data="+data);
        }else {
            return ResultData.fail(code,(String) jsonObject.get("message"));
        }
    }
    @GetMapping("/consumer/pay/get/{id}")
    public ResultData getPayInfo(@PathVariable("id") Integer id) throws IOException {
        CloseableHttpClient aDefault = HttpClients.createDefault();
        HttpGet httpGet = new HttpGet(url+"/pay/get/"+id);
        CloseableHttpResponse execute = aDefault.execute(httpGet);
        HttpEntity entity = execute.getEntity();
        String string = EntityUtils.toString(entity);
        JSONObject jsonObject = JSON.parseObject(string);
        String code = (String) jsonObject.get("code");
        Object data = jsonObject.get("data");
        if (code.equals("200")) {
            return ResultData.success(data);
        }else {
            return ResultData.fail(code,(String) jsonObject.get("message"));
        }
    }
}
1.9 服务调用RestTemplate

RestTemplate是一套封装好的客户端工具,能够发起HTTP请求。类似于okHttp、HttpClient,但是相较于他们,做了更进一步的封装,简化了发送请求的过程。

  1. 导入依赖

    SpringBootWeb中自带,所以如果是一个web项目就不需要导入额外的依赖了
    
  2. 创建对象

    //两种方式
    //1.使用的时候直接new
    RestTemplate restTemplate = new RestTemplate();
    
    //2.在容器中配置一个
    @Configuration
    public class OrderConfiguration {
        @Bean
        public RestTemplate restTemplate() {
            return new RestTemplate();
        }
    }
    
  3. 发送请求

    //1.get请求【两个方法任选一个】
    //两个方法的参数一样:①url请求地址 ②请求参数【可以省略】 ③返回值接收对象类型
    
    //只接受返回对象用这个:restTemplate.getForObject("请求地址",参数,返回值对象类型)
    Result result = restTemplate.getForObject("http://localhost:8080/order/getPayResult", Result.class);
    
    //全部响应体用这个:restTemplate.getForEntity("请求地址",参数,返回值对象类型)
    ResponseEntity<Result> forEntity = restTemplate.getForEntity("http://localhost:8001/pay/get/all", Result.class);
    
    //2.post请求【两个方法任选一个】
    //两个方法的参数一样:①url请求地址 ②请求参数 ③返回值接收对象类型
    
    //只接受返回对象用这个:restTemplate.postForObject("请求地址",参数,返回值对象类型)
    Result result = restTemplate.postForObject("http://localhost:8080/order/getPayResult",pay,Result.class);
    
    //全部响应体用这个:restTemplate.postForEntity("请求地址",参数,返回值对象类型)
    ResponseEntity<String> stringResponseEntity = restTemplate.postForEntity("http://localhost:8080/order/getPayResult",pay,String.class);
    
    //3.delete请求
    //方法的参数:①url请求地址
    restTemplate.delete("http://localhost:8080/order/getPayResult");
    
    //4.put请求
    //方法的参数:①url请求地址 ②请求参数
    restTemplate.put("http://localhost:8080/order/getPayResult", pay);
    
1.10 重复代码抽取

问题:两个模块中有很多重复的代码。例如实体类、返回结果、异常处理类等。

解决方法:将公共代码抽取到一个模块中,其他模块引用公共模块

  1. 创建一个模块cloud-api-commons,引入依赖

        <dependencies>
            <!--SpringBoot通用依赖模块-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-actuator</artifactId>
            </dependency>
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <optional>true</optional>
            </dependency>
            <!--hutool-->
            <dependency>
                <groupId>cn.hutool</groupId>
                <artifactId>hutool-all</artifactId>
            </dependency>
            <dependency>
                <groupId>javax.persistence</groupId>
                <artifactId>persistence-api</artifactId>
            </dependency>
        </dependencies>
    
  2. 将前面两个模块中的公共代码抽取出来放到这个新的模块中

    例如:entities包、utils包、exception包

  3. 将模块打成jar包,放到本地仓库中

  4. 支付和订单模块在pom文件中引入公共模块的jar包

    <dependency>
       <groupId>com.atguigu.cloud</groupId>
       <artifactId>cloud-api-commons</artifactId>
       <version>1.0-SNAPSHOT</version>
    </dependency>
    
  5. 启动项目,测试功能

2.问题引入

问题:为什么一定要引入SpringCloud?

回答:我们刚才那样将每一个模块拆成一个个微服务之后,使用http调用起来很麻烦,而且地址是写死的。如果我们的项目地址变了,我们的代码不得不修改。而且后面如果每个模块以集群部署,每个模块都会有多个地址,那地址该怎么写呢?

四、Consul服务注册和发现

1.基本介绍

  • Consul是什么?

    Consul是一款开源的分布式服务发现与配置管理系统,由HashiCorp公司使用Go语言开发。

    官方:http://consul.io/

  • Consul能干什么?
    1. 服务发现:提供HTTP和DNS两种发现方式
    2. 健康检测
    3. KV存储
    4. 多数据中心
    5. 可视化WEB界面
  • 为什么不使用Eureka了?
    1. Eureka停更了,不在开发新版本了
    2. Eureka对初学者不友好
    3. 我们希望注册中心能够从项目中分离出来,单独运行,而Eureka做不到这一点

2.下载运行

  1. 下载地址:https://developer.hashicorp.com/consul/install

  2. 下载对应的版本【adm64版本的就是x86_64版本的,386就是x86_32版本的】

  3. windows使用下面的命令启动,然后缩放到最小化就行

    consul agent -dev
    
  4. 访问8500端口,进入ui界面

    localhost:8500
    

3.服务注册与发现

需求说明:将前面单体服务中的支付模块、订单模块注册到Consul中

  1. 对应模块的pom文件中引入依赖

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-consul-discovery</artifactId>
    </dependency>
    
  2. 编写配置文件yaml【健康检查那里不配置consul会爆红,不知道为什么】

    spring:
      #当前服务名
      application:
        name: cloud-pay-service
        #配置注册中心的地址
      cloud:
        consul:
          host: localhost
          port: 8500
          discovery:
            #配置当前服务注册到里面使用的名字
            service-name: ${spring.application.name}
            #开启consul的健康检查
            heartbeat:
              enabled: true
              ttl: 10s
    
  3. 启动类加上@EnableDiscoveryClient注解,开启服务发现功能【有人说可以不加这个注解了】

    @EnableDiscoveryClient
    
  4. 启动boot项目

  5. 去consul的ui页面查看是否注册成功

  6. 将订单接口中支付模块的url地址改为consul中注册的名字

    private String url="http://cloud-payment-service";
    
  7. 因为consul默认支持负载均衡,所以http客户端加上@LoadBalanced注解

    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
       return new RestTemplate();
    }
    
  8. 测试接口调用是否成功

4.服务配置

问题说明

系统拆分之后,会产生大量的微服务。每个微服务都有其对应的配置文件yml。如果其中的某个配置项发生了修改,一个一个微服务修改会很麻烦。因此一套集中式的、动态的配置管理设施是必不可少的。从而实现一次修改,处处生效。

案例:给班里同学通知下节课不上了

麻烦的方法:一个个发送消息

简单的方法:直接在班级群@所有人

思路:既然是全局配置信息,那么可以把信息注册到Consul中,需要什么就去Consul中获取

  1. 给对应的模块添加服务配置的依赖

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-consul-config</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-bootstrap</artifactId>
    </dependency>
    
  2. 在resources下新建一个bootstrap.yml文件,将公共配置从application.yml中抽取出来

    说明:

    1. bootstrap.yml和applicaiton.yml一样都是配置文件。applicaiton.yml是用户级的,bootstrap.yml是系统级的,优先级更加高

    2. Spring Cloud会创建一个“Bootstrap Context”,作为Spring应用的Application Context父上下文。初始化的时候,Bootstrap Context负责从外部源加载配置属性并解析配置。这两个上下文共享一个从外部获取的Environment

    3. Bootstrap属性有高优先级,默认情况下,它们不会被本地配置覆盖。

    4. application.yml和bootstrap.yml可以共存,公共的配置项写到bootstrap.yml中,项目特有的配置项写到application.yml

    5. bootstrap.yml比application.yml先加载的

    spring:
      #当前服务名
      application:
        name: cloud-pay-service
        #配置注册中心的地址
      cloud:
        consul:
          host: localhost
          port: 8500
          discovery:
            #配置当前服务注册到里面使用的名字
            service-name: ${spring.application.name}
            #开启consul的健康检查
            heartbeat:
              enabled: true
              ttl: 10s
          #服务配置
          config:
            #这个是配置文件名以-连接【consul的k-v存储用到】,例如:cloud-payment-service
            profile-separator: '-'
            #说明consul中kv的文本格式
            format: YAML
      datasource:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://altman.fun:3306/db2024?characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true
        #读取consul中的配置到这里
        username: ${mysql.username}
        password: ${mysql.password}
    
  3. application.yml就只剩下没有抽取出去的属于微服务自己的配置了

    server:
      port: 8001
    
    spring:
      jackson:
        date-format: yyyy-MM-dd HH-mm-ss
        time-zone: GMT+8
    
    mybatis:
      mapper-locations: classpath:mapper/*.xml
      type-aliases-package: fun.altman.pojo
      configuration:
        map-underscore-to-camel-case: true
    
  4. 打开consul的ui界面,找到key-value,点击右上角的create,创建文件夹

    image-20240531022245351

  5. 在consul中创建二级文件夹config/ 微服务名/,然后创建data文件,供项目测试是否能够读取

    说明:配置默认存储到config/微服务名-配置文件版本/data中,项目启动的时候使用的哪套application.yaml文件就会来这里找对应的文件

    例如:cloud-payment-service微服务如果在application.yml没有指定启用的配置文件

    config/cloud-payment-service/data

    例如:cloud-payment-service微服务如果在application.yml指定启用的配置文件是application-dev.yml

    config/cloud-payment-service-dev/data

    例如:cloud-payment-service微服务如果在application.yml指定启用的配置文件是application-prod.yml

    config/cloud-payment-service-prod/data

    如果创建的是文件夹那么以/结尾

    image-20240531023026068 image-20240531023205025

  6. 创建data文件,随便输入数据库的账号和密码,测试项目是否能读取成功,并连接数据库

    image-20241006162018942

  7. 编写代码,查看项目能否读取到consul中的k-v值

    //从application.yml中获取
    @Value("${server.port}")
    private String port;
    
    @GetMapping(value = "/pay/get/consul")
    //从consul中获取
    public ResultData  getConsul(@Value("${altman.info}") String info) {
            return ResultData.success(info+"当前端口号"+port);
    }
    
  8. 说明:在consul配置数据源,项目读取之后启动,然后改变consul数据源的值没作用,不知道为什么。

5.动态刷新

需求说明:希望Consul的配置变动之后,项目读取的内容也能立马改变。

说明:在consul配置数据源,项目启动之后,改变数据源没作用,不知道为什么。

  1. 在主启动类加上@RefreshScope注解【如果不生效,就放到controller上】

  2. 然后在bootstrap.yml中设置刷新的间隔【这一步不设置也可以,因为官网默认设置了1s刷新】

    spring:
      application:
        name: cloud-payment-service
      cloud:
        consul:
          host: localhost
          port: 8500
          discovery:
            service-name: ${spring.application.name}
          config:
            profile-separator: '-'
            format: YAML
            #设置了这里,1s刷新
            watch:
              wait-time: 1
    

6.配置数据持久化

场景:如果我们把Consul关了,下次启动的时候,之前配置的yaml数据就会全丢了。我们现在需要解决这个问题。

解决方法:写了一个脚本,让k-v存储到指定文件夹。假如真是用到了可以去网上查。

六、LoadBlancer负载均衡

1.基本介绍

LoadBlancer的前身是Ribbon,是一套负责负载均衡的客户端工具。

主要功能:LoadBlancer的主要作用就是提供客户端软件的负载均衡,然后由OpenFeign去调用具体的微服务

  • 负载均衡:通过算法,将请求平均分摊到多个服务上

2.基本使用

场景:订单模块通过负载均衡访问支付模块的8001/8002/8003服务

使用步骤:

  1. 先从注册中心拉取可调用的服务列表,了解他有多少个服务
  2. 按照指定的负载均衡策略,从服务列表中选择一个地址,进行调用
  1. 使用前提:已经使用了注册中心

  2. 启动两个支付模块的项目【为了方便,就不启动三个了】

  3. 因为spring-cloud-starter-consul-discovery 中已经集成了spring-cloud-starter-loadbalancer,所以不需要额外加注解了

    如果没有loadbalancer的依赖,那就自己加上
    
  4. 在订单模块的RestTemplate客户端上加@LoadBalanced,开启负载均衡

    RestTemplate和WebClient支持使用@LoadBalanced注解实现负载均衡,而HttpClient不支持使用@LoadBalanced注解实现负载均衡
    
  5. 将调用的url改成在注册中心注册的名称

    public static final String PaymentSrv_URL = "http://cloud-payment-service";
    
  6. 测试接口

3.基本原理

  1. 会在项目中创建一个DiscoveryClient对象
  2. 通过DiscoveryClient对象,就能够获取注册中心中所有注册的服务
  3. 然后将获取的服务与调用地址中传入的微服务名称进行对比
  4. 如果一致,就会将微服务集群的相关信息返回
  5. 然后通过负载均衡算法,选择出其中一个服务进行调用

4.负载均衡算法

LoadBlancer默认包含两种负载均衡算法,轮询算法和随机算法,同时还可以自定义负载均衡算法。默认使用轮询算法。

  • 轮询算法【LoadBlancer默认使用这个】

    实际调用服务器位置下标=rest接口第几次请求数 % 服务器集群总数量【每次服务重启动后rest接口计数从1开始】
    
    如:
    	List [0] instances = 127.0.0.1:8002
    
    	List [1] instances = 127.0.0.1:8001
    
    8001+ 8002 组合成为集群,它们共计2台机器,集群总数为2, 按照轮询算法原理:
    
    当总请求数为1时: 1 % 2 =1 对应下标位置为1 ,则获得服务地址为127.0.0.1:8001
    
    当总请求数位2时: 2 % 2 =0 对应下标位置为0 ,则获得服务地址为127.0.0.1:8002
    
    当总请求数位3时: 3 % 2 =1 对应下标位置为1 ,则获得服务地址为127.0.0.1:8001
    
    当总请求数位4时: 4 % 2 =0 对应下标位置为0 ,则获得服务地址为127.0.0.1:8002
    
    如此类推......
    
  • 随机算法【LoadBlancer中也包含】

    随机给一个数,然后请求下标对应的微服务
    
  • 支持自定义负载均衡算法

5.负载均衡算法切换

默认的轮询足够开发中使用,这里只是简单说明一下

@Configuration
//下面的value值大小写一定要和consul里面的名字一样,必须一样
//value的值是指对哪个微服务生效
@LoadBalancerClient(value = "cloud-payment-service",configuration = RestTemplateConfig.class)
public class RestTemplateConfig
{
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }

    @Bean
    ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(Environment environment,
                                                            LoadBalancerClientFactory loadBalancerClientFactory) {
        String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);

        //这里切换成了随机算法
        return new RandomLoadBalancer(loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);
    }
}

七、OpenFeign服务接口调用

1.基本介绍

OpenFeign编写了一套声明式的Web服务客户端,使用LoadBlancer实现负载均衡,从而使WEB服务的调用变得很简单。

OpenFeign已经是当前微服务调用最常用的技术

2.能干什么

前面的LoadBalancer章节,我们在使用LoadBalancer+RestTemplate实现了微服务的负载均衡调用,但是在实际开发中,一个接口往往会被多处调用,这就需要多次定义重复的代码,而OpenFeign简化了这个过程。

3.基本使用

  1. 引入OpenFeign和LoadBlancer的依赖

    哪个服务需要调用其他服务的接口,就在哪个服务中引用【例如:订单服务调用支付服务的接口,就在订单服务中引入依赖】

    引入LoadBlancer的依赖,是因为它使用LoadBlancer实现负载均衡

      <!--openFeign-->
      <dependency>
          <groupId>org.springframework.cloud</groupId>
          <artifactId>spring-cloud-starter-openfeign</artifactId>
      </dependency>
      <!--loadbalancer做负载均衡-->
      <dependency>
          <groupId>org.springframework.cloud</groupId>
          <artifactId>spring-cloud-starter-loadbalancer</artifactId>
      </dependency>
    
  2. 启动类加上@EnableFeignClients注解,启动OpenFeign功能

    //如果FeignClient不在SpringBootApplication的扫描范围内可以在@EnableFeignClients中指定扫描范围
    @EnableFeignClients(basePackages="com.atguigu.cloud")
    
  3. 在项目中创建一个api包,专门存放OpenFegin接口

    这里以订单模块调用支付模块的接口为例,因此在订单模块中创建
    
  4. 创建OpenFeign的接口,加上@FeignClient注解,注解的值就是被调用微服务的name

    //例如:被调用的模块是支付模块,支付模块在注册中心的名字叫cloud-payment-service
    @FeignClient("cloud-payment-service")
    public interface PayFeignApi {
        
    }
    
  5. 编写接口中的方法

    @FeignClient("cloud-payment-service")
    public interface PayFeignApi {
        //方法上的注解就是被调用方法的请求类型和地址
        //这样他就合成了http://cloud-payment-service/pay/getall
        @GetMapping("/pay/getall")
        //这里的返回值需要和被调用接口的返回值一致
        ResultData getOrders();
    }
    
  6. controller中注入feign接口对象,然后在需要的地方调用feign接口的方法

    //注入feign对象
    @Autowired
    private PayFeignApi payFeignApi;
    
    @GetMapping("/feign/pay/getall")
    public ResultData getPayInfo(){
        ResultData orders = payFeignApi.getOrders();
        List<Pay> payList = (List<Pay>) orders.getData();
        return ResultData.success(payList);
    }
    

4.最佳实践

上面的基本使用步骤只是基本用法,他暴露了几个基本问题。

如果某个接口需要在不同的微服务中被多次调用,那我们上面的这个写法就需要写多次,从而造成代码的冗余。

因此我们可以把所有的Feign接口抽取成一个公共的模块,然后其他模块引入这个Feign模块调用它里面的方法

  1. 在项目中创建一个模块

  2. 添加openfeign的依赖和公共模块的依赖【@FeignClient注解需要Feign依赖】

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
    
  3. 创建包,创建接口,编写接口中的方法

  4. 其他模块引用这个模块,调用模块中的方法

5.超时控制

问题引入:比较简单的业务使用默认配置是没有问题的,但是如果是复杂业务需要进行很多操作,就可能会出现Read Timeout异常。因此学习定制化超时时间是有必要的

  • OpenFeign客户端的默认等待时间60S,超过这个时间就会报错(这个时间太长了,我们应该设置短一点)

通过两个参数控制超时时间:

  • connectTimeout:连接超时时间【多长时间内必须建立链接】
  • readTimeout:请求处理超时时间【多长时间内必须处理完成】【默认60S】
5.1 全局配置

全局配置能直接控制所有的Feign超时时间

直接修改yaml文件

spring:
  cloud:
    openfeign:
      client:
        config:
          default:
          #指定超时时间最大:3S
            read-timeout: 3000
            #指定连接时间最大:3S
            connect-timeout: 3000
5.2 指定配置

指定配置能够控制指定微服务的接口超时时间。

如果全局配置和指定配置同时存在,指定配置生效

spring:
  cloud:
    openfeign:
      client:
        config:
        #这里将default换成微服务的名称
          cloud-payment-service:
          #指定超时时间最大:3S
            read-timeout: 3000
            #指定连接时间最大:3S
            connect-timeout: 3000

6.重试机制

超时之后不会直接结束请求,而是会重新尝试连接

重试机制默认是关闭的,如何开启呢?只需要编写一个配置类,配置Retryer对象

//1.创建一个配置类
@Configuration
public class RetryerConfig {
    //2.配置Retryer
    @Bean
    public Retryer retryer() {
        //3,设置重试机制
        //return Retryer.NEVER_RETRY;这个是默认的
        
        //第一个参数是多长时间后开启重试机制:这里设置100ms
        //第二个参数是重试的间隔:这里设置1s一次
        //第三个参数是最大请求次数:3次【这个次数是一共的,也就是最大请求几次,而不是第一次请求失败后再请求几次】
        return new Retryer.Default(100, 1, 3);
    }
}

7.连接池

OpenFeign允许指定连接方式,但是默认方式使用jdk自带的HttpURLConnection,但是HttpURLConnection不支持连接池,因此性能较低。

HttpClient和OkHttp都支持连接池,因此为了提升OpenFeign的性能,可以改成使用HttpClient5

  1. 引入HttpClient5和Feign-hc5依赖

    <!-- httpclient5-->
    <dependency>
        <groupId>org.apache.httpcomponents.client5</groupId>
        <artifactId>httpclient5</artifactId>
        <version>5.3</version>
    </dependency>
    <!-- feign-hc5-->
    <dependency>
        <groupId>io.github.openfeign</groupId>
        <artifactId>feign-hc5</artifactId>
        <version>13.1</version>
    </dependency>
    
  2. 在配置文件中开启hc5

    spring:
      cloud:
        openfeign:
          httpclient:
            hc5:
              enabled: true
    

8.请求/响应压缩

OpenFeign支持对请求和响应进行GZIP压缩,以减少通信过程中的性能损耗

spring:
  cloud:
    openfeign:
      compression:
        request:
        #开启请求压缩
          enabled: true
          #达到多大才触发压缩
          min-request-size: 2048
          #触发压缩的类型
          mime-types: types=text/xml,application/xml,application/json
        response:
          #开启响应压缩
          enabled: true

9.日志打印

OpenFeign需要输出日志需要符合两个条件:

  1. FeignClient所在的包日志级别为debug
  2. Feign的日志级别在NONE以上
Feign的日志级别:
	NONE:不记录任何日志信息,这是默认值。
	BASIC:仅记录请求的方法,URL以及响应状态码和执行时间
	HEADERS:在BASIC的基础上,额外记录了请求和响应的头信息
	FULL:记录所有请求和响应的明细,包括头信息、请求体、元数据。
  1. 定义一个类定义Feign的日志级别

    public class DefaultFeignConfig {
        @Bean
        public Logger.Level feignLogLevel(){
            return Logger.Level.FULL;//日志级别
        }
    }
    
  2. 配置文件中设置feign所在包的打印级别

    logging:
      level:
      #下面是feign接口的包
        com:
          atguigu:
            cloud:
              apis:
                PayFeignApi: debug 
    

八、CircuitBreaker断路器

1.基本介绍

分布式系统存在的问题:复杂的分布式应用程序,调用关系复杂,往往有数十个调用关系,调用关系在某些时候将不可避免的失败。比如:超时、异常等。因此我们需要一个框架保证在调用出现问的情况下,不会导致整体服务的失败,避免级联故障,从而提高分布式系统的弹性


解决思路:对于有问题的节点/服务,不再接受请求(快速返回失败处理,或者返回默认的兜底处理结果)


断路器就是这种开关装置。可以想象成家里的保险丝,假如家里真有某个电器发生了故障,能保证及时跳闸,别把整个家给烧了。


他的功能:

  1. 服务熔断:当达到最大访问后,直接拒绝访问,此时调用方会接收到服务降级的处理并返回有好的兜底提示【就好像电闸直接跳了】

    服务熔断会调用服务降级
    
  2. 服务降级:让用户的体验变差【返回简单的提示】,但是不会导致服务的雪崩

    image-20240611162002366

  3. 服务限流:限制访问微服务的请求的并发量,避免服务因流量激增出现故障【实现方法:前面加了一个限流器】

    image-20240611151811851

  4. 服务限时:只能在指定时间访问,其他时间均不可访问

  5. 服务预热:请求一点点放通,别一口气全进来

    例如:学校开门了,门缝由小变大
    一开始让10个请求进来,后面够20个请求的空间了,再后来门全打开了,可以一次让100个请求进来了
    
  6. 实时监控

  7. 兜底的处理动作

2.CircuitBreaker和Resilience4的关系

  • CircuitBreaker是一套抽象的规范
  • Resilience4J实现了CircuitBreaker的规范

3.CircuitBreaker的实现原理

CircuitBreaker的目的是保护分布式系统免受故障和异常,提高系统的可用性和健壮性。

  1. 正常状态处于close状态【闸刀闭合】
  2. 当一个服务或组件出现故障,CircuitBreaker会迅速切换到Open状态(跳闸断电),组织请求发送到该组件或服务,从而避免更多的请求发送到该组件或服务,防止组件或服务的进一步崩溃。
  3. 等待一段时间之后会尝试闭合Half_Open,放几个请求过来探探路,如果可以用了,就会转换到close状态,如果还不行就还是变成open状态。

九、Resilience4J

1.基本介绍

Resilience4J是一个轻量级的容错库,专门做服务熔断、降级等工作。

实现了CircuitBreaker规范。

Resilience4J 2要求使用Java17。

2.基本功能

2.1 熔断【服务熔断+服务降级】
2.1.1 断路器3大状态

image-20240611165334317

2.1.2 断路器状态转换
断路器有三个普通状态:关闭CLOSE【正常请求】、开启OPEN【断电不可用】、半开HALF_OPEN
	1.当熔断器处于CLOSE关闭状态,所有的请求都会通过熔断器。
	2.如果失败率超过设定的阈值,熔断器就会从关闭状态【CLOSE】转换到打开状态【OPEN】,这时所有的请求都会被拒绝
	3.当处于开启状态【OPEN】一段时间后,熔断器就会从开启状态转换到半开状态【HALF_OPEN】,这时会有一定数量的请求放入,并重新计算失败率
	4.如果失败率超过阈值,则会转成打开状态,如果低于阈值,则会变成关闭状态
	
还有两个特殊状态:DISABLED【始终允许访问】、FORCED_OPEN【始终拒绝访问】【这两个状态再生产中不会使用】


断路器的滑动窗口用来存储和统计调用的结果,可以选择基于调用数量的滑动窗口或者基于时间的滑动窗口:
    1.基于时间的滑动窗口:统计最近N秒的调用结果
    2.基于数量的滑动窗口:统计最近N次调用的结果
2.2 限速
2.3 隔离

五、面试题

1.常见的注册中心和他们的特点

常见的注册中心:Eureka、Consul、Zookeeper、Nacos

image-20240531013700815

Eureka:保证数据的可用性和容错性。为了保证高可用,牺牲了一定程度的数据一致性,这意味着服务列表可能不是实时准确的。同时不支持配置中心

Consul:在设计上更倾向于提供一致性和分区容错性。强一致性模型在某些情况下可能导致更高的延迟,尤其是在写操作频繁或网络状况不佳时。支持配置中心

Zookeep:类似Consul,Zookeeper也实现了CP原则。

2.客户端负载均衡和服务器负载均衡有什么区别?

Nging是服务器负载均衡,所有请求都交给Nginx,由Nginx决定去访问哪个服务器的接口【类似于中介】

LoadBlancer是服务端负载均衡,他在本地自己决定调用哪个服务器的接口【没有中间商赚差价】

https://www.bilibili.com/video/BV1gW421P7RDp=35&spm_id_from=pageDriver&vd_source=b246a40ef435cdf32c518bf3f296775d


http://www.kler.cn/a/514397.html

相关文章:

  • 62,【2】 BUUCTF WEB [强网杯 2019]Upload1
  • Text2SQL 智能报表方案介绍
  • flume系列之:flume落cos
  • 【RAG落地利器】向量数据库Chroma入门教程
  • 前端开发Web
  • LeetCode hot 力扣热题100 排序链表
  • 2025年1月19日(舵机VCC)
  • vue3切换路由后页面不报错显示空白,刷新后显示正常
  • 鸿蒙产业学院正式揭牌!软通动力与深信息签署校企合作框架协议
  • Postgresql源码(141)JIT系列分析汇总
  • HDFS的Shell操作
  • 【愚公系列】《微信小程序与云开发从入门到实践》059-迷你商城小程序的开发(加入购物车与创建订单功能开发)
  • c++ 与 Matlab 程序的数据比对
  • 【Docker】 privileged: true:允许容器获得比默认更高的权限
  • JavaScript正则表达式解析:模式、方法与实战案例
  • 基于微信小程序高校订餐系统的设计与开发ssm+论文源码调试讲解
  • 【2024年华为OD机试】 (E卷,200分)-通过软盘拷贝文件(JavaScriptJava PythonC/C++)
  • 使用一行 CSS 去除图像背景
  • el-tree树结构在名称后面添加其他文字
  • 基于Vue的电子商城后台管理系统
  • 【从0带做】基于Springboot3+Vue3的心理健康预约平台
  • 数据库(MySQL)
  • 在 Kubernetes 上快速安装 KubeSphere v4.1.2
  • Wireshark TS | 虚假的 TCP Spurious Retransmission
  • UML-组件图
  • 机器学习(二)