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

文章目录

一、笔记内容技术选型

技术

版本

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站继续学习

  • 注册与发现
    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其他的东西都删了

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

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

  4. 检查java编译版本

  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中的插件

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,创建文件夹

  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

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

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

  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. 服务降级:让用户的体验变差【返回简单的提示】,但是不会导致服务的雪崩

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

  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大状态
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

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

相关推荐
泰晶科技3 分钟前
【前装供应链实操笔记:车规晶振背后AEC-Q100与Q200的绑定关系】
笔记
神明不懂浪漫36 分钟前
【第四章】CSS(二)——文本外观属性与复合选择器
前端·css·经验分享·笔记
什巳1 小时前
JAVA练习278- 和为 K 的子数组
java·学习·算法·leetcode
碎光拾影2 小时前
CPU与MCU核心区别揭秘
单片机·学习·51单片机
栈溢出了3 小时前
Redis 分片集群与哈希槽笔记
java·redis·笔记·spring
我命由我123454 小时前
复利极简理解
经验分享·笔记·学习·职场和发展·求职招聘·职场发展·学习方法
AOwhisky4 小时前
Python 学习笔记(第三期)——流程控制核心知识点自测与详解
开发语言·笔记·python·学习·云原生·运维开发·流程控制
tyqtyq224 小时前
药品说明书解读 —— AI 安全用药助手,鸿蒙原生应用深度解析
人工智能·学习·华为·生活·harmonyos
小心亦新5 小时前
STM32学习13 定时器1中断
stm32·嵌入式硬件·学习
我命由我123455 小时前
72 法则极简理解
经验分享·笔记·学习·职场和发展·求职招聘·职场发展·学习方法