红色部分是Alibaba
1、从boot和cloud版本选型开始 最终选型 1 2 3 4 5 6 Java 17+ cloud 2023.0.0 boot 3.2.0 cloud alibaba 2022.0.0.0-RC2 Maven 3.9+ Mysql 8.0+
推演过程 为什么选版本?怎么选型呢?
SpringCloud 和 SpringCloud Alibaba 截然不同,根据工作需要可以直接学SpringCloudAlibaba
SpringBoot版本选择
git源码地址 https://github.com/spring-projects/spring-boot/releases/
官网看Boot版本 3.2.0
也就是直接看官网,但是官网有延后,还要看 git源码上的版本说明,配合着看
那谁决定谁呢?
若同时使用Boot和cloud,由话事人cloud决定boot版本
Spring cloud Alibaba 毕业版本依赖关系
2、关于Cloud各种组件的停更/升级/替换 微服务理论知识入门 什么是微服务
形象一点来说:微服务就像搭积木,每个微服务都是一个零件一个继母,并使用这些继母零件组装出不同的形状,出来最后一个成体。
通俗来说:微服务就是把一个大系统按业务功能分解成多个职责单一的小系统,每一个小木块尽量专一的只做一件事,并利用简单的方式使多个小系统相互协作,组合成一个大系统后再同意对外提供整体服务。
学科派一点:专注于单一责任小型功能模块为基础,并利用轻量化机制(通常为HTTP RESTful API)实现通信完成复杂业务搭建的一种架构思想。
那么微服务架构需要的功能有以下:
1、服务注册与发现
2、服务调用
3、服务熔断
4、负载均衡
5、服务降级
6、服务消息队列
7、配置中心管理
8、服务网关
9、服务监控
10、全链路追踪
11、自动化构建部署
12、服务定时任务调度操作
等等。
springcloud和springboot的区别
Spring Boot 是「快速开发单个微服务应用」的基础框架,解决 “单体应用快速开发、配置简化” 问题;Spring Cloud 是「管理 / 协调多个微服务」的分布式框架,基于 Spring Boot 构建,解决 “微服务集群的服务治理、分布式协作” 问题 。
SpringCloud 和SpringCloud Alibaba 的区别
Spring Cloud Alibaba 是 Spring Cloud 生态的「本土化增强实现」,是 Spring Cloud 标准规范的具体落地;两者完全可以混着用,且实际生产中大多是 “Spring Cloud 核心组件 + Spring Cloud Alibaba 特色组件” 的组合方式 。
那么SpringCloud = 分布式微服务架构的一站式解决方案,是多种微服务架构落地技术的集合体,俗称微服务全家桶
SpringCloud这个大集合里有多少种技术?大概20多种各种各样,但是下面就讲解通用的。
现在用的组件。
3、微服务架构编码Base工程模块构建 理论知道了,这里直接实战理解。
做一个订单 -> 支付 的案例
需求说明
约定 > 配置 > 编码 IDEA新建Project和Maven父工程 微服务cloud整体聚合Maven父工程Project 1、New Project
2、聚合总父工程名字
3、字符编码
4、注解生效激活
5、java编译版本选17
6、File Type过滤以及配置自己的Maven
父工程POM文件内容 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 <?xml version="1.0" encoding="UTF-8" ?> <project xmlns ="http://maven.apache.org/POM/4.0.0" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" > <modelVersion > 4.0.0</modelVersion > <groupId > com.bitzh</groupId > <artifactId > cloud2024</artifactId > <version > 1.0-SNAPSHOT</version > <packaging > pom</packaging > <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 > <lombok.version > 1.18.26</lombok.version > <druid.version > 1.1.20</druid.version > <mybatis.springboot.version > 3.0.2</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 > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-parent</artifactId > <version > ${spring.boot.version}</version > <type > pom</type > <scope > import</scope > </dependency > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-dependencies</artifactId > <version > ${spring.cloud.version}</version > <type > pom</type > <scope > import</scope > </dependency > <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 > <dependency > <groupId > org.mybatis.spring.boot</groupId > <artifactId > mybatis-spring-boot-starter</artifactId > <version > ${mybatis.springboot.version}</version > </dependency > <dependency > <groupId > mysql</groupId > <artifactId > mysql-connector-java</artifactId > <version > ${mysql.version}</version > </dependency > <dependency > <groupId > com.alibaba</groupId > <artifactId > druid-spring-boot-starter</artifactId > <version > ${druid.version}</version > </dependency > <dependency > <groupId > tk.mybatis</groupId > <artifactId > mapper</artifactId > <version > ${mapper.version}</version > </dependency > <dependency > <groupId > javax.persistence</groupId > <artifactId > persistence-api</artifactId > <version > ${persistence-api.version}</version > </dependency > <dependency > <groupId > com.alibaba.fastjson2</groupId > <artifactId > fastjson2</artifactId > <version > ${fastjson2.version}</version > </dependency > <dependency > <groupId > org.springdoc</groupId > <artifactId > springdoc-openapi-starter-webmvc-ui</artifactId > <version > ${swagger3.version}</version > </dependency > <dependency > <groupId > cn.hutool</groupId > <artifactId > hutool-all</artifactId > <version > ${hutool.version}</version > </dependency > <dependency > <groupId > org.projectlombok</groupId > <artifactId > lombok</artifactId > <version > ${lombok.version}</version > <optional > true</optional > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-test</artifactId > <version > ${spring.boot.test.version}</version > <scope > test</scope > </dependency > </dependencies > </dependencyManagement > </project >
Maven工程落地细节复习 Maven中的DependencyManagement和Dependencies dependencyManagement
Maven 使用dependencyManagement 元素来提供了一种管理依赖版本号的方式。
通常会在一个组织或者项目的最顶层的父POM 中看到dependencyManagement 元素。
使用pom.xml 中的dependencyManagement 元素能让所有在子项目中引用一个依赖而不用显式的列出版本号。
Maven会沿着父子层次向上走,直到找到一个拥有dependencyManagement 元素的项目,然后它就会使用这个
dependencyManagement 元素中指定的版本号。
这样做的好处就是:如果有多个子项目都引用同一样依赖,则可以避免在每个使用的子项目里都声明一个版本号,优势:
1、这样当想升级或切换到另一个版本时,只需要在顶层父容器里更新,而不需要一个一个子项目的修改 ;
2、另外如果某个子项目需要另外的一个版本,只需要声明version就可。
dependencyManagement里只是声明依赖,并不实现引入\ ,因此子项目需要显示的声明需要用的依赖。
* 如果不在子项目中声明依赖,是不会从父项目中继承下来的,只有在子项目中写了该依赖项并且没有指定具体版本,才会从父项目中继承该项 且version和scope都读取自父pom;
* 如果子项目中指定了版本号,那么会使用子项目中指定的jar版本。
maven中跳过单元测试 1、配置
1 2 3 4 5 6 7 8 9 10 11 <build > <plugins > <plugin > <groupId > org.apache.maven.plugins</groupId > <artifactId > maven-surefire-plugin</artifactId > <configuration > <skip > true</skip > </configuration > </plugin > </plugins > </build >
2、IDEA工具支持(推荐)
mysql驱动说明 Mysql5 1 2 3 4 5 6 7 8 9 10 11 12 # mysql5.7---JDBC四件套 jdbc.driverClass = com.mysql.jdbc.Driver jdbc.url= jdbc:mysql://localhost:3306/db2024?useUnicode=true&characterEncoding=UTF-8&useSSL=false jdbc.user = root jdbc.password =123456 # Maven的POM文件处理 <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency>
Mysql8 1 2 3 4 5 6 7 8 9 10 11 12 # mysql8.0---JDBC四件套 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 # Maven的POM <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.11</version> </dependency>
Mapper4之一键生成 这只是个单独的生成工具,也可以使用idea的一些插件,或者用ai生成常见的sql脚本,都是一样的。
mybatis-generator 官网:http://mybatis.org/generator/
MyBatis通用Mapper4官网 官网:https://github.com/abel533/Mapper
本次使用Mapper4
一键生成步骤 SQL 新建数据库db2024
创建库t_pay支付信息表SQL
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 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;
Module 普通的Maven工程:mybatis_generator2024
POM 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 <?xml version="1.0" encoding="UTF-8" ?> <project xmlns ="http://maven.apache.org/POM/4.0.0" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" > <modelVersion > 4.0.0</modelVersion > <parent > <groupId > com.bitzh</groupId > <artifactId > cloud2024</artifactId > <version > 1.0-SNAPSHOT</version > </parent > <artifactId > mybatis_generator2024</artifactId > <properties > <maven.compiler.source > 17</maven.compiler.source > <maven.compiler.target > 17</maven.compiler.target > <project.build.sourceEncoding > UTF-8</project.build.sourceEncoding > </properties > <dependencies > <dependency > <groupId > org.mybatis</groupId > <artifactId > mybatis</artifactId > <version > 3.5.13</version > </dependency > <dependency > <groupId > org.mybatis.generator</groupId > <artifactId > mybatis-generator-core</artifactId > <version > 1.4.2</version > </dependency > <dependency > <groupId > tk.mybatis</groupId > <artifactId > mapper</artifactId > </dependency > <dependency > <groupId > mysql</groupId > <artifactId > mysql-connector-java</artifactId > </dependency > <dependency > <groupId > javax.persistence</groupId > <artifactId > persistence-api</artifactId > </dependency > <dependency > <groupId > cn.hutool</groupId > <artifactId > hutool-all</artifactId > </dependency > <dependency > <groupId > org.projectlombok</groupId > <artifactId > lombok</artifactId > <optional > true</optional > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-test</artifactId > <scope > test</scope > <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 > </project >
配置 在src\main\resources路径下新建
config.properties
1 2 3 4 5 6 7 8 package.name =com.bitzh.cloud 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 = root
generatorConfig.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 <?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 >
一键生成 双击插件 mybatis-generator:generate,一键生成
生成enetity + mapper接口 + xml实现SQL
Rest通用Base工程构建 工程V1 1、cloud-provider-payment8001微服务提供者支付Module模块 微服务小口诀 建module,改POM,写YML,主启动,业务类
步骤 1、建module
创建普通Maven模块:cloud-provider-payment8001
2、改POM
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 <?xml version="1.0" encoding="UTF-8" ?> <project xmlns ="http://maven.apache.org/POM/4.0.0" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" > <modelVersion > 4.0.0</modelVersion > <parent > <groupId > com.bitzh</groupId > <artifactId > cloud2024</artifactId > <version > 1.0-SNAPSHOT</version > </parent > <artifactId > cloud-provider-payment8001</artifactId > <properties > <maven.compiler.source > 17</maven.compiler.source > <maven.compiler.target > 17</maven.compiler.target > <project.build.sourceEncoding > UTF-8</project.build.sourceEncoding > </properties > <dependencies > <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 > com.alibaba</groupId > <artifactId > druid-spring-boot-starter</artifactId > </dependency > <dependency > <groupId > org.springdoc</groupId > <artifactId > springdoc-openapi-starter-webmvc-ui</artifactId > </dependency > <dependency > <groupId > org.mybatis.spring.boot</groupId > <artifactId > mybatis-spring-boot-starter</artifactId > </dependency > <dependency > <groupId > mysql</groupId > <artifactId > mysql-connector-java</artifactId > </dependency > <dependency > <groupId > javax.persistence</groupId > <artifactId > persistence-api</artifactId > </dependency > <dependency > <groupId > tk.mybatis</groupId > <artifactId > mapper</artifactId > </dependency > <dependency > <groupId > cn.hutool</groupId > <artifactId > hutool-all</artifactId > </dependency > <dependency > <groupId > com.alibaba.fastjson2</groupId > <artifactId > fastjson2</artifactId > </dependency > <dependency > <groupId > org.projectlombok</groupId > <artifactId > lombok</artifactId > <version > 1.18.28</version > <scope > provided</scope > </dependency > <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 > </plugin > </plugins > </build > </project >
3、写YML
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 server: port: 8001 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: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.atguigu.cloud.entities configuration: map-underscore-to-camel-case: true
4、主启动
1 2 3 4 5 6 7 8 @SpringBootApplication @MapperScan("com.bitzh.cloud.mapper") public class Main8001 { public static void main (String[] args) { SpringApplication.run(Main8001.class, args); } }
5、业务类
这个版本有些bug,但是常见所以这里演示一些bug
首先将之前一键生成的代码直接拷贝进入8001模块
entities
主实体Pay
传递数值PayDTO(PO 对应数据库表,DTO 用于服务间 / 层间传输,VO 面向前端展示 / 接收)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Data @AllArgsConstructor @NoArgsConstructor public class PayDTO implements Serializable { private Integer id; private String payNo; private String orderNo; private Integer userId; private BigDecimal amount; }
mapper
Mapper接口PayMapper用生成的
映射文件PayMapper.xml用生成的
service
服务接口 PayService
实现类 PayServiceImpl
controller
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 @RestController public class PayController { @Resource PayService payService; @PostMapping(value = "/pay/add") public String addPay (@RequestBody Pay pay) { System.out.println(pay.toString()); int i = payService.add(pay); return "成功插入记录,返回值:" +i; } @DeleteMapping(value = "/pay/del/{id}") public Integer deletePay (@PathVariable("id") Integer id) { return payService.delete(id); } @PutMapping(value = "/pay/update") public String updatePay (@RequestBody PayDTO payDTO) { Pay pay = new Pay (); BeanUtils.copyProperties(payDTO, pay); int i = payService.update(pay); return "成功修改记录,返回值:" +i; } @GetMapping(value = "/pay/get/{id}") public Pay getById (@PathVariable("id") Integer id) { return payService.getById(id); } @GetMapping(value = "/pay/getAll") public List<Pay> getAll () { return payService.getAll(); } }
测试 1、postman/apifox一键生成
2、Swqgger3 / Knife4J
常用注解
注解列表
Swagger想要启动需要配置,下面的配置含分组迭代
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 package com.bitzh.cloud.config;import io.swagger.v3.oas.models.ExternalDocumentation;import io.swagger.v3.oas.models.OpenAPI;import io.swagger.v3.oas.models.info.Info;import org.springdoc.core.models.GroupedOpenApi;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;@Configuration public class Swagger3Config { @Bean public GroupedOpenApi PayApi () { return GroupedOpenApi.builder().group("支付微服务模块" ).pathsToMatch("/pay/**" ).build(); } @Bean public GroupedOpenApi OtherApi () { 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/" )); } }
访问: http://localhost:8001/swagger-ui/index.html
2、上面模块还有哪些问题 1、时间格式问题 时间日志格式的统一和定制情况?
2、Java如何设计API接口实现统一格式返回? 影响前端/小程序/app等交互体验和开发
3、全局异常介入返回的标准格式 有统一返回值 + 全局统一异常
等等
工程V2 解决时间格式问题 方法一:在相应的类的属性上使用 @JsonFormat 注解
1 2 3 4 @JsonFormat(pattern = "yyy-MM-dd HH:mm:ss",timezone = "GMT+8") 或者 @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:sss") private Date billtime;
方法二:如果是 Spring Boot项目,也可以在 application.yml文件中制定
1 2 3 4 spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT+8
但是后期配置到yml里面的文件越来越多,所以就不写进去了。
解决统一返回值 思路 定义返回标准格式,3大标配
code 状态值:由后端统一定义各种返回结果的状态码
message 描述:本次接口调用的结果描述
data 数据:本次返回的数据
扩展:接口调用时间之类
步骤 新建枚举类 ReturnCodeEnum
枚举类小口诀:举值、构造、遍历
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 @Getter public enum ReturnCodeEnum { 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" , "不支持的认证模式" ); private final String code; private final String message; ReturnCodeEnum(String code, String message){ this .code = code; this .message = message; } public static ReturnCodeEnum getReturnCodeEnum (String code) { for (ReturnCodeEnum element : ReturnCodeEnum.values()) { if (element.getCode().equalsIgnoreCase(code)) { return element; } } return null ; } public static ReturnCodeEnum getReturnCodeEnumV2 (String code) { return Arrays.stream(ReturnCodeEnum.values()).filter(x -> x.getCode().equalsIgnoreCase(code)).findFirst().orElse(null ); } }
新建统一定义返回对象ResultData :ResultData<T>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 @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; } }
修改PayController 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 @RestController @Tag(name = "支付微服务模块",description = "支付CRUD") public class PayController { @Resource PayService payService; @PostMapping(value = "/pay/add") @Operation(summary = "新增",description = "新增支付流水方法,json串做参数") public ResultData<String> addPay (@RequestBody Pay pay) { System.out.println(pay.toString()); int i = payService.add(pay); return ResultData.success("成功插入记录,返回值:" +i); } @DeleteMapping(value = "/pay/del/{id}") @Operation(summary = "删除",description = "删除支付流水方法") public ResultData<Integer> deletePay (@PathVariable("id") Integer id) { int i = payService.delete(id); return ResultData.success(i); } @PutMapping(value = "/pay/update") @Operation(summary = "修改",description = "修改支付流水方法") public ResultData<String> updatePay (@RequestBody PayDTO payDTO) { Pay pay = new Pay (); BeanUtils.copyProperties(payDTO, pay); int i = payService.update(pay); return ResultData.success("成功修改记录,返回值:" +i); } @GetMapping(value = "/pay/get/{id}") @Operation(summary = "按照ID查流水",description = "查询支付流水方法") public ResultData<Pay> getById (@PathVariable("id") Integer id) { Pay pay = payService.getById(id); return ResultData.success(pay); } }
解决:全局异常介入返回的标准格式 为什么需要全局异常处理器 不要再手写 try catch
当然非要写也可以
新建全局异常类 GlobalExceptionHandler
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Slf4j @RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(RuntimeException.class) @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) public ResultData<String> exception (Exception e) { System.out.println("----come in GlobalExceptionHandler" ); log.error("全局异常信息exception:{}" , e.getMessage(), e); return ResultData.fail(ReturnCodeEnum.RC500.getCode(),e.getMessage()); } }
引入微服务理念,从这里开始 订单微服务80如何才能调用到支付微服务8001? cloud-consumer-order80微服务调用者订单Module模块 建立 cloud-consumer-order80 改POM 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 <?xml version="1.0" encoding="UTF-8" ?> <project xmlns ="http://maven.apache.org/POM/4.0.0" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" > <modelVersion > 4.0.0</modelVersion > <parent > <groupId > com.bitzh</groupId > <artifactId > cloud2024</artifactId > <version > 1.0-SNAPSHOT</version > </parent > <groupId > com.bitzh.cloud</groupId > <artifactId > cloud-consumer-order80</artifactId > <properties > <maven.compiler.source > 17</maven.compiler.source > <maven.compiler.target > 17</maven.compiler.target > <project.build.sourceEncoding > UTF-8</project.build.sourceEncoding > </properties > <dependencies > <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 > <dependency > <groupId > cn.hutool</groupId > <artifactId > hutool-all</artifactId > </dependency > <dependency > <groupId > com.alibaba.fastjson2</groupId > <artifactId > fastjson2</artifactId > </dependency > <dependency > <groupId > org.springdoc</groupId > <artifactId > springdoc-openapi-starter-webmvc-ui</artifactId > </dependency > </dependencies > <build > <plugins > <plugin > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-maven-plugin</artifactId > </plugin > </plugins > </build > </project >
写YML
主启动
1 2 3 4 5 6 7 8 @SpringBootApplication public class Main80 { public static void main (String[] args) { SpringApplication.run(Main80.class,args); } }
业务类 那么我们现在有两个Module,怎么调用另一个模块呢?这时候就需要了解RestTemplate
entities 传递数值PayDTO
消费者调用支付模块,应该是支付模块暴露接口让消费者调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 @Data @AllArgsConstructor @NoArgsConstructor public class PayDTO implements Serializable { private Integer id; private String payNo; private String orderNo; private Integer userId; private BigDecimal amount; }
RestTemplate
是什么?
1 2 3 4 RestTemplate提供了多种便捷访问远程Http服务的方法, 是一种简单便捷的访问restful服务模板类,是Spring提供的用于访问Rest服务的客户端模板工具集 官网:https://docs.spring.io/spring-framework/docs/6.0.11/javadoc-api/org/springframework/web/client/RestTemplate.html
常用API使用说明
1 2 3 4 5 使用restTemplate访问restful接口非常的简单粗暴无脑。 (url, requestMap, ResponseBean.class)这三个参数分别代表 REST请求地址、请求参数、HTTP响应转换被转换成的对象类型。
getForObject方法/getForEntity方法
返回对象为响应体中数据转化成的对象,基本上可以理解为Json
返回对象为ResponseEntity对象,包含了响应中的一些重要信息,比如响应头、响应状态码、响应体等
postForObject / postForEntity
GET请求方法
1 2 3 4 5 6 7 8 9 10 11 12 <T> T getForObject (String url, Class<T> responseType, Object... uriVariables) ; <T> T getForObject (String url, Class<T> responseType, Map<String, ?> uriVariables) ; <T> T getForObject (URI url, Class<T> responseType) ; <T> ResponseEntity<T> getForEntity (String url, Class<T> responseType, Object... uriVariables) ; <T> ResponseEntity<T> getForEntity (String url, Class<T> responseType, Map<String, ?> uriVariables) ; <T> ResponseEntity<T> getForEntity (URI var1, Class<T> responseType) ;
POST请求方法
1 2 3 4 5 6 7 8 9 10 11 12 <T> T postForObject (String url, @Nullable Object request, Class<T> responseType, Object... uriVariables) ; <T> T postForObject (String url, @Nullable Object request, Class<T> responseType, Map<String, ?> uriVariables) ; <T> T postForObject (URI url, @Nullable Object request, Class<T> responseType) ; <T> ResponseEntity<T> postForEntity (String url, @Nullable Object request, Class<T> responseType, Object... uriVariables) ; <T> ResponseEntity<T> postForEntity (String url, @Nullable Object request, Class<T> responseType, Map<String, ?> uriVariables) ; <T> ResponseEntity<T> postForEntity (URI url, @Nullable Object request, Class<T> responseType) ;
当然正常开发的时候不会每个都使用都new一次,而是写一个配置类利用容器管理对象
config配置类
1 2 3 4 5 6 7 8 9 @Configuration public class RestTemplateConfig { @Bean public RestTemplate restTemplate () { return new RestTemplate (); } }
controller调用支付模块
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 @RestController public class OrderController { public static final String PaymentSrv_URL = "http://localhost:8001" ; @Resource private RestTemplate restTemplate; @GetMapping("/consumer/pay/add") public ResultData addOrder (PayDTO payDTO) { return restTemplate.postForObject(PaymentSrv_URL + "/pay/add" , payDTO, ResultData.class); } @GetMapping("/consumer/pay/get/{id}") public ResultData getPayInfo (@PathVariable("id") Integer id) { return restTemplate.getForObject(PaymentSrv_URL + "/pay/get/{id}" , ResultData.class,id); } @PutMapping("/consumer/pay/update") public ResultData updatePayInfo (@RequestBody PayDTO payDTO) { HttpEntity<PayDTO> requestEntity = new HttpEntity <>(payDTO); return restTemplate.exchange( PaymentSrv_URL + "/pay/update" , HttpMethod.PUT, requestEntity, ResultData.class).getBody(); } @DeleteMapping("/consumer/pay/delete/{id}") public ResultData deletePayInfo (@PathVariable("id") Integer id) { return restTemplate.exchange( PaymentSrv_URL + "/pay/del/{id}" , HttpMethod.DELETE, null , ResultData.class,id).getBody(); } }
一个个启动会很多,所以把他纳入到service里面,统一管理起来
测试
现在测试直接访问cosumer看能不能调用成功。
工程重构 观察问题 现在两个模块中有重复的部分,需要进行重构简化代码
新建Module 创建cloud-api-commons
创建对外暴露通用的组件 / api / 接口 / 工具类等
改POM 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 <?xml version="1.0" encoding="UTF-8" ?> <project xmlns ="http://maven.apache.org/POM/4.0.0" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" > <modelVersion > 4.0.0</modelVersion > <parent > <groupId > com.bitzh</groupId > <artifactId > cloud2024</artifactId > <version > 1.0-SNAPSHOT</version > </parent > <groupId > com.bitzh.cloud</groupId > <artifactId > cloud-api-commons</artifactId > <properties > <maven.compiler.source > 17</maven.compiler.source > <maven.compiler.target > 17</maven.compiler.target > <project.build.sourceEncoding > UTF-8</project.build.sourceEncoding > </properties > <dependencies > <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 > <dependency > <groupId > cn.hutool</groupId > <artifactId > hutool-all</artifactId > </dependency > </dependencies > </project >
entities 这个模块不用启动,只是公共模块所以不用写主启动
然后现在把重复的类抽取出来
一个是 PayDTO ,一个是统一返回
然后把通用的打包成 jar 包对外暴露,提供服务
订单80和支付8001分别改造
删除各自原先的 entities 和 统一返回体等内容
然后各自粘贴POM内容
1 2 3 4 5 6 <dependency > <groupId > com.bitzh.cloud</groupId > <artifactId > cloud-api-commons</artifactId > <version > 1.0-SNAPSHOT</version > </dependency >
目前工程样图
截至目前,没有引入任何SpringCloud相关内容
为什么要引入微服务 上一个问题controller写死的问题
微服务所在的IP地址和端口号硬编码到订单微服务中,会存在非常多的问题
(1)如果订单微服务和支付微服务的IP地址或者端口号发生了变化,则支付微服务将变得不可用,需要同步修改订单微服务中调用支付微服务的IP地址和端口号。
(2)如果系统中提供了多个订单微服务和支付微服务,则无法实现微服务的负载均衡功能。
(3)如果系统需要支持更高的并发,需要部署更多的订单微服务和支付微服务,硬编码订单微服务则后续的维护会变得异常复杂。
所以,在微服务开发的过程中,需要引入服务治理功能,实现微服务之间的动态注册与发现,从此刻开始我们正式进入SpringCloud实战
4、Consul服务注册与发现 为什么要引入服务注册中心 之前写死地址硬编码会有很多问题。
为什么不再使用老牌的Eureka 1、Eureka停更了
2、Eureka对初学者不友好,自我保护机制
3、注册中心独立且和微服务功能解耦:目前主流服务中心,希望单独隔离出来,而不是嵌入到系统当中。也就是说订单服务,库存服务,支付服务,然后还有个Eureka服务还需要程序员自己开发,而不是独立出来解耦。
4、Spring Cloud Alibaba,现在能做两件事,Eureka只能做一件事。
consul简介 是什么 官网:https://www.consul.io/
Consul 是一套开源的分布式服务发现和配置管理系统,由 HashiCorp 公司用 Go 语言开发。
提供了微服务系统中的服务治理、配置中心、控制总线 等功能。这些功能中的每一个都可以根据需要单独使用,也可以一起使用以构建全方位的服务网格 ,总之Consul提供了一种完整的服务网格解决方案。它具有很多优点。包括: 基于 raft 协议 ,比较简洁; 支持健康检查 , 同时支持 HTTP 和 DNS 协议 支持跨数据中心的 WAN 集群 提供图形界面 跨平台 ,支持 Linux、Mac、Windows
HashiCorp是一家非常知名的基础软件提供商,很多人可能没听过它的名字,但是其旗下的6款主流软件,Terraform、Consul、Vagrant、Nomad、Vault,Packer 相信不少程序员都听说或使用过,尤其是Consul使用者不尽其数。截止目前为止,从HashiCorp 官网上的声明来看,开源项目其实还是“安全”的,被禁用的只是Vault企业版(并且原因是Vault产品 目前使用的加密算法在中国不符合法规,另一方面是美国出口管制法在涉及加密相关软件上也有相应规定。因此这两项原因使得HashiCorp不得不在声明中说明风险)而非其他所有开源产品(Terraform、Consul等)。因此,大家可以暂时放下心来,放心使用!
spring consul https://docs.spring.io/spring-cloud-consul/docs/current/reference/html/
能干嘛 服务发现 提供HTTP和DNS两种发现方式。
健康检测 支持多种方式,HTTP、TCP、Docker、Shell脚本定制化监控
KV存储 Key、Value的存储方式
多数据中心 Consul支持多数据中心
可视化Web界面 去哪里下 官网:https://developer.hashicorp.com/consul/install
centos
1 2 3 sudo yum install -y yum-utils sudo yum-config-manager --add-repo https://rpm.releases.hashicorp.com/RHEL/hashicorp.repo sudo yum -y install consul
怎么玩 看官网,两大作用:服务注册中心,分布式配置管理
安装并运行consul 下载解压后,在cmd里面然后进入到该解压路径下,然后输入命令 consul --version验证是否安装✅️
使用开发模式启动
1 2 consul agent -dev 通过访问Consul的首页:http://localhost:8500
服务注册与发现 现在要把8001和80注册进入注册中心了。
服务提供者8001 支付服务provider8001注册进consul POM 1 2 3 4 5 <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-consul-discovery</artifactId > </dependency >
YML 1 2 3 4 5 6 7 cloud: consul: host: localhost port: 8500 discovery: service-name: ${spring.application.name}
主启动 1 2 3 4 5 6 7 8 9 10 @SpringBootApplication @MapperScan("com.atguigu.cloud.mapper") @EnableDiscoveryClient public class Main8001 { public static void main (String[] args) { SpringApplication.run(Main8001.class,args); } }
因为没有业务和代码操作,本来的还有的业务类和controller,现在都不用操作了。
启动8001并查看consul控制台
然后控制台让我们 remove 一个commons-logging.jar
1 2 3 4 5 6 7 8 9 10 11 <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-consul-discovery</artifactId > <exclusions > <exclusion > <groupId > commons-logging</groupId > <artifactId > commons-logging</artifactId > </exclusion > </exclusions > </dependency >
服务消费者80 修改微服务 cloud-consumer-order80
POM 1 2 3 4 5 6 7 8 9 10 11 <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-consul-discovery</artifactId > <exclusions > <exclusion > <groupId > commons-logging</groupId > <artifactId > commons-logging</artifactId > </exclusion > </exclusions > </dependency >
YML 1 2 3 4 5 6 7 8 9 10 11 spring: application: name: cloud-consumer-order cloud: consul: host: localhost port: 8500 discovery: prefer-ip-address: true service-name: ${spring.application.name}
主启动 1 2 3 4 5 6 7 8 9 @SpringBootApplication @EnableDiscoveryClient public class Main80 { public static void main (String[] args) { SpringApplication.run(Main80.class,args); } }
Controller 1 public static final String PaymentSrv_URL = "http://cloud-payment-service" ;
启动并查看consul控制台
访问测试地址 http://localhost/consumer/pay/get/1
一个bug,报错了说找不到我们的地址。因为默认是负载均衡,需要在restTemplate支持负载均衡就行了。
配置修改RestTemplateConfig 1 2 3 4 5 6 7 8 9 10 @Configuration public class RestTemplateConfig { @Bean @LoadBalanced public RestTemplate restTemplate () { return new RestTemplate (); } }
三个注册中心异同点 面试会问还用没用过别的注册中心,有什么区别,这种问题需要从 CAP 的角度回答。
C:Consistency(强一致性)
A:Availability(可用性)
P:Partition tolerance(分区容错性)
C 是所有节点数据一致 A 是节点始终能提供服务 P 是节点通信中断也能运行
最多只能同时较好的满足两个。
CAP理论的核心是 一个分布式系统不可能同时很好的满足一致性,可用性和分区容错性这三个需求,
因此,根据 CAP 原理将 NoSQL 数据库分成了满足 CA 原则、满足 CP 原则和满足 AP 原则三 大类:
CA - 单点集群,满足一致性,可用性的系统,通常在可扩展性上不太强大。
CA 系统的隐含假设:没有网络分区 + 节点不会挂(或单节点)
CP - 满足一致性,分区容忍必的系统,通常性能不是特别高。
AP - 满足可用性,分区容忍性的系统,通常可能对一致性要求低一些。
AP(Eureka)
CP(Zookeeper / Consul)
服务配置与刷新 分布式系统面临的配置问题 微服务意味着要将单体应用中的业务拆分成一个个子服务,每个服务的粒度相对较小,因此系统中会出现大量的服务。由于每个服务都需要必要的配置信息才能运行,所以一套集中式的、动态的配置管理设施是必不可少的。比如某些配置文件中的内容大部分都是相同的,只有个别的配置项不同。就拿数据库配置来说吧,如果每个微服务使用的技术栈都是相同的,则每个微服务中关于数据库的配置几乎都是相同的,有时候主机迁移了,我希望一次修改,处处生效。
当下我们每一个微服务自己带着一个application.yml,上百个配置文件的管理……/(ㄒoㄒ)/~~
官网说明
那么也就是说Consul 能管理服务配置也能进行服务注册中心
服务配置案例步骤 需求 通用全局配置信息,直接注册进Consul服务器,从Consul获取
既然从Consul获取自然要遵守Consul的配置规则要求
一次修改出处生效,一次修改广播生效
修改cloud-provider-payment8001 POM 1 2 3 4 5 6 7 8 9 <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 >
YML 配置规则说明
新增配置文件bootstrap.yml 是什么
applicaiton.yml是用户级的资源配置项
bootstrap.yml是系统级的,优先级更加高
Spring Cloud会创建一个“Bootstrap Context”,作为Spring应用的Application Context的父上下文。初始化的时候,Bootstrap Context负责从外部源加载配置属性并解析配置。这两个上下文共享一个从外部获取的Environment。
Bootstrap属性有高优先级,默认情况下,它们不会被本地配置覆盖。 Bootstrap context和Application Context有着不同的约定,所以新增了一个bootstrap.yml文件,保证Bootstrap Context和Application Context配置的分离。
application.yml文件改为bootstrap.yml,这是很关键的或者两者共存
因为bootstrap.yml是比application.yml先加载的。bootstrap.yml优先级高于application.yml
bootstrap.yml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 spring: application: name: cloud-payment-service cloud: consul: host: localhost port: 8500 discovery: service-name: ${spring.application.name} config: profile-separator: '-' format: YAML
application.yml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 server: port: 8001 spring: 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: root profiles: active: dev mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.bitzh.cloud.entities configuration: map-underscore-to-camel-case: true
consul服务器key/value配置填写 参考规则 1 2 3 # config/cloud-payment-service/data # /cloud-payment-service-dev/data # /cloud-payment-service-prod/data
创建config文件夹,以 / 结尾 必须以config开头,然后子文件夹就是以服务名为文件夹名来确认是配置哪个服务,不写就是默认default。
data就不再是文件夹了,而是key - value 的 key
controller 然后现在controller看看能不能读取到配置文件内容。
1 2 3 4 5 6 7 @Value("${server.port}") private String port;@GetMapping(value = "/pay/get/info") public String getInfoByConsul (@Value("$bitzh.info") String info) { return info + port; }
测试 访问 http://localhost:8001/pay/get/info 看能否读取到consul的配置信息。
动态刷新案例步骤 我们在 consul 的 配置分支修改了内容,马上访问,结果无效
步骤 @RefreshScope 主启动类添加 1 2 3 4 5 6 7 8 9 10 11 @SpringBootApplication @MapperScan("com.atguigu.cloud.mapper") @EnableDiscoveryClient @RefreshScope public class Main8001 { public static void main (String[] args) { SpringApplication.run(Main8001.class,args); } }
bootstrap.yml修改下等待时间 这个只是教学,实际别改
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 spring: application: name: cloud-payment-service cloud: consul: host: localhost port: 8500 discovery: service-name: ${spring.application.name} config: profile-separator: '-' format: YAML watch: wait-time: 1
默认已经有了默认值是 55 秒,这里改为1直接看效果
思考 截止到这,服务配置和动态刷新全部通过,假设我们重启Consul , 之前的配置还在吗?
就不在了,所以我们需要Consul配置持久化。后续再讲
5、LoadBalancer负载均衡服务调用 前身是Ribbon,目前进入维护模式 是什么 Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端 负载均衡的工具。
简单的说,Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载 均衡算法和服务调用 。Ribbon客户端组件提供一系列完善的配置项如连接超时,重试等。简单的说,就是在配置文件中列出Load Balancer(简称LB)后面所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随机连接等)去连接这些机器。我们很容易使用Ribbon实现自定义的负载均衡算法。
Ribbon未来替换方案 spring-cloud-loadbalancer
spring-cloud-loadbalancer概述 是什么 LB负载均衡(Load Balance)是什么
简单的说就是将用户的请求平摊的分配到多个服务上,从而达到系统的HA(高可用),常见的负载均衡有软件Nginx,LVS,硬件 F5等
spring-cloud-starter-loadbalancer组件是什么
Spring Cloud LoadBalancer是由SpringCloud官方提供的一个开源的、简单易用的客户端负载均衡器 ,它包含在SpringCloud-commons中用它来替换了以前的Ribbon组件。相比较于Ribbon,SpringCloud LoadBalancer不仅能够支持RestTemplate,还支持WebClient (WeClient是Spring Web Flux中提供的功能,可以实现响应式异步请求)
客户端负载 vs 服务端负载的区别 loadbalancer本地负载均衡客户端 VS Nginx服务端负载均衡区别
Nginx是服务器负载均衡,客户端所有请求都会交给nginx,然后由nginx实现转发请求,即负载均衡是由服务端实现的。
loadbalancer本地负载均衡,在调用微服务接口时候,会在注册中心上获取注册信息服务列表之后缓存到JVM本地,从而在本地实现RPC远程服务调用技术。
spring-cloud-loadbalancer负载均衡解析 负载均衡演示案例-理论
LoadBalancer 在工作时分成两步:
第一步 ,先选择ConsulServer从服务端查询并拉取服务列表,知道了它有多个服务(上图3个服务),这3个实现是完全一样的,
默认轮询调用谁都可以正常执行。类似生活中求医挂号,某个科室今日出诊的全部医生,客户端你自己选一个。
第二步 ,按照指定的负载均衡策略从server取到的服务注册列表中由客户端自己选择一个地址,所以LoadBalancer是一个客户端的 负载均衡器。
如果是nginx,压力都在服务器,需要接受所有的请求然后分发。
现在ribbion,压力在客户端也就是请求的人,而且压力分散,直接得知找的真正服务的地址,然后就能通过负载均衡调用对应的服务了。类似于去医院,选医生。
架构说明:80通过轮询负载访问 8001 / 8002 / 8003
负载均衡演示案例-实操 官网参考如何使用
按照8001拷贝后新建8002微服务 也是按照POM,YML,业务类,controller,主启动
启动Consul,将8001/8002启动后注册进微服务 1 2 3 consul agent -dev 将 8001 / 8002 启动后注册进微服务 出bug了,我们之前的配置完全消失了,没有持久化保存
Consul数据持久化配置 并且注册为Windows服务。
1 2 3 4 5 6 7 8 9 10 1、在一个D:\Study4It\SoftWare\consul\consul_1.17.0_windows_386目录下新建 D:\Study4It\SoftWare\consul\consul_1.17.0_windows_386\mydata 2、consul_start.bat内容信息 @echo.服务启动...... @echo off @sc create Consul binpath= "D:\Study4It\SoftWare\consul\consul_1.17.0_windows_386\consul.exe agent -server -ui -bind=127.0.0.1 -client=0.0.0.0 -bootstrap-expect 1 -data-dir D:\Study4It\SoftWare\consul\consul_1.17.0_windows_386\mydata" @net start Consul @sc config Consul start= AUTO @echo.Consul start is OK......success @pause
然后consul 就持久化了。
现在启动8001 和 8002 然后登录 consul 然后发现有2个实例
然后访问 http://localhost:8001/pay/get/info
订单80模块修改POM并注册进consul,新增LoadBalancer组件 POM
1 2 3 4 5 <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-loadbalancer</artifactId > </dependency >
Controller
1 2 3 4 5 @GetMapping(value = "/consumer/pay/get/info") private String getInfoByConsul () { return restTemplate.getForObject(PaymentSrv_URL + "/pay/get/info" , String.class); }
然后负载均衡的模式默认是轮询。
测试访问
负载均衡演示案例-小总结 编码使用DiscoverClient动态获取所有上线的服务列表 是怎么实现知道哪些服务上线的呢?
官网说有这个配置,那么我们就知道这个配置就是放所有服务节点的了。
然后我们从代码上测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @Resource private DiscoveryClient discoveryClient;@GetMapping("/consumer/discovery") public String discovery () { List<String> services = discoveryClient.getServices(); for (String element : services) { System.out.println(element); } System.out.println("===================================" ); List<ServiceInstance> instances = discoveryClient.getInstances("cloud-payment-service" ); for (ServiceInstance element : instances) { System.out.println(element.getServiceId()+"\t" +element.getHost()+"\t" +element.getPort()+"\t" +element.getUri()); } return instances.get(0 ).getServiceId()+":" +instances.get(0 ).getPort(); }
那么负载均衡的原理是什么:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 负载均衡算法:rest接口第几次请求数 % 服务器集群总数量 = 实际调用服务器位置下标 ,每次服务重启动后rest接口计数从1开始。 List<ServiceInstance> instances = discoveryClient.getInstances("cloud-payment-service"); 如: 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 如此类推......
负载均衡算法原理 默认算法是什么有几种? 默认是轮询,还有一个是随机。如果想要切换
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @Configuration @LoadBalancerClient( //下面的value值大小写一定要和consul里面的名字一样,必须一样 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); } }
6、OpenFeign服务接口调用 已经有LoadBalancer为什么还要学习OpenFeign?
两个都有道理日常用哪个?
是什么 openfeign是一个声明式的Web服务客户端
只需要创建一个Rest接口并在该接口上添加注解@FeignClient即可
OpenFeign基本上就是当前微服务之间调用的事实标准
能干嘛 可插拔的注解支持,包括Feign注解和JAX-RS注解 支持可插拔的HTTP编码器和解码器 支持Sentinel和他的Fallback 支持SpringCloudLoadBalancer的负载均衡 支持HTTP请求和响应的压缩 前面在使用SpringCloud LoadBalancer +RestTemplate时,利用RestTemplate对http请求的封装处理形成了一套模版化的调用方法。但是在实际开发中,由于对服务依赖的调用可能不止一处,往往一个接口会被多处调用,所以通常都会针对每个微服务自行封装一些客户端类来包装这些依赖服务的调用。
也就是说,每次调用都要创建一个配置类然后new RestTemplate,每个系统都散落这些new很不规范
所以,OpenFeign在此基础上做了进一步封装,由他来帮助我们定义和实现依赖服务接口的定义。在OpenFeign的实现下,我们只需创建一个接口并使用注解的方式来配置它(在一个微服务接口上面标注一个@FeignClient\ 注解即可),即可完成对服务提供方的接口绑定,统一对外暴露可以被调用的接口方法,大大简化和降低了调用客户端的开发量,也即由服务提供者给出调用接口清单,消费者直接通过OpenFeign调用即可,O(∩_∩)O。
OpenFeign同时还集成SpringCloud LoadBalancer
可以在使用OpenFeign时提供Http客户端的负载均衡,也可以集成阿里巴巴Sentinel来提供熔断、降级等功能 。而与SpringCloud LoadBalancer不同的是,通过OpenFeign只需要定义服务绑定接口且以声明式的方法,优雅而简单的实现了服务调用。
怎么玩 接口+注解 微服务Api接口 + @FeignClient注解标签
架构说明图
服务消费者80 → 调用含有@FeignClient注解的Api服务接口 → 服务提供者(8001/8002)
流程步骤 建Moudle 建立 cloud-consumer-feign-order80
改POM 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 <?xml version="1.0" encoding="UTF-8" ?> <project xmlns ="http://maven.apache.org/POM/4.0.0" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" > <modelVersion > 4.0.0</modelVersion > <parent > <groupId > com.bitzh</groupId > <artifactId > cloud2024</artifactId > <version > 1.0-SNAPSHOT</version > </parent > <groupId > com.bitzh.cloud</groupId > <artifactId > cloud-consumer-feign-order80</artifactId > <properties > <maven.compiler.source > 17</maven.compiler.source > <maven.compiler.target > 17</maven.compiler.target > <project.build.sourceEncoding > UTF-8</project.build.sourceEncoding > </properties > <dependencies > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-openfeign</artifactId > </dependency > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-consul-discovery</artifactId > </dependency > <dependency > <groupId > com.bitzh.cloud</groupId > <artifactId > cloud-api-commons</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-actuator</artifactId > </dependency > <dependency > <groupId > org.projectlombok</groupId > <artifactId > lombok</artifactId > <optional > true</optional > </dependency > <dependency > <groupId > cn.hutool</groupId > <artifactId > hutool-all</artifactId > </dependency > <dependency > <groupId > com.alibaba.fastjson2</groupId > <artifactId > fastjson2</artifactId > </dependency > <dependency > <groupId > org.springdoc</groupId > <artifactId > springdoc-openapi-starter-webmvc-ui</artifactId > </dependency > </dependencies > <build > <plugins > <plugin > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-maven-plugin</artifactId > </plugin > </plugins > </build > </project >
写YML 1 2 3 4 5 6 7 8 9 10 11 12 13 14 server: port: 80 spring: application: name: cloud-consumer-openfeign-order cloud: consul: host: localhost port: 8500 discovery: prefer-ip-address: true service-name: ${spring.application.name}
主启动 在主启动类上面配置 @EnableFeignClients表示开启OpenFeign功能并激活
1 2 3 4 5 6 7 8 9 10 @SpringBootApplication @EnableDiscoveryClient @EnableFeignClients public class MainOpenFeign80 { public static void main (String[] args) { SpringApplication.run(MainOpenFeign80.class, args); } }
业务类 按照架构图,服务提供者已经配好了,现在要配置公共模块了。
修改cloud-api-commons通用模块 在通用模块配置,就不用每个服务都配置一遍新建一个template了
引入openfeign依赖 1 2 3 4 5 <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-openfeign</artifactId > </dependency >
新建服务接口PayFeignApi,头上配置@FeignClient注解
参考微服务8001的Controller层,新建PayFeignApi接口 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 @FeignClient(value = "cloud-payment-service") public interface PayFeignApi { @PostMapping("/pay/add") public ResultData addPay (@RequestBody PayDTO payDTO) ; @GetMapping("/pay/get/{id}") public ResultData getPayInfo (@PathVariable("id") Integer id) ; @GetMapping(value = "/pay/get/info") public String mylb () ; }
也就是说80端口访问8001和8002不是访问本地的Ribbon了,而是访问Openfeign的这个接口然后在访问8001和8002了。
然后现在公共模块好了,现在修改80端口的controller
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 @RestController @Slf4j public class OrderController { @Resource private PayFeignApi payFeignApi; @PostMapping("/feign/pay/add") public ResultData addOrder (@RequestBody PayDTO payDTO) { System.out.println("第一步:模拟本地addOrder新增订单成功(省略sql操作),第二步:再开启addPay支付微服务远程调用" ); ResultData resultData = payFeignApi.addPay(payDTO); return resultData; } @GetMapping("/feign/pay/get/{id}") public ResultData getPayInfo (@PathVariable("id") Integer id) { System.out.println("-------支付微服务远程调用,按照id查询订单支付流水信息" ); ResultData resultData = payFeignApi.getPayInfo(id); return resultData; } @GetMapping(value = "/feign/pay/mylb") public String mylb () { return payFeignApi.mylb(); } }
测试 小总结
高级特性 1、OpenFeign超时控制 OpenFeign的版本要注意,版本会影响配置的。
在Spring Cloud微服务架构中,大部分公司都是利用OpenFeign进行服务间的调用,而比较简单的业务使用默认配置是不会有多大问题的,但是如果是业务比较复杂,服务要进行比较繁杂的业务计算,那后台很有可能会出现Read Timeout这个异常,因此定制化配置超时时间就有必要了 。
超时设置,故意设置超时演示出错情况,自己使坏写bug
故意设置超时错误 服务提供方8001写暂停62秒程序
为什么是62秒?
服务调用方80写好捕捉超时异常 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @GetMapping("/feign/pay/get/{id}") public ResultData getPayInfo (@PathVariable("id") Integer id) { System.out.println("-------支付微服务远程调用,按照id查询订单支付流水信息" ); ResultData resultData = null ; try { System.out.println("调用开始-----:" +DateUtil.now()); resultData = payFeignApi.getPayInfo(id); } catch (Exception e) { e.printStackTrace(); System.out.println("调用结束-----:" +DateUtil.now()); ResultData.fail(ReturnCodeEnum.RC500.getCode(),e.getMessage()); } return resultData; }
测试 然后看报错
结论 OpenFeign默认等待60秒钟,超过后报错。
官网解释+配置处理 默认OpenFeign客户端等待60秒钟,但是服务端处理超过规定时间会导致Feign客户端返回报错。
为了避免这样的情况,有时候我们需要设置Feign客户端的超时控制,默认60秒太长或者业务时间太短都不好
yml文件中开启配置:
connectTimeout 连接超时时间
readTimeout 请求处理超时时间
全局配置 1 2 3 4 5 6 7 8 9 10 spring: cloud: openfeign: client: config: default: connectTimeout: 3000 readTimeout: 3000
全部的配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 server: port: 80 spring: application: name: cloud-consumer-openfeign-order cloud: consul: host: localhost port: 8500 discovery: prefer-ip-address: true service-name: ${spring.application.name} openfeign: client: config: default: connectTimeout: 3000 readTimeout: 3000
指定配置 单个服务配置超时时间
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 spring: cloud: openfeign: client: config: default: connectTimeout: 4000 readTimeout: 4000 serviceC: connectTimeout: 2000 readTimeout: 2000
1 2 3 4 5 6 7 8 9 10 spring: cloud: openfeign: client: config: cloud-payment-service: connectTimeout: 5000 readTimeout: 5000
全部内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 server: port: 80 spring: application: name: cloud-consumer-openfeign-order cloud: consul: host: localhost port: 8500 discovery: prefer-ip-address: true service-name: ${spring.application.name} openfeign: client: config: cloud-payment-service: connectTimeout: 8000 readTimeout: 8000
2、OpenFeign重试机制 步骤 默认重试是关闭的,给了默认值
开启重试功能 新增配置类FeignConfig并修改Retryer配置 1 2 3 4 5 6 7 8 9 10 11 12 @Configuration public class FeignConfig { @Bean public Retryer myRetryer () { return new Retryer .Default(100 ,1 ,3 ); } }
如果你觉得效果不明显的同学,后续演示feign 日志功能的时候再演示,
目前控制台没有看到3次重试过程,只看到结果,正常的,正确的\ ,是feign的日志打印问题
3、OpenFeign默认HttpClient修改 是什么 OpenFeign中http client
如果不做特殊配置,OpenFeign默认使用JDK自带的HttpURLConnection发送HTTP请求,
由于默认HttpURLConnection没有连接池、性能和效率比较低,如果采用默认,性能上不是最牛B的,所以加到最大。
Apache HttpClient5替换 为什么
修改微服务feign90 FeignConfig类里面将Retryer属性修改为默认 1 2 3 4 5 6 7 8 9 10 @Configuration public class FeignConfig { @Bean public Retryer myRetryer () { return Retryer.NEVER_RETRY; } }
POM修改 1 2 3 4 5 6 7 8 9 10 11 12 <dependency > <groupId > org.apache.httpcomponents.client5</groupId > <artifactId > httpclient5</artifactId > <version > 5.3</version > </dependency > <dependency > <groupId > io.github.openfeign</groupId > <artifactId > feign-hc5</artifactId > <version > 13.1</version > </dependency >
配置 1 2 3 4 5 6 7 spring: cloud: openfeign: httpclient: hc5: enabled: true
全部配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 server: port: 80 spring: application: name: cloud-consumer-openfeign-order cloud: consul: host: localhost port: 8500 discovery: prefer-ip-address: true service-name: ${spring.application.name} openfeign: client: config: default: connectTimeout: 4000 readTimeout: 4000 httpclient: hc5: enabled: true
4、OpenFeign请求/响应压缩 是什么 对请求和响应进行GZIP压缩
Spring Cloud OpenFeign支持对请求和响应进行GZIP压缩,以减少通信过程中的性能损耗。
通过下面的两个参数设置,就能开启请求与相应的压缩功能:
spring.cloud.openfeign.compression.request.enabled=true
spring.cloud.openfeign.compression.response.enabled=true
细粒度化设置
对请求压缩做一些更细致的设置,比如下面的配置内容指定压缩的请求数据类型并设置了请求压缩的大小下限,
只有超过这个大小的请求才会进行压缩:
spring.cloud.openfeign.compression.request.enabled=true
spring.cloud.openfeign.compression.request.mime-types=text/xml,application/xml,application/json #触发压缩数据类型
spring.cloud.openfeign.compression.request.min-request-size=2048 #最小触发压缩的大小
配置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 server: port: 80 spring: application: name: cloud-consumer-openfeign-order cloud: consul: host: localhost port: 8500 discovery: prefer-ip-address: true service-name: ${spring.application.name} openfeign: client: config: default: connectTimeout: 4000 readTimeout: 4000 httpclient: hc5: enabled: true compression: request: enabled: true min-request-size: 2048 mime-types: text/xml,application/xml,application/json response: enabled: true
5、OpenFeign日志打印功能 是什么 Feign 提供了日志打印功能,我们可以通过配置来调整日志级别,
从而了解 Feign 中 Http 请求的细节,
说白了就是对Feign接口的调用情况进行监控和输出
日志级别 1 2 3 4 NONE:默认的,不显示任何日志; BASIC:仅记录请求方法、URL、响应状态码及执行时间; HEADERS:除了 BASIC 中定义的信息之外,还有请求和响应的头信息; FULL:除了 HEADERS 中定义的信息之外,还有请求和响应的正文及元数据。
配置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Configuration public class FeignConfig { @Bean public Retryer myRetryer () { return Retryer.NEVER_RETRY; } @Bean Logger.Level feignLoggerLevel () { return Logger.Level.FULL; } }
YML文件里需要开启日志的Feign客户端 公式(三段):logging.level + 含有@FeignClient注解的完整带包名的接口名+debug
1 2 3 4 5 6 7 8 logging: level: com: bitzh: cloud: apis: PayFeignApi: debug
后台日志查看 带着压缩调用
去掉压缩调用
补充实验,重试机制控制台看到3次过程 1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Configuration public class FeignConfig { @Bean public Retryer myRetryer () { return new Retryer .Default(100 ,1 ,3 ); } @Bean Logger.Level feignLoggerLevel () { return Logger.Level.FULL; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 server: port: 80 spring: application: name: cloud-consumer-openfeign-order cloud: consul: host: localhost port: 8500 discovery: prefer-ip-address: true service-name: ${spring.application.name} openfeign: client: config: default: connectTimeout: 2000 readTimeout: 2000 httpclient: hc5: enabled: true compression: request: enabled: true min-request-size: 2048 mime-types: text/xml,application/xml,application/json response: enabled: true logging: level: com: bitzh: cloud: apis: PayFeignApi: debug
最终YML
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 server: port: 80 spring: application: name: cloud-consumer-openfeign-order cloud: consul: host: localhost port: 8500 discovery: prefer-ip-address: true service-name: ${spring.application.name} openfeign: client: config: default: connectTimeout: 2000 readTimeout: 2000 httpclient: hc5: enabled: true compression: request: enabled: true min-request-size: 2048 mime-types: text/xml,application/xml,application/json response: enabled: true logging: level: com: atguigu: cloud: apis: PayFeignApi: debug
OpenFeign和Sentinel集成实现fallback服务降级 这个在后面的springcloud alibaba 篇章
7、CircuitBreaker断路器 Hystrix目前也进入维护模式 Hystrix是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖不可避免的会调用失败,比如超时、异常等,Hystrix能够保证在一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性。
了解一下即可,2024年了不再使用Hystrix
未来替代方案:Resilience4J
概述 分布式系统面临的问题 分布式系统面临的问题
复杂分布式体系结构中的应用程序有数十个依赖关系,每个依赖关系在某些时候将不可避免地失败。
服务雪崩
多个微服务之间调用的时候,假设微服务A调用微服务B和微服务C,微服务B和微服务C又调用其它的微服务,这就是所谓的“扇出”。如果扇出的链路上某个微服务的调用响应时间过长或者不可用,对微服务A的调用就会占用越来越多的系统资源,进而引起系统崩溃,所谓的“雪崩效应”.
对于高流量的应用来说,单一的后端依赖可能会导致所有服务器上的所有资源都在几秒钟内饱和。比失败更糟糕的是,这些应用程序还可能导致服务之间的延迟增加,备份队列,线程和其他系统资源紧张,导致整个系统发生更多的级联故障。这些都表示需要对故障和延迟进行隔离和管理,以便单个依赖关系的失败,不能取消整个应用程序或系统。
所以,
通常当你发现一个模块下的某个实例失败后,这时候这个模块依然还会接收流量,然后这个有问题的模块还调用了其他的模块,这样就会发生级联故障,或者叫雪崩。
需求 问题:
禁止服务雪崩故障
解决:
- 有问题的节点,快速熔断(快速返回失败处理或者返回默认兜底数据【服务降级】)。
“断路器”本身是一种开关装置,当某个服务单元发生故障之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个符合预期的、可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方无法处理的异常,这样就保证了服务调用方的线程不会被长时间、不必要地占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩。
如何解决问题 服务熔断 服务降级 服务限流 服务限时 服务预热 接近实时的监控 兜底的处理动作 一句话,出故障了“保险丝”跳闸,别把整个家给烧了,😄
Circuit Breaker是什么 这是一套规范,抽象实现,支持用Resilience4J实现和Spring Retry。这里我们用Resilience4J。
实现原理 CircuitBreaker的目的是保护分布式系统免受故障和异常,提高系统的可用性和健壮性。
当一个组件或服务出现故障时,CircuitBreaker会迅速切换到开放OPEN状态(保险丝跳闸断电),阻止请求发送到该组件或服务从而避免更多的请求发送到该组件或服务。这可以减少对该组件或服务的负载,防止该组件或服务进一步崩溃,并使整个系统能够继续正常运行。同时,CircuitBreaker还可以提高系统的可用性和健壮性,因为它可以在分布式系统的各个组件之间自动切换,从而避免单点故障的问题。
一句话 Circuit Breaker只是一套规范和接口,落地实现者是Resilience4J
Resilience4J 是什么
能干嘛
缓存就用redis,重试用guava(单点重试)OpenFeign(跨节点重试),超时处理OpenFeign里面内置了。
案例实战 熔断 CircuitBreaker(服务熔断 + 服务降级) 断路器3大状态
断路器3大状态之间的转换
断路器所有配置参数参考
配置项名
说明
failure-rate-threshold
以百分比配置失败率峰值
sliding-window-type
断路器的滑动窗口期类型,可以基于“次数”(COUNT_BASED)或者“时间”(TIME_BASED)进行熔断,默认是 COUNT_BASED
sliding-window-size
若 COUNT_BASED,则10次调用中有50%失败(即5次)打开熔断断路器;若为 TIME_BASED 则,此时还有额外的两个设置属性,含义为:在N秒内(sliding-window-size)100%(slow-call-rate-threshold)的请求超过N秒(slow-call-duration-threshold)打开断路器
slowCallRateThreshold
以百分比的方式配置,断路器把调用时间大于 slowCallDurationThreshold 的调用视为慢调用,当慢调用比例大于等于峰值时,断路器开启,并进入服务降级
slowCallDurationThreshold
配置调用时间的峰值,高于该峰值的视为慢调用
permitted-number-of-calls-in-half-open-state
运行断路器在 HALF_OPEN 状态下时进行 N 次调用,如果故障或慢速调用仍然高于阈值,断路器再次进入打开状态
minimum-number-of-calls
在每个滑动窗口期样本数,配置断路器计算错误率或者慢调用率的最小调用数。比如设置为5意味着,在计算故障率之前,必须至少调用5次。如果只记录了4次,即使4次都失败了,断路器也不会进入到打开状态
wait-duration-in-open-state
从 OPEN 到 HALF_OPEN 状态需要等待的时间
熔断+降级案例需求说明 6次访问中当执行方法的失败率达到50% 时CircuitBreaker 将进入开启OPEN 状态( 保险丝跳闸断电) 拒绝所有请求。等待 5秒后, CircuitBreaker将自动从开启 OPEN状态过渡到半开 HALF_OPEN状态,允许一些请求通过以测试服务是否恢复正常。
如还是异常CircuitBreaker 将重新进入开启OPEN 状态;如正常将进入关闭CLOSE 闭合状态恢复正常处理请求。
具体时间和频次等属性见具体实际案例,这里只是作为case举例讲解
判定方式有按照次数有按照时间,也就是说6次3次失败,或者6秒中3秒失败。
按照COUNT_BASED 计数的滑动窗口 修改cloud-provider-payment8001 新建PayCircuitController
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @RestController public class PayCircuitController { @GetMapping(value = "/pay/circuit/{id}") public String myCircuit (@PathVariable("id") Integer id) { if (id == -4 ) throw new RuntimeException ("----circuit id 不能负数" ); if (id == 9999 ){ try { TimeUnit.SECONDS.sleep(5 ); } catch (InterruptedException e) { e.printStackTrace(); } } return "Hello, circuit! inputId: " +id+" \t " + IdUtil.simpleUUID(); } }
修改对外暴露的接口PayFeignApi接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 @FeignClient(value = "cloud-payment-service") public interface PayFeignApi { @PostMapping("/pay/add") public ResultData addPay (@RequestBody PayDTO payDTO) ; @GetMapping("/pay/get/{id}") public ResultData getPayInfo (@PathVariable("id") Integer id) ; @GetMapping(value = "/pay/get/info") public String mylb () ; @GetMapping(value = "/pay/circuit/{id}") public String myCircuit (@PathVariable("id") Integer id) ; }
保险丝装在调用者,所以修改cloud-consumer-feign-order80
1 2 3 4 5 6 7 8 9 10 <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-circuitbreaker-resilience4j</artifactId > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-aop</artifactId > </dependency >
改YML
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 server: port: 80 spring: application: name: cloud-consumer-openfeign-order cloud: consul: host: localhost port: 8500 discovery: prefer-ip-address: true service-name: ${spring.application.name} openfeign: client: config: default: connectTimeout: 20000 readTimeout: 20000 httpclient: hc5: enabled: true compression: request: enabled: true min-request-size: 2048 mime-types: text/xml,application/xml,application/json response: enabled: true circuitbreaker: enabled: true group: enabled: true resilience4j: circuitbreaker: configs: default: failureRateThreshold: 50 slidingWindowType: COUNT_BASED slidingWindowSize: 6 minimumNumberOfCalls: 6 automaticTransitionFromOpenToHalfOpenEnabled: true waitDurationInOpenState: 5s permittedNumberOfCallsInHalfOpenState: 2 recordExceptions: - java.lang.Exception instances: cloud-payment-service: baseConfig: default
新建OrderCircuitController
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @RestController public class OrderCircuitController { @Resource private PayFeignApi payFeignApi; @GetMapping(value = "/feign/pay/circuit/{id}") @CircuitBreaker(name = "cloud-payment-service", fallbackMethod = "myCircuitFallback") public String myCircuitBreaker (@PathVariable("id") Integer id) { return payFeignApi.myCircuit(id); } public String myCircuitFallback (Integer id,Throwable t) { return "myCircuitFallback,系统繁忙,请稍后再试-----/(ㄒoㄒ)/~~" ; } }
测试
访问错误次数多了,就算范文正常的也是错误的响应过5秒后恢复。
按照TIME_BASED 时间的滑动窗口
原理:不用深究,然后为了计算,每个桶里面有三个整形,失败调用数,慢调用数,总调用数、还有一个long类型变量,存储所有调用的响应时间。
修改cloud-consumer-feign-order80 写YML
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 server: port: 80 spring: application: name: cloud-consumer-openfeign-order cloud: consul: host: localhost port: 8500 discovery: prefer-ip-address: true service-name: ${spring.application.name} openfeign: client: config: default: connectTimeout: 20000 readTimeout: 20000 httpclient: hc5: enabled: true compression: request: enabled: true min-request-size: 2048 mime-types: text/xml,application/xml,application/json response: enabled: true circuitbreaker: enabled: true group: enabled: true logging: level: com: atguigu: cloud: apis: PayFeignApi: debug resilience4j: timelimiter: configs: default: timeout-duration: 10s circuitbreaker: configs: default: failureRateThreshold: 50 slowCallDurationThreshold: 2s slowCallRateThreshold: 30 slidingWindowType: TIME_BASED slidingWindowSize: 2 minimumNumberOfCalls: 2 permittedNumberOfCallsInHalfOpenState: 2 waitDurationInOpenState: 5s recordExceptions: - java.lang.Exception instances: cloud-payment-service: baseConfig: default
为了避免影响实验效果,记得关闭FeignConfig自己写的重试3次
测试(慢查询) 一次超时,一次正常访问,同时进行
小总结 断路器开启或者关闭的条件
当满足一定的峰值和失败率达到一定条件后,断路器将会进入OPEN状态(保险丝跳闸),服务熔断
当OPEN的时候,所有请求都不会调用主业务逻辑方法,而是直接走fallbackmethod兜底背锅方法,服务降级。
一段时间之后,这个时候断路器会从OPEN进入到HALF_OPEN半开状态,会放几个请求过去探探链路是否通?
如果成功,断路器会关闭CLOSE(类似保险丝闭合,恢复可用)
如果失败,继续开启,重复上述。
不要混用,推荐按照调用次数。
隔离(BulkHead) 是什么 bulkhead(船的)舱壁/(飞机的)隔板
板来自造船行业,床仓内部一般会分成很多小隔舱,一旦一个隔舱漏水因为隔板的存在而不至于影响其它隔舱和整体船。
用来限并发的。
能干嘛 一来隔离 & 负载保护:用来限制对于下游服务的最大并发数量的限制
Resilience4J提供了如下两种隔离的实现方式,可以限制并发执行的数量 实现SemaphoreBulkhead(信号量舱壁) 这个使用了信号量
信号量原理
基本上就是我们JUC信号灯内容的同样思想
信号量舱壁(SemaphoreBulkhead)原理
当信号量有空闲时,进入系统的请求会直接获取信号量并开始业务处理。
当信号量全被占用时,接下来的请求将会进入阻塞状态,SemaphoreBulkhead提供了一个阻塞计时器,
如果阻塞状态的请求在阻塞计时内无法获取到信号量则系统会拒绝这些请求。
若请求在阻塞计时内获取到了信号量,那将直接获取信号量并执行相应的业务处理。
源码分析
cloud-provider-payment8001支付微服务
修改PayCircuitController
1 2 3 4 5 6 7 8 9 10 11 12 13 @GetMapping(value = "/pay/bulkhead/{id}") public String myBulkhead (@PathVariable("id") Integer id) { if (id == -4 ) throw new RuntimeException ("----bulkhead id 不能-4" ); if (id == 9999 ) { try { TimeUnit.SECONDS.sleep(5 ); } catch (InterruptedException e) { e.printStackTrace(); } } return "Hello, bulkhead! inputId: " +id+" \t " + IdUtil.simpleUUID(); }
PayFeignApi接口新增舱壁api方法
1 2 3 4 5 6 7 @GetMapping(value = "/pay/bulkhead/{id}") public String myBulkhead (@PathVariable("id") Integer id) ;
修改 cloud-consumer-feign-order80
POM
1 2 3 4 5 <dependency > <groupId > io.github.resilience4j</groupId > <artifactId > resilience4j-bulkhead</artifactId > </dependency >
YML
示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 server: port: 80 spring: application: name: cloud-consumer-openfeign-order cloud: consul: host: localhost port: 8500 discovery: prefer-ip-address: true service-name: ${spring.application.name} openfeign: client: config: default: connectTimeout: 20000 readTimeout: 20000 httpclient: hc5: enabled: true compression: request: enabled: true min-request-size: 2048 mime-types: text/xml,application/xml,application/json response: enabled: true circuitbreaker: enabled: true group: enabled: true logging: level: com: bitzh: cloud: apis: PayFeignApi: debug resilience4j: bulkhead: configs: default: maxConcurrentCalls: 2 maxWaitDuration: 1s instances: cloud-payment-service: baseConfig: default timelimiter: configs: default: timeout-duration: 20s
业务类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @GetMapping(value = "/feign/pay/bulkhead/{id}") @Bulkhead(name = "cloud-payment-service",fallbackMethod = "myBulkheadFallback",type = Bulkhead.Type.SEMAPHORE) public String myBulkhead (@PathVariable("id") Integer id) { return payFeignApi.myBulkhead(id); } public String myBulkheadFallback (Throwable t) { return "myBulkheadFallback,隔板超出最大数量限制,系统繁忙,请稍后再试-----/(ㄒoㄒ)/~~" ; }
@Bulkhead Bulkhead.Type.SEMAPHORE
实现FixedThreadPoolBulkhead(固定线程池舱壁) 这个使用有界队列和固定大小线程池
概述
基本上就是我们JUC-线程池内容的同样思想
固定线程池舱壁(FixedThreadPoolBulkhead)
FixedThreadPoolBulkhead的功能与SemaphoreBulkhead一样也是用于限制并发执行的次数 的,但是二者的实现原理存在差别而且表现效果也存在细微的差别。FixedThreadPoolBulkhead使用一个固定线程池和一个等待队列来实现舱壁。
当线程池中存在空闲时,则此时进入系统的请求将直接进入线程池开启新线程或使用空闲线程来处理请求。
当线程池中无空闲时时,接下来的请求将进入等待队列,
若等待队列仍然无剩余空间时接下来的请求将直接被拒绝,
在队列中的请求等待线程池出现空闲时,将进入线程池进行业务处理。
另外:ThreadPoolBulkhead只对CompletableFuture方法有效,所以我们必创建返回CompletableFuture类型的方法
信号量舱壁就是利用调用方的线程,对并发量限制,固定线程池舱壁就是服务器里面专属线程池,和调用方线程隔离
想要理解具体源码,去JUC里面看了
这里了解如何使用
修改cloud-consumer-feign-order80
POM
1 2 3 4 5 <dependency > <groupId > io.github.resilience4j</groupId > <artifactId > resilience4j-bulkhead</artifactId > </dependency >
YML
这里讲解原理,也就是说线程进入CorePool如果多了就进入到阻塞队列,如果阻塞队列满了就扩Pool,然后到maximumPool。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 server: port: 80 spring: application: name: cloud-consumer-openfeign-order cloud: consul: host: localhost port: 8500 discovery: prefer-ip-address: true service-name: ${spring.application.name} openfeign: client: config: default: connectTimeout: 20000 readTimeout: 20000 httpclient: hc5: enabled: true compression: request: enabled: true min-request-size: 2048 mime-types: text/xml,application/xml,application/json response: enabled: true circuitbreaker: enabled: true logging: level: com: bitzh: cloud: apis: PayFeignApi: debug resilience4j: timelimiter: configs: default: timeout-duration: 10s thread-pool-bulkhead: configs: default: core-thread-pool-size: 1 max-thread-pool-size: 1 queue-capacity: 1 instances: cloud-payment-service: baseConfig: default
order80的controller
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @GetMapping(value = "/feign/pay/bulkhead/{id}") @Bulkhead(name = "cloud-payment-service",fallbackMethod = "myBulkheadPoolFallback",type = Bulkhead.Type.THREADPOOL) public CompletableFuture<String> myBulkheadTHREADPOOL (@PathVariable("id") Integer id) { System.out.println(Thread.currentThread().getName()+"\t" +"enter the method!!!" ); try { TimeUnit.SECONDS.sleep(3 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"\t" +"exist the method!!!" ); return CompletableFuture.supplyAsync(() -> payFeignApi.myBulkhead(id) + "\t" + " Bulkhead.Type.THREADPOOL" ); } public CompletableFuture<String> myBulkheadPoolFallback (Integer id,Throwable t) { return CompletableFuture.supplyAsync(() -> "Bulkhead.Type.THREADPOOL,系统繁忙,请稍后再试-----/(ㄒoㄒ)/~~" ); }
限流 限流 :限制 “单位时间内进入大楼的总人数”,防止大楼入口拥堵。
舱壁 :把大楼分成多个独立房间,限制 “每个房间的人数”,防止一个房间爆满影响其他房间。
限流 就是限制最大访问流量。系统能提供的最大并发是有限的,同时来的请求又太多,就需要限流。
比如商城秒杀业务,瞬时大量请求涌入,服务器忙不过就只好排队限流了,和去景点排队买票和去医院办理业务排队等号道理相同。
所谓限流,就是通过对并发访问/请求进行限速,或者对一个时间窗口内的请
求进行限速,以保护应用系统,一旦达到限制速率则可以拒绝服务、排队或
等待、降级等处理。
限流是频率控制
常见的限流算法 漏斗算法 一个固定容量的漏桶,按照设定常量固定速率流出水滴,类似医院打吊针,不管你源头流量多大,我设定匀速流出。 如果流入水滴超出了桶的容量,则流入的水滴将会溢出了(被丢弃),而漏桶容量是不变的。
缺点: 这里有两个变量,一个是桶的大小,支持流量突发增多时可以存多少的水(burst),另一个是水桶漏洞的大小(rate)。因为漏桶的漏出速率是固定的参数,所以,即使网络中不存在资源冲突(没有发生拥塞),漏桶算法也不能使流突发(burst)到端口速率。因此,漏桶算法对于存在突发特性的流量来说缺乏效率 。
漏桶算法(Leaky Bucket)在处理突发(bursty)流量时,无法灵活应对短时间内的大量请求,容易造成不必要的拒绝或延迟,从而降低系统对合法突发流量的利用率和响应效率。
令牌桶算法(重点) SpringCloud默认使用该算法
滚动时间窗口 允许固定数量的请求进入(比如1秒取4个数据相加,超过25值就over)超过数量就拒绝或者排队,等下一个时间段进入。
由于是在一个时间间隔内进行限制,如果用户在上个时间间隔结束前请求(但没有超过限制),同时在当前时间间隔刚开始请求(同样没超过限制),在各自的时间间隔内,这些请求都是正常的。下图统计了3次,but……
缺点:间隔临界的一段时间内的请求就会超过系统限制,可能导致系统被压垮
假如设定1分钟最多可以请求100次某个接口,如12:00:00-12:00:59时间段内没有数据请求但12:00:59-12:01:00时间段内突然并发100次请求,紧接着瞬间跨入下一个计数周期计数器清零;在12:01:00-12:01:01内又有100次请求。那么也就是说在时间临界点左右可能同时有2倍的峰值进行请求,从而造成后台处理请求加倍过载 的bug,导致系统运营能力不足,甚至导致系统崩溃,/(ㄒoㄒ)/~~
1 2 3 4 5 假设限流是 100 人/分钟。 00:59 秒: 100 人冲进来。滚动窗口保安看了一眼表:“嗯,这一分钟正好 100 人,准入!” 01:00 秒: 窗口重置,计数器清零。 01:01 秒: 又冲进来 100 人。滚动窗口保安又看了一眼表:“这一分钟刚开始,现在才 100 人,准入!” 结果: 保安在 2 秒内放进了 200 人。后端服务器直接被压垮了。
滑动时间窗口 滑动时间窗口(sliding time window)
顾名思义,该时间窗口是滑动的。所以,从概念上讲,这里有两个方面的概念需要理解:
- 窗口:需要定义窗口的大小
- 滑动:需要定义在窗口中滑动的大小,但理论上讲滑动的大小不能超过窗口大小
滑动窗口算法是把固定时间片进行划分并且随着时间移动,移动方式为开始时间点变为时间列表中的第2个时间点,结束时间点增加一个时间点,
不断重复,通过这种方式可以巧妙的避开计数器的临界点的问题。下图统计了5次
1 2 3 4 5 6 7 滑动窗口:那个“时刻警惕”的保安 假设限流依然是 100 人/分钟,但我们用滑动窗口(每秒移动一次)。 00:59 秒: 100 人冲进来。保安看了一下过去 60 秒(00:00-01:00),正好 100 人,准入。 01:00 秒: 保安的窗口移动了,现在看的是(00:01-01:01)。 01:01 秒: 又有 1 人想冲进来。 关键时刻: 保安低头看表,发现 00:01 到 01:01 这过去的一分钟里,已经有 100 人了! 保安反应: “停!这 1 分钟名额满了,你不能进去!”(直接丢弃请求或报 429 错误)。
实战 cloud-provider-payment8001支付微服务 修改PayCircuitController新增myRatelimit方法
1 2 3 4 5 6 @GetMapping(value = "/pay/ratelimit/{id}") public String myRatelimit (@PathVariable("id") Integer id) { return "Hello, myRatelimit欢迎到来 inputId: " +id+" \t " + IdUtil.simpleUUID(); }
PayFeignApi接口新增限流api方法
1 2 3 4 5 6 7 @GetMapping(value = "/pay/ratelimit/{id}") public String myRatelimit (@PathVariable("id") Integer id) ;
修改 cloud-consumer-feign-order80
POM
1 2 3 4 5 <dependency > <groupId > io.github.resilience4j</groupId > <artifactId > resilience4j-ratelimiter</artifactId > </dependency >
YML
1 2 3 4 5 6 7 8 9 10 11 resilience4j: ratelimiter: configs: default: limitForPeriod: 2 limitRefreshPeriod: 1s timeout-duration: 1 instances: cloud-payment-service: baseConfig: default
order的controller
1 2 3 4 5 6 7 8 9 10 @GetMapping(value = "/feign/pay/ratelimit/{id}") @RateLimiter(name = "cloud-payment-service",fallbackMethod = "myRatelimitFallback") public String myBulkhead (@PathVariable("id") Integer id) { return payFeignApi.myRatelimit(id); } public String myRatelimitFallback (Integer id,Throwable t) { return "你被限流了,禁止访问/(ㄒoㄒ)/~~" ; }
8、Sleuth(Micrometer)+ZipKin分布式链路追踪 ❌️Sleuth目前也进入维护模式 Sleuth未来替换方案:Micrometer Tracing
分布式链路追踪概述 为什么会出现这个技术,需要解决什么问题
问题
在微服务框架中,一个由客户端发起的请求在后端系统中会经过多个不同的的服务节点调用来协同产生最后的请求结果,每一个前段请求都会形成一条复杂的分布式服务调用链路,链路中的任何一环出现高延时或错误都会引起整个请求最后的失败。
在分布式与微服务场景下,我们需要解决如下问题:
在大规模分布式与微服务集群下,如何实时观测系统的整体调用链路情况。
在大规模分布式与微服务集群下,如何快速发现并定位到问题。
在大规模分布式与微服务集群下,如何尽可能精确的判断故障对系统的影响范围与影响程度。
在大规模分布式与微服务集群下,如何尽可能精确的梳理出服务之间的依赖关系,并判断出服务之间的依赖关系是否合理。
在大规模分布式与微服务集群下,如何尽可能精确的分析整个系统调用链路的性能与瓶颈点。
在大规模分布式与微服务集群下,如何尽可能精确的分析系统的存储瓶颈与容量规划。
上述问题就是我们的落地议题答案:
分布式链路追踪技术要解决的问题,分布式链路追踪(Distributed Tracing),就是将一次分布式请求还原成调用链路,进行日志记录,性能监控并将一次分布式请求的调用情况集中展示。比如各个服务节点上的耗时、请求具体到达哪台机器上、每个服务节点的请求状态等等。
Micrometer Tracing zipkin是啥 Spring Cloud Sleuth (micrometer)提供了一套完整的分布式链路追踪解决方案且兼容支持了zipkin展现
Sleuth负责收集,zipkin负责展现
小总结 将一次分布式请求还原成调用链路,进行日志记录和性能监控,并将一次分布式请求的调用情况集中web展示。
行业内比较成熟的其他分布式链路追踪技术解决方案
如果不想用 zipkin 推荐用 skywalking
分布式链路追踪原理 假定3个微服务调用链路
Service1 调用 2,2调用3和4
上一步的完整的调用链路
1 2 3 那么一条链路追踪会在每个服务调用的时候加上Trace ID 和 Span ID 链路通过TraceId唯一标识, Span标识发起的请求信息,各span通过parent id 关联起来 (Span:表示调用链路来源,通俗的理解span就是一次请求信息)
上面都是请求,下面都是响应。
简化图:
一条链路通过Trace Id唯一标识,Span标识发起的请求信息,各span通过parent id 关联起来
节点序号
Span ID
Parent ID
节点描述
涉及服务
1
A
null
Service 1 接收到请求
Service 1
2
B
A
Service 1 发送请求到 Service 2 返回响应给 Service 1 的过程
Service 1、Service 2
3
C
B
Service 2 的中间解决过程
Service 2
4
D
C
Service 2 发送请求到 Service 3 返回响应给 Service 2 的过程
Service 2、Service 3
5
E
D
Service 3 的中间解决过程
Service 3
6
F
C
Service 3 发送请求到 Service 4 返回响应给 Service 3 的过程
Service 3、Service 4
7
G
F
Service 4 的中间解决过程
Service 4
Zipkin Zipkin是一种分布式链路跟踪系统图形化的工具,Zipkin 是 Twitter 开源的分布式跟踪系统,能够收集微服务运行过程中的实时调用链路信息,并能够将这些调用链路信息展示到Web图形化界面上供开发人员分析,开发人员能够从ZipKin中分析出调用链路中的性能瓶颈,识别出存在问题的应用程序,进而定位问题和解决问题。
下载+安装+运行 下载职业 https://zipkin.io/pages/quickstart
支持3个方式
2023.12,版本名称
zipkin-server-3.0.0-rc0-exec.jar
1 2 3 4 5 # 方式1:直接下载指定版本JAR包(推荐,跳过默认脚本的版本自动选择) curl -sSL -o zipkin-server-3.0.0-rc0-exec.jar https://repo1.maven.org/maven2/io/zipkin/zipkin-server/3.0.0-rc0/zipkin-server-3.0.0-rc0-exec.jar # 方式2:启动指定版本的Zipkin java -jar zipkin-server-3.0.0-rc0-exec.jar
Micrometer+ZipKin搭建链路监控案例步骤 Micrometer 数据采样
ZipKin 图形展示
步骤 总体POM父工程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 <dependency > <groupId > io.micrometer</groupId > <artifactId > micrometer-tracing-bom</artifactId > <version > ${micrometer-tracing.version}</version > <type > pom</type > <scope > import</scope > </dependency > <dependency > <groupId > io.micrometer</groupId > <artifactId > micrometer-tracing</artifactId > <version > ${micrometer-tracing.version}</version > </dependency > <dependency > <groupId > io.micrometer</groupId > <artifactId > micrometer-tracing-bridge-brave</artifactId > <version > ${micrometer-tracing.version}</version > </dependency > <dependency > <groupId > io.micrometer</groupId > <artifactId > micrometer-observation</artifactId > <version > ${micrometer-observation.version}</version > </dependency > <dependency > <groupId > io.github.openfeign</groupId > <artifactId > feign-micrometer</artifactId > <version > ${feign-micrometer.version}</version > </dependency > <dependency > <groupId > io.zipkin.reporter2</groupId > <artifactId > zipkin-reporter-brave</artifactId > <version > ${zipkin-reporter-brave.version}</version > </dependency >
这些 jar 包什么意思?
由于Micrometer Tracing是一个门面工具自身并没有实现完整的链路追踪系统,具体的链路追踪另外需要引入的是第三方链路追踪系统的依赖:
序号
组件名称
说明
1
micrometer-tracing-bom
导入链路追踪的版本中心(Bill of Materials),用于统一管理 Micrometer Tracing 相关依赖的版本,实现体系化的依赖管理。
2
micrometer-tracing
提供分布式链路追踪的核心抽象和基础能力,是 Micrometer 生态中用于跟踪(Tracing)的主模块。
3
micrometer-tracing-bridge-brave
Micrometer 的桥接模块,用于与分布式追踪工具 Brave 集成。Brave 是一个开源的分布式追踪库,通过在请求头中传递“跟踪上下文”(Trace Context),记录请求在各服务间的流转路径、耗时等信息,实现端到端的链路追踪。
4
micrometer-observation
基于 Micrometer 的观测(Observation)模块,提供统一的编程模型,用于收集指标(Metrics)、日志(Logs)和追踪(Traces)等可观测性数据。
5
feign-micrometer
为 Feign HTTP 客户端提供的 Micrometer 集成模块,自动收集 Feign 发起的远程调用的指标和追踪数据(如请求次数、延迟、成功率等)。
6
zipkin-reporter-brave
将 Brave 采集的分布式追踪数据上报到 Zipkin 可视化追踪系统的报告器(Reporter)库,实现追踪数据的集中存储与展示。
补充包:spring-boot-starter-actuator SpringBoot框架的一个模块用于监视和管理应用程序
若是没有使用父工程的要添加开启spring boot监控和管理工具的依赖
总POM
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 搜索 便笺 <?xml version="1.0" encoding="UTF-8" ?> <project xmlns ="http://maven.apache.org/POM/4.0.0" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" > <modelVersion > 4.0.0</modelVersion > <groupId > com.atguigu.cloud</groupId > <artifactId > mscloudV6</artifactId > <version > 1.0-SNAPSHOT</version > <packaging > pom</packaging > <modules > <module > cloud-provider-payment8001</module > <module > cloud-consumer-order80</module > <module > cloud-api-commons</module > <module > cloud-provider-payment8002</module > <module > cloud-consumer-feign-order80</module > </modules > <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 > <lombok.version > 1.18.26</lombok.version > <druid.version > 1.1.20</druid.version > <mybatis.springboot.version > 3.0.2</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.41</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 > <micrometer-tracing.version > 1.2.0</micrometer-tracing.version > <micrometer-observation.version > 1.12.0</micrometer-observation.version > <feign-micrometer.version > 12.5</feign-micrometer.version > <zipkin-reporter-brave.version > 2.17.0</zipkin-reporter-brave.version > </properties > <dependencyManagement > <dependencies > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-parent</artifactId > <version > ${spring.boot.version}</version > <type > pom</type > <scope > import</scope > </dependency > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-dependencies</artifactId > <version > ${spring.cloud.version}</version > <type > pom</type > <scope > import</scope > </dependency > <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 > <dependency > <groupId > org.mybatis.spring.boot</groupId > <artifactId > mybatis-spring-boot-starter</artifactId > <version > ${mybatis.springboot.version}</version > </dependency > <dependency > <groupId > mysql</groupId > <artifactId > mysql-connector-java</artifactId > <version > ${mysql.version}</version > </dependency > <dependency > <groupId > com.alibaba</groupId > <artifactId > druid-spring-boot-starter</artifactId > <version > ${druid.version}</version > </dependency > <dependency > <groupId > tk.mybatis</groupId > <artifactId > mapper</artifactId > <version > ${mapper.version}</version > </dependency > <dependency > <groupId > javax.persistence</groupId > <artifactId > persistence-api</artifactId > <version > ${persistence-api.version}</version > </dependency > <dependency > <groupId > com.alibaba.fastjson2</groupId > <artifactId > fastjson2</artifactId > <version > 2.0.40</version > </dependency > <dependency > <groupId > org.springdoc</groupId > <artifactId > springdoc-openapi-starter-webmvc-ui</artifactId > <version > ${swagger3.version}</version > </dependency > <dependency > <groupId > cn.hutool</groupId > <artifactId > hutool-all</artifactId > <version > ${hutool.version}</version > </dependency > <dependency > <groupId > org.projectlombok</groupId > <artifactId > lombok</artifactId > <version > ${lombok.version}</version > <optional > true</optional > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-test</artifactId > <version > ${spring.boot.test.version}</version > <scope > test</scope > </dependency > <dependency > <groupId > io.micrometer</groupId > <artifactId > micrometer-tracing-bom</artifactId > <version > ${micrometer-tracing.version}</version > <type > pom</type > <scope > import</scope > </dependency > <dependency > <groupId > io.micrometer</groupId > <artifactId > micrometer-tracing</artifactId > <version > ${micrometer-tracing.version}</version > </dependency > <dependency > <groupId > io.micrometer</groupId > <artifactId > micrometer-tracing-bridge-brave</artifactId > <version > ${micrometer-tracing.version}</version > </dependency > <dependency > <groupId > io.micrometer</groupId > <artifactId > micrometer-observation</artifactId > <version > ${micrometer-observation.version}</version > </dependency > <dependency > <groupId > io.github.openfeign</groupId > <artifactId > feign-micrometer</artifactId > <version > ${feign-micrometer.version}</version > </dependency > <dependency > <groupId > io.zipkin.reporter2</groupId > <artifactId > zipkin-reporter-brave</artifactId > <version > ${zipkin-reporter-brave.version}</version > </dependency > </dependencies > </dependencyManagement > </project >
有时候A调用B,有时候B调用A所以最好两个都加上。但是这次是单向,80 调用 8001
服务提供者8001
POM
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 <?xml version="1.0" encoding="UTF-8" ?> <project xmlns ="http://maven.apache.org/POM/4.0.0" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" > <modelVersion > 4.0.0</modelVersion > <parent > <groupId > com.bitzh</groupId > <artifactId > cloud2024</artifactId > <version > 1.0-SNAPSHOT</version > </parent > <artifactId > cloud-provider-payment8001</artifactId > <properties > <maven.compiler.source > 17</maven.compiler.source > <maven.compiler.target > 17</maven.compiler.target > <project.build.sourceEncoding > UTF-8</project.build.sourceEncoding > </properties > <dependencies > <dependency > <groupId > io.micrometer</groupId > <artifactId > micrometer-tracing</artifactId > </dependency > <dependency > <groupId > io.micrometer</groupId > <artifactId > micrometer-tracing-bridge-brave</artifactId > </dependency > <dependency > <groupId > io.micrometer</groupId > <artifactId > micrometer-observation</artifactId > </dependency > <dependency > <groupId > io.github.openfeign</groupId > <artifactId > feign-micrometer</artifactId > </dependency > <dependency > <groupId > io.zipkin.reporter2</groupId > <artifactId > zipkin-reporter-brave</artifactId > </dependency > <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 > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-consul-discovery</artifactId > </dependency > <dependency > <groupId > com.bitzh.cloud</groupId > <artifactId > cloud-api-commons</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-actuator</artifactId > </dependency > <dependency > <groupId > com.alibaba</groupId > <artifactId > druid-spring-boot-starter</artifactId > </dependency > <dependency > <groupId > org.springdoc</groupId > <artifactId > springdoc-openapi-starter-webmvc-ui</artifactId > </dependency > <dependency > <groupId > org.mybatis.spring.boot</groupId > <artifactId > mybatis-spring-boot-starter</artifactId > </dependency > <dependency > <groupId > mysql</groupId > <artifactId > mysql-connector-java</artifactId > </dependency > <dependency > <groupId > javax.persistence</groupId > <artifactId > persistence-api</artifactId > </dependency > <dependency > <groupId > tk.mybatis</groupId > <artifactId > mapper</artifactId > </dependency > <dependency > <groupId > cn.hutool</groupId > <artifactId > hutool-all</artifactId > </dependency > <dependency > <groupId > com.alibaba.fastjson2</groupId > <artifactId > fastjson2</artifactId > </dependency > <dependency > <groupId > org.projectlombok</groupId > <artifactId > lombok</artifactId > <version > 1.18.28</version > <scope > provided</scope > </dependency > <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 > </plugin > </plugins > </build > </project >
新建业务类PayMicrometerController
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @RestController public class PayMicrometerController { @GetMapping(value = "/pay/micrometer/{id}") public String myMicrometer (@PathVariable("id") Integer id) { return "Hello, 欢迎到来myMicrometer inputId: " +id+" \t 服务返回:" + IdUtil.simpleUUID(); } }
Api接口PayFeignApi
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 @FeignClient(value = "cloud-payment-service") public interface PayFeignApi { @PostMapping("/pay/add") public ResultData addPay (@RequestBody PayDTO payDTO) ; @GetMapping("/pay/get/{id}") public ResultData getPayInfo (@PathVariable("id") Integer id) ; @GetMapping(value = "/pay/get/info") public String mylb () ; @GetMapping(value = "/pay/circuit/{id}") public String myCircuit (@PathVariable("id") Integer id) ; @GetMapping(value = "/pay/bulkhead/{id}") public String myBulkhead (@PathVariable("id") Integer id) ; @GetMapping(value = "/pay/ratelimit/{id}") public String myRatelimit (@PathVariable("id") Integer id) ; @GetMapping(value = "/pay/micrometer/{id}") public String myMicrometer (@PathVariable("id") Integer id) ; }
服务调用者80
POM
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 <dependency > <groupId > io.micrometer</groupId > <artifactId > micrometer-tracing</artifactId > </dependency > <dependency > <groupId > io.micrometer</groupId > <artifactId > micrometer-tracing-bridge-brave</artifactId > </dependency > <dependency > <groupId > io.micrometer</groupId > <artifactId > micrometer-observation</artifactId > </dependency > <dependency > <groupId > io.github.openfeign</groupId > <artifactId > feign-micrometer</artifactId > </dependency > <dependency > <groupId > io.zipkin.reporter2</groupId > <artifactId > zipkin-reporter-brave</artifactId > </dependency >
总POM
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 搜索 便笺 <?xml version="1.0" encoding="UTF-8" ?> <project xmlns ="http://maven.apache.org/POM/4.0.0" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" > <modelVersion > 4.0.0</modelVersion > <parent > <groupId > com.bitzh.cloud</groupId > <artifactId > mscloudV6</artifactId > <version > 1.0-SNAPSHOT</version > </parent > <artifactId > cloud-consumer-feign-order80</artifactId > <properties > <maven.compiler.source > 17</maven.compiler.source > <maven.compiler.target > 17</maven.compiler.target > <project.build.sourceEncoding > UTF-8</project.build.sourceEncoding > </properties > <dependencies > <dependency > <groupId > io.micrometer</groupId > <artifactId > micrometer-tracing</artifactId > </dependency > <dependency > <groupId > io.micrometer</groupId > <artifactId > micrometer-tracing-bridge-brave</artifactId > </dependency > <dependency > <groupId > io.micrometer</groupId > <artifactId > micrometer-observation</artifactId > </dependency > <dependency > <groupId > io.github.openfeign</groupId > <artifactId > feign-micrometer</artifactId > </dependency > <dependency > <groupId > io.zipkin.reporter2</groupId > <artifactId > zipkin-reporter-brave</artifactId > </dependency > <dependency > <groupId > io.github.resilience4j</groupId > <artifactId > resilience4j-ratelimiter</artifactId > </dependency > <dependency > <groupId > io.github.resilience4j</groupId > <artifactId > resilience4j-bulkhead</artifactId > </dependency > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-circuitbreaker-resilience4j</artifactId > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-aop</artifactId > </dependency > <dependency > <groupId > org.apache.httpcomponents.client5</groupId > <artifactId > httpclient5</artifactId > <version > 5.3</version > </dependency > <dependency > <groupId > io.github.openfeign</groupId > <artifactId > feign-hc5</artifactId > <version > 13.1</version > </dependency > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-openfeign</artifactId > </dependency > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-consul-discovery</artifactId > </dependency > <dependency > <groupId > com.bitzh.cloud</groupId > <artifactId > cloud-api-commons</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-actuator</artifactId > </dependency > <dependency > <groupId > org.projectlombok</groupId > <artifactId > lombok</artifactId > <optional > true</optional > </dependency > <dependency > <groupId > cn.hutool</groupId > <artifactId > hutool-all</artifactId > </dependency > <dependency > <groupId > com.alibaba.fastjson2</groupId > <artifactId > fastjson2</artifactId > </dependency > <dependency > <groupId > org.springdoc</groupId > <artifactId > springdoc-openapi-starter-webmvc-ui</artifactId > </dependency > </dependencies > <build > <plugins > <plugin > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-maven-plugin</artifactId > </plugin > </plugins > </build > </project >
yml
1 2 3 4 5 6 7 8 management: zipkin: tracing: endpoint: http://localhost:9411/api/v2/spans tracing: sampling: probability: 1.0
controller
1 2 3 4 5 6 7 8 9 10 11 12 13 @RestController @Slf4j public class OrderMicrometerController { @Resource private PayFeignApi payFeignApi; @GetMapping(value = "/feign/micrometer/{id}") public String myMicrometer (@PathVariable("id") Integer id) { return payFeignApi.myMicrometer(id); } }
测试
访问 http://localhost:9411 即可
9、Gateway新一代网关 是什么?为什么有了nginx还要有gateway,两个都要用还是只用一个?
是什么 Gateway是在Spring生态系统之上构建的API网关服务 ,基于Spring6,Spring Boot 3和Project Reactor等技术。它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式 ,并为它们提供跨领域的关注点,例如:安全性、监控/度量和恢复能力。
体系定位 Cloud全家桶中有个很重要的组件就是网关,在1.x版本中都是采用的Zuul网关;
但在2.x版本中,zuul的升级一直跳票,SpringCloud最后自己研发了一个网关SpringCloud Gateway替代Zuul,
那就是SpringCloud Gateway一句话:gateway是原zuul1.x版的替代
微服务架构中网关在哪
这里的负载均衡就是nginx,那么这里又会有疑问,那之前的loadbalancer不也是负载均衡吗?
组件
类型
工作位置
主要作用
Nginx
L7 反向代理 + 负载均衡器(外部/边缘层)
客户端 ↔ 微服务集群之间(南北向流量 )
对外统一入口,将用户请求分发到后端多个微服务实例(如 user-service 的多个节点)
Spring Cloud LoadBalancer
客户端负载均衡器(内部/服务间)
微服务内部(东西向流量 )
当 Service A 调用 Service B 时,在 Service A 内部自动选择一个健康的 Service B 实例
OpenFeign也是内部的,nginx是客户端调用微服务集群之间的
能干嘛
小总结 Spring Cloud Gateway组件的核心是一系列的过滤器,通过这些过滤器可以将客户端发送的请求转发(路由)到对应的微服务。 Spring Cloud Gateway是加在整个微服务最前沿的防火墙和代理器,隐藏微服务结点IP端口信息,从而加强安全保护。Spring Cloud Gateway本身也是一个微服务,需要注册进服务注册中心。
Gateway三大核心 Route (路由) 路由是构建网关的基本模块,他由ID,目标URI,一系列的断言和过滤器组成,如果断言为true则匹配该路由
Predicate(断言) 参考的是Java8的 java.util.function.Predicate 开发人员可以匹配HTTP请求中的所有内容(例如请求头或请求参数),如果请求与断言相匹配则进行路由
Filter(过滤) 指的是Spring框架中GatewayFilter的实例,使用过滤器,可以在请求被路由前或者之后对请求进行修改。
总结
web前端请求,通过一些匹配条件,定位到真正的服务节点。并在这个转发过程的前后,进行一些精细化控制。
predicate就是我们的匹配条件;
filter,就可以理解为一个无所不能的拦截器。有了这两个元素,再加上目标uri,就可以实现一个具体的路由了
Gateway工作流程
客户端向 Spring Cloud Gateway 发出请求。然后在 Gateway Handler Mapping 中找到与请求相匹配的路由,将其发送到 Gateway Web Handler。Handler 再通过指定的过滤器链来将请求发送到我们实际的服务执行业务逻辑,然后返回。
过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前(Pre)或之后(Post)执行业务逻辑。
在“pre”类型的过滤器可以做参数校验、权限校验、流量监控、日志输出、协议转换等;
在“post”类型的过滤器中可以做响应内容、响应头的修改,日志的输出,流量监控等有着非常重要的作用。
核心逻辑:路由转发 + 断言判断 + 执行过滤器链
入门配置 因为这个也是一个微服务,所以建Module ,改POM,写YML,主启动,业务类,测试
建Module
cloud-gateway9527
改POM
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 <?xml version="1.0" encoding="UTF-8" ?> <project xmlns ="http://maven.apache.org/POM/4.0.0" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" > <modelVersion > 4.0.0</modelVersion > <parent > <groupId > com.bitzh</groupId > <artifactId > cloud2024</artifactId > <version > 1.0-SNAPSHOT</version > </parent > <groupId > com.bitzh.cloud</groupId > <artifactId > cloud-gateway9527</artifactId > <properties > <maven.compiler.source > 17</maven.compiler.source > <maven.compiler.target > 17</maven.compiler.target > <project.build.sourceEncoding > UTF-8</project.build.sourceEncoding > </properties > <dependencies > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-gateway</artifactId > </dependency > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-consul-discovery</artifactId > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-actuator</artifactId > </dependency > </dependencies > <build > <plugins > <plugin > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-maven-plugin</artifactId > </plugin > </plugins > </build > </project >
写YML
1 2 3 4 5 6 7 8 9 10 11 12 13 server: port: 9527 spring: application: name: cloud-gateway cloud: consul: host: localhost port: 8500 discovery: prefer-ip-address: true service-name: ${spring.application.name}
主启动
1 2 3 4 5 6 7 8 9 @SpringBootApplication @EnableDiscoveryClient public class Main9527 { public static void main (String[] args) { SpringApplication.run(Main9527.class,args); } }
业务类
无,不写任何业务代码,网关和业务无关
测试
先启动8500服务中心Consul,在启动9527网关入驻
9527网关如何做路由映射 如何做路由映射 诉求
我们目前不想暴露8001端口,希望在8001真正的支付微服务外面套一层9527网关
8001新建PayGateWayController 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @RestController public class PayGateWayController { @Resource PayService payService; @GetMapping(value = "/pay/gateway/get/{id}") public ResultData<Pay> getById (@PathVariable("id") Integer id) { Pay pay = payService.getById(id); return ResultData.success(pay); } @GetMapping(value = "/pay/gateway/info") public ResultData<String> getGatewayInfo () { return ResultData.success("gateway info test:" + IdUtil.simpleUUID()); } }
现在直接访问8001端口测试通过,说明调用没问题,下面开始映射
9527网关YML新增配置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 server: port: 9527 spring: application: name: cloud-gateway cloud: consul: host: localhost port: 8500 discovery: prefer-ip-address: true service-name: ${spring.application.name} gateway: routes: - id: pay_routh1 uri: http://localhost:8001 predicates: - Path=/pay/gateway/get/** - id: pay_routh2 uri: http://localhost:8001 predicates: - Path=/pay/gateway/info/**
现在配好之后访问gateway就能成功访问到支付服务了。
测试2 之前9527到8001可以了,现在要测试80到9527
我们启动80订单微服务,它从Consul注册中心通过微服务名称找到8001支付微服务进行调用,
80 → 9527 → 8001
要求访问9527网关后才能访问8001,如果我们此时启动80订单,可以做到吗?
修改PayFeignApi接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @GetMapping(value = "/pay/gateway/get/{id}") public ResultData getById (@PathVariable("id") Integer id) ;@GetMapping(value = "/pay/gateway/info") public ResultData<String> getGatewayInfo () ;
修改feign-order80
新建OrderGateWayController
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @RestController public class OrderGateWayController { @Resource private PayFeignApi payFeignApi; @GetMapping(value = "/feign/pay/gateway/get/{id}") public ResultData getById (@PathVariable("id") Integer id) { return payFeignApi.getById(id); } @GetMapping(value = "/feign/pay/gateway/info") public ResultData<String> getGatewayInfo () { return payFeignApi.getGatewayInfo(); } }
测试之后发现feign是通过服务名直接到,服务器里面了,绕开了网关。
正确做法 同一家公司自己人,系统内环境,直接找微服务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @FeignClient(value = "cloud-payment-service") public interface PayFeignApi { @GetMapping(value = "/pay/gateway/get/{id}") public ResultData getById (@PathVariable("id") Integer id) ; @GetMapping(value = "/pay/gateway/info") public ResultData<String> getGatewayInfo () ; }
不同家公司有外人,系统外访问,先找网关再服务
刷新feign接口jar包
也就是把feignApi接口改成网关的服务名
然后重启80微服务,这时候没有网关就会报错了。
还有问题:网关9527的Yml配置映射写死问题
GateWay高级特性 Route以微服务名-动态获取服务URI 解决 uri 地址写死问题
9527修改前YML
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 server: port: 9527 spring: application: name: cloud-gateway cloud: consul: host: localhost port: 8500 discovery: prefer-ip-address: true service-name: ${spring.application.name} gateway: routes: - id: pay_routh1 uri: http://localhost:8001 predicates: - Path=/pay/gateway/get/** - id: pay_routh2 uri: http://localhost:8001 predicates: - Path=/pay/gateway/info/**
9527修改后YML
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 server: port: 9527 spring: application: name: cloud-gateway cloud: consul: host: localhost port: 8500 discovery: prefer-ip-address: true service-name: ${spring.application.name} gateway: routes: - id: pay_routh1 uri: lb://cloud-payment-service predicates: - Path=/pay/gateway/get/** - id: pay_routh2 uri: lb://cloud-payment-service predicates: - Path=/pay/gateway/info/**
Predicate断言 是什么
整体架构概述
常用内置Route Predicate 配置语法总体概述 Shortcut
用过滤器过滤名字,然后用减号来区别参数,参数值之间用 逗号分隔
Fully expanded
用key value 键值对来配置,然后用args来确定值
常用的是shortcut
常用断言Api
时间断言,这个常用于秒杀,什么时间之后再放开等
我们的问题是:上述这个After好懂,这个时间串串???对应的格式如何获得?
1 2 3 4 5 6 7 8 public class ZonedDateTimeDemo { public static void main (String[] args) { ZonedDateTime zbj = ZonedDateTime.now(); System.out.println(zbj); } }
然后修改YML配置,模仿官网写法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 server: port: 9527 spring: application: name: cloud-gateway cloud: consul: host: localhost port: 8500 discovery: prefer-ip-address: true service-name: ${spring.application.name} gateway: routes: - id: pay_routh1 uri: lb://cloud-payment-service predicates: - Path=/pay/gateway/get/** - After=2023-11-20T17:38:13.586918800+08:00[Asia/Shanghai] - id: pay_routh2 uri: lb://cloud-payment-service predicates: - Path=/pay/gateway/info/**
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 server: port: 9527 spring: application: name: cloud-gateway cloud: consul: host: localhost port: 8500 discovery: prefer-ip-address: true service-name: ${spring.application.name} gateway: routes: - id: pay_routh1 uri: lb://cloud-payment-service predicates: - Path=/pay/gateway/get/** - Before=2023-11-27T15:25:06.424566300+08:00[Asia/Shanghai] - id: pay_routh2 uri: lb://cloud-payment-service predicates: - Path=/pay/gateway/info/**
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 server: port: 9527 spring: application: name: cloud-gateway cloud: consul: host: localhost port: 8500 discovery: prefer-ip-address: true service-name: ${spring.application.name} gateway: routes: - id: pay_routh1 uri: lb://cloud-payment-service predicates: - Path=/pay/gateway/get/** - Between=2023-11-21T17:38:13.586918800+08:00[Asia/Shanghai],2023-11-22T17:38:13.586918800+08:00[Asia/Shanghai] - id: pay_routh2 uri: lb://cloud-payment-service predicates: - Path=/pay/gateway/info/**
Cookie Route Predicate需要两个参数,一个是 Cookie name ,一个是正则表达式。
路由规则会通过获取对应的 Cookie name 值和正则表达式去匹配,如果匹配上就会执行路由,如果没有匹配上则不执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 server: port: 9527 spring: application: name: cloud-gateway cloud: consul: host: localhost port: 8500 discovery: prefer-ip-address: true service-name: ${spring.application.name} gateway: routes: - id: pay_routh1 uri: lb://cloud-payment-service predicates: - Path=/pay/gateway/get/** - Before=2023-12-29T17:58:13.586918800+08:00[Asia/Shanghai] - Cookie=username,zzyy - id: pay_routh2 uri: lb://cloud-payment-service predicates: - Path=/pay/gateway/info/**
两个参数:一个是属性名称和一个正则表达式,这个属性值和正则表达式匹配则执行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 server: port: 9527 spring: application: name: cloud-gateway cloud: consul: host: localhost port: 8500 discovery: prefer-ip-address: true service-name: ${spring.application.name} gateway: routes: - id: pay_routh1 uri: lb://cloud-payment-service predicates: - Path=/pay/gateway/get/** - Before=2023-12-29T17:58:13.586918800+08:00[Asia/Shanghai] - Header=X-Request-Id, \d+ - id: pay_routh2 uri: lb://cloud-payment-service predicates: - Path=/pay/gateway/info/**
Host Route Predicate 接收一组参数,一组匹配的域名列表,这个模板是一个 ant 分隔的模板,用.号作为分隔符。
它通过参数中的主机地址作为匹配规则。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 server: port: 9527 spring: application: name: cloud-gateway cloud: consul: host: localhost port: 8500 discovery: prefer-ip-address: true service-name: ${spring.application.name} gateway: routes: - id: pay_routh1 uri: lb://cloud-payment-service predicates: - Path=/pay/gateway/get/** - Before=2023-12-29T17:58:13.586918800+08:00[Asia/Shanghai] - Host=**.bitzh.com - id: pay_routh2 uri: lb://cloud-payment-service predicates: - Path=/pay/gateway/info/**
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 server: port: 9527 spring: application: name: cloud-gateway cloud: consul: host: localhost port: 8500 discovery: prefer-ip-address: true service-name: ${spring.application.name} gateway: routes: - id: pay_routh1 uri: lb://cloud-payment-service predicates: - Path=/pay/gateway/get/** - Before=2023-12-29T17:58:13.586918800+08:00[Asia/Shanghai] - Host=**.atguigu.com - id: pay_routh2 uri: lb://cloud-payment-service predicates: - Path=/pay/gateway/info/**
支持传入两个参数,一个是属性名,一个为属性值,属性值可以是正则表达式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 server: port: 9527 spring: application: name: cloud-gateway cloud: consul: host: localhost port: 8500 discovery: prefer-ip-address: true service-name: ${spring.application.name} gateway: routes: - id: pay_routh1 uri: lb://cloud-payment-service predicates: - Path=/pay/gateway/get/** - Before=2023-12-29T17:58:13.586918800+08:00[Asia/Shanghai] - Query=username, \d+ - id: pay_routh2 uri: lb://cloud-payment-service predicates: - Path=/pay/gateway/info/**
RemoteAddr Route Predicate
远程地址访问
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 server: port: 9527 spring: application: name: cloud-gateway cloud: consul: host: localhost port: 8500 discovery: prefer-ip-address: true service-name: ${spring.application.name} gateway: routes: - id: pay_routh1 uri: lb://cloud-payment-service predicates: - Path=/pay/gateway/get/** - Before=2023-12-29T17:58:13.586918800+08:00[Asia/Shanghai] - RemoteAddr=192.168.124.1/24 - id: pay_routh2 uri: lb://cloud-payment-service predicates: - Path=/pay/gateway/info/**
配置某个请求地址,只能用Get/Post方法访问,访问限制
All
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 server: port: 9527 spring: application: name: cloud-gateway cloud: consul: host: localhost port: 8500 discovery: prefer-ip-address: true service-name: ${spring.application.name} gateway: routes: - id: pay_routh1 uri: lb://cloud-payment-service predicates: - Path=/pay/gateway/get/** - After=2023-12-30T23:02:39.079979400+08:00[Asia/Shanghai] - id: pay_routh2 uri: lb://cloud-payment-service predicates: - Path=/pay/gateway/info/**
Predicate 就是为了实现一组匹配规则,让请求过来找到对应的Route 进行处理。
自定义断言 XXXRoutePredicateFactory规则
看看源码
结合架构图
照着抄一个
模板套路
要么实现AbstractRoutePredicateFactory抽象类
要么实现RoutePredicateFactory接口
开头任意取名,但是必须以RoutePredicateFactory后缀结尾
自定义路由断言规则步骤套路 编写步骤
新建类名XXX需要以RoutePredicateFactory结尾并继承AbstractRoutePredicateFactory类
1 2 3 @Component 标注不可忘public class MyRoutePredicateFactory extends AbstractRoutePredicateFactory <MyRoutePredicateFactory.Config>{ }
1 2 3 4 5 @Override public Predicate<ServerWebExchange> apply (MyRoutePredicateFactory.Config config) { return null ; }
新建apply方法所需要的静态内部类MyRoutePredicateFactory.Config(这个Config类就是我们的路由断言规则,重要)
1 2 3 4 5 6 7 @Validated public static class Config { @Setter @Getter @NotEmpty private String userType; }
1 2 3 4 public MyRoutePredicateFactory () { super (MyRoutePredicateFactory.Config.class); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @Override public Predicate<ServerWebExchange> apply (MyRoutePredicateFactory.Config config) { return new Predicate <ServerWebExchange>() { @Override public boolean test (ServerWebExchange serverWebExchange) { String userType = serverWebExchange.getRequest().getQueryParams().getFirst("userType" ); if (userType == null ) return false ; if (userType.equals(config.getUserType())) { return true ; } return false ; } }; }
All
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 @Component public class MyRoutePredicateFactory extends AbstractRoutePredicateFactory <MyRoutePredicateFactory.Config>{ public MyRoutePredicateFactory () { super (MyRoutePredicateFactory.Config.class); } @Validated public static class Config { @Setter @Getter @NotEmpty private String userType; } @Override public Predicate<ServerWebExchange> apply (MyRoutePredicateFactory.Config config) { return new Predicate <ServerWebExchange>() { @Override public boolean test (ServerWebExchange serverWebExchange) { String userType = serverWebExchange.getRequest().getQueryParams().getFirst("userType" ); if (userType == null ) return false ; if (userType.equals(config.getUserType())) { return true ; } return false ; } }; } }
测试发现有bug,不行了,报错说没有绑定属性错误。
然后发现短促的写法不对要用full的写法。这个fully expanded arguments 的格式可以
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 server: port: 9527 spring: application: name: cloud-gateway cloud: consul: host: localhost port: 8500 discovery: prefer-ip-address: true service-name: ${spring.application.name} gateway: routes: - id: pay_routh1 uri: lb://cloud-payment-service predicates: - Path=/pay/gateway/get/** - After=2023-12-30T23:02:39.079979400+08:00[Asia/Shanghai] - name: My args: userType: diamond - id: pay_routh2 uri: lb://cloud-payment-service predicates: - Path=/pay/gateway/info/**
原来想要短促写法,是缺少shortcutFieldOrder方法的视线,所以不支持短格式
最终完整代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 @Component public class MyRoutePredicateFactory extends AbstractRoutePredicateFactory <MyRoutePredicateFactory.Config>{ public MyRoutePredicateFactory () { super (MyRoutePredicateFactory.Config.class); } @Validated public static class Config { @Setter @Getter @NotEmpty private String userType; } @Override public Predicate<ServerWebExchange> apply (MyRoutePredicateFactory.Config config) { return new Predicate <ServerWebExchange>() { @Override public boolean test (ServerWebExchange serverWebExchange) { String userType = serverWebExchange.getRequest().getQueryParams().getFirst("userType" ); if (userType == null ) return false ; if (userType.equals(config.getUserType())) { return true ; } return false ; } }; } @Override public List<String> shortcutFieldOrder () { return Collections.singletonList("userType" ); } }
现在这样配置就可以了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 server: port: 9527 spring: application: name: cloud-gateway cloud: consul: host: localhost port: 8500 discovery: prefer-ip-address: true service-name: ${spring.application.name} gateway: routes: - id: pay_routh1 uri: lb://cloud-payment-service predicates: - Path=/pay/gateway/get/** - After=2023-12-30T23:02:39.079979400+08:00[Asia/Shanghai] - My=diamond - id: pay_routh2 uri: lb://cloud-payment-service predicates: - Path=/pay/gateway/info/**
Filter过滤 SpringMVC里面的拦截器Interceptor,Servlet的过滤器
pre和post分别会在请求被执行前调用和被执行后调用,用来修改请求和响应信息
断言(Predicate)决定“这个请求要不要路由” 过滤器(Filter)决定“路由前后对请求/响应做什么处理”
概述 能干嘛 请求鉴权,异常处理,记录接口调用时长统计(大厂面试设计题)
类型 1、全局默认过滤器 Global Filter
gateway出厂默认已有的,直接用即可,主要作用于所有的路由
不需要再配置文件中配置,作用在所有的路由上,实现GlobalFilter接口即可
2、单一内置过滤器GatewayFilter
也可以成为网关过滤器,这种过滤器主要是作用于单一路由或者某个路由分组
3、自定义过滤器
Gateway内置的过滤器 是什么 单一内置过滤器GatewayFilter
只看常见和通用的,不是全部。
常用的内置过滤器 1、请求头(RequestHeader)相关组
The AddRequestHeader GatewayFilter Factory
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 指定请求头内容ByName 8001微服务PayGateWayController新增方法 @RestController public class PayGateWayController { @Resource PayService payService; @GetMapping(value = "/pay/gateway/get/{id}") public ResultData<Pay> getById(@PathVariable("id") Integer id) { Pay pay = payService.getById(id); return ResultData.success(pay); } @GetMapping(value = "/pay/gateway/info") public ResultData<String> getGatewayInfo() { return ResultData.success("gateway info test:"+ IdUtil.simpleUUID()); } @GetMapping(value = "/pay/gateway/filter") public ResultData<String> getGatewayFilter(HttpServletRequest request) { String result = ""; Enumeration<String> headers = request.getHeaderNames(); while(headers.hasMoreElements()) { String headName = headers.nextElement(); String headValue = request.getHeader(headName); System.out.println("请求头名: " + headName +"\t\t\t"+"请求头值: " + headValue); if(headName.equalsIgnoreCase("X-Request-atguigu1") || headName.equalsIgnoreCase("X-Request-atguigu2")) { result = result+headName + "\t " + headValue +" "; } } return ResultData.success("getGatewayFilter 过滤器 test: "+result+" \t "+ DateUtil.now()); } } server: port: 9527 spring: application: name: cloud-gateway #以微服务注册进consul或nacos服务列表内 cloud: consul: #配置consul地址 host: localhost port: 8500 discovery: prefer-ip-address: true service-name: ${spring.application.name} gateway: routes: - id: pay_routh1 #pay_routh1 #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名 #uri: http://localhost:8001 #匹配后提供服务的路由地址 uri: lb://cloud-payment-service #匹配后提供服务的路由地址 predicates: - Path=/pay/gateway/get/** # 断言,路径相匹配的进行路由 - After=2023-12-30T23:02:39.079979400+08:00[Asia/Shanghai] #- Cookie=username,zzyy # - Header=X-Request-Id, \d+ # 请求头要有X-Request-Id属性并且值为整数的正则表达式 #- Host=**.atguigu.com #- Query=username, \d+ # 要有参数名username并且值还要是整数才能路由 #- RemoteAddr=192.168.124.1/24 # 外部访问我的IP限制,最大跨度不超过32,目前是1~24它们是 CIDR 表示法。 - My=gold # - name: My # args: # userType: diamond - id: pay_routh2 #pay_routh2 #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名 #uri: http://localhost:8001 #匹配后提供服务的路由地址 uri: lb://cloud-payment-service predicates: - Path=/pay/gateway/info/** # 断言,路径相匹配的进行路由 - id: pay_routh3 #pay_routh3 uri: lb://cloud-payment-service #匹配后提供服务的路由地址 predicates: - Path=/pay/gateway/filter/** # 断言,路径相匹配的进行路由 filters: - AddRequestHeader=X-Request-bitzh1,bitzhValue1 # 请求头kv,若一头含有多参则重写一行设置 - AddRequestHeader=X-Request-bitzh2,bitzhValue2
The RemoveRequestHeader GatewayFilter Factory
1 2 3 4 5 6 7 8 9 10 删除请求头ByName 9527YML - id: pay_routh3 #pay_routh3 uri: lb://cloud-payment-service #匹配后提供服务的路由地址 predicates: - Path=/pay/gateway/filter/** # 断言,路径相匹配的进行路由 filters: - AddRequestHeader=X-Request-bitzh1,bitzhValue1 # 请求头kv,若一头含有多参则重写一行设置 - AddRequestHeader=X-Request-bitzh2,bitzhValue2 - RemoveRequestHeader=sec-fetch-site # 删除请求头sec-fetch-site
The SetRequestHeader GatewayFilter Factory
1 2 3 4 5 6 7 8 9 10 11 修改请求头ByName 9527YML - id: pay_routh3 #pay_routh3 uri: lb://cloud-payment-service #匹配后提供服务的路由地址 predicates: - Path=/pay/gateway/filter/** # 断言,路径相匹配的进行路由 filters: - AddRequestHeader=X-Request-bitzh1,bitzhValue1 # 请求头kv,若一头含有多参则重写一行设置 - AddRequestHeader=X-Request-bitzh2,bitzhValue2 - RemoveRequestHeader=sec-fetch-site # 删除请求头sec-fetch-site - SetRequestHeader=sec-fetch-mode, Blue-updatebyzzyy # 将请求头sec-fetch-mode对应的值修改为Blue-updatebyzzyy
2、请求参数(RequestParameter)相关组
The AddRequestParameter GatewayFilter Factory
The RemoveRequestParameter GatewayFilter Factory
上述两个合一块
1 2 3 4 5 6 7 8 9 10 11 - id: pay_routh3 uri: lb://cloud-payment-service predicates: - Path=/pay/gateway/filter/** filters: - AddRequestHeader=X-Request-atguigu1,atguiguValue1 - AddRequestHeader=X-Request-atguigu2,atguiguValue2 - RemoveRequestHeader=sec-fetch-site - SetRequestHeader=sec-fetch-mode, Blue-updatebyzzyy - AddRequestParameter=customerId,9527001 - RemoveRequestParameter=customerName
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 @GetMapping(value = "/pay/gateway/filter") public ResultData<String> getGatewayFilter (HttpServletRequest request) { String result = "" ; Enumeration<String> headers = request.getHeaderNames(); while (headers.hasMoreElements()) { String headName = headers.nextElement(); String headValue = request.getHeader(headName); System.out.println("request headName:" + headName +"---" +"request headValue:" + headValue); if (headName.equalsIgnoreCase("X-Request-atguigu1" ) || headName.equalsIgnoreCase("X-Request-atguigu2" )) { result = result+headName + "\t " + headValue +" " ; } } System.out.println("=============================================" ); String customerId = request.getParameter("customerId" ); System.out.println("request Parameter customerId: " +customerId); String customerName = request.getParameter("customerName" ); System.out.println("request Parameter customerName: " +customerName); System.out.println("=============================================" ); return ResultData.success("getGatewayFilter 过滤器 test: " +result+" \t " + DateUtil.now()); }
3、回应头(ResponseHeader)相关组
The AddResponseHeader GatewayFilter Factory
1 2 3 4 5 6 7 8 9 10 11 12 - id: pay_routh3 uri: lb://cloud-payment-service predicates: - Path=/pay/gateway/filter/** filters: - AddRequestHeader=X-Request-atguigu1,atguiguValue1 - AddRequestHeader=X-Request-atguigu2,atguiguValue2 - RemoveRequestHeader=sec-fetch-site - SetRequestHeader=sec-fetch-mode, Blue-updatebyzzyy - AddRequestParameter=customerId,9527001 - RemoveRequestParameter=customerName - AddResponseHeader=X-Response-atguigu, BlueResponse
The SetResponseHeader GatewayFilter Factory
1 2 3 4 5 6 7 8 9 10 11 12 13 - id: pay_routh3 uri: lb://cloud-payment-service predicates: - Path=/pay/gateway/filter/** filters: - AddRequestHeader=X-Request-atguigu1,atguiguValue1 - AddRequestHeader=X-Request-atguigu2,atguiguValue2 - RemoveRequestHeader=sec-fetch-site - SetRequestHeader=sec-fetch-mode, Blue-updatebyzzyy - AddRequestParameter=customerId,9527001 - RemoveRequestParameter=customerName - AddResponseHeader=X-Response-atguigu, BlueResponse - SetResponseHeader=Date,2099-11-11
The RemoveResponseHeader GatewayFilter Factory
1 2 3 4 5 6 7 8 9 10 11 12 13 14 - id: pay_routh3 uri: lb://cloud-payment-service predicates: - Path=/pay/gateway/filter/** filters: - AddRequestHeader=X-Request-atguigu1,atguiguValue1 - AddRequestHeader=X-Request-atguigu2,atguiguValue2 - RemoveRequestHeader=sec-fetch-site - SetRequestHeader=sec-fetch-mode, Blue-updatebyzzyy - AddRequestParameter=customerId,9527001 - RemoveRequestParameter=customerName - AddResponseHeader=X-Response-atguigu, BlueResponse - SetResponseHeader=Date,2099-11-11 - RemoveResponseHeader=Content-Type
4、前缀和路径相关组
The PrefixPath GatewayFilter Factory 自动添加路径前缀
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 server: port: 9527 spring: application: name: cloud-gateway cloud: consul: host: localhost port: 8500 discovery: prefer-ip-address: true service-name: ${spring.application.name} gateway: routes: - id: pay_routh1 uri: lb://cloud-payment-service predicates: - Path=/pay/gateway/get/** - Before=2023-12-29T17:58:13.586918800+08:00[Asia/Shanghai] - id: pay_routh2 uri: lb://cloud-payment-service predicates: - Path=/pay/gateway/info/** - id: pay_routh3 uri: lb://cloud-payment-service predicates: - Path=/gateway/filter/** filters: - AddRequestHeader=X-Request-atguigu1,atguiguValue1 - PrefixPath=/pay
The SetPath GatewayFilter Factory 访问路径修改
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 server: port: 9527 spring: application: name: cloud-gateway cloud: consul: host: localhost port: 8500 discovery: prefer-ip-address: true service-name: ${spring.application.name} gateway: routes: - id: pay_routh1 uri: lb://cloud-payment-service predicates: - Path=/pay/gateway/get/** - Before=2023-12-29T17:58:13.586918800+08:00[Asia/Shanghai] - id: pay_routh2 uri: lb://cloud-payment-service predicates: - Path=/pay/gateway/info/** - id: pay_routh3 uri: lb://cloud-payment-service predicates: - Path=/XYZ/abc/{segment} filters: - AddRequestHeader=X-Request-atguigu1,atguiguValue1 - SetPath=/pay/gateway/{segment}
The RedirectTo GatewayFilter Factory 重定向到某个页面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 server: port: 9527 spring: application: name: cloud-gateway cloud: consul: host: localhost port: 8500 discovery: prefer-ip-address: true service-name: ${spring.application.name} gateway: routes: - id: pay_routh1 uri: lb://cloud-payment-service predicates: - Path=/pay/gateway/get/** - Before=2023-12-29T17:58:13.586918800+08:00[Asia/Shanghai] - id: pay_routh2 uri: lb://cloud-payment-service predicates: - Path=/pay/gateway/info/** - id: pay_routh3 uri: lb://cloud-payment-service predicates: - Path=/pay/gateway/filter/** filters: - AddRequestHeader=X-Request-atguigu1,atguiguValue1 - RedirectTo=302, http://www.atguigu.com/
5、其他
Default Filters 配置在此处相当于全局通用,自定义秒变Global
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 server: port: 9527 spring: application: name: cloud-gateway cloud: consul: host: localhost port: 8500 discovery: prefer-ip-address: true service-name: ${spring.application.name} gateway: routes: - id: pay_routh1 uri: lb://cloud-payment-service predicates: - Path=/pay/gateway/get/** - After=2023-12-30T23:02:39.079979400+08:00[Asia/Shanghai] - My=gold - id: pay_routh2 uri: lb://cloud-payment-service predicates: - Path=/pay/gateway/info/** - id: pay_routh3 uri: lb://cloud-payment-service predicates: - Path=/pay/gateway/filter/** filters: - RedirectTo=302, http://www.atguigu.com/
Gateway自定义过滤器 自定义全局Filter 面试题
统计接口调用耗时情况,如何落地,谈谈设计思路
通过自定义全局过滤器搞定上述需求
自定义接口调用耗时统计的全局过滤器
步骤
新建类MyGlobalFilter 并实现GolbalFilter,Ordered两个接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @Component @Slf4j public class MyGlobalFilter implements GlobalFilter , Ordered{ @Override public Mono<Void> filter (ServerWebExchange exchange, GatewayFilterChain chain) { return null ; } @Override public int getOrder () { return 0 ; } }
配置YML,把需要计时的服务,进行断言路由到对应的地址
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 server: port: 9527 spring: application: name: cloud-gateway cloud: consul: host: localhost port: 8500 discovery: prefer-ip-address: true service-name: ${spring.application.name} gateway: routes: - id: pay_routh1 uri: lb://cloud-payment-service predicates: - Path=/pay/gateway/get/** - After=2023-12-30T23:02:39.079979400+08:00[Asia/Shanghai] - id: pay_routh2 uri: lb://cloud-payment-service predicates: - Path=/pay/gateway/info/** - id: pay_routh3 uri: lb://cloud-payment-service predicates: - Path=/pay/gateway/filter/** filters: - AddRequestHeader=X-Request-atguigu1,atguiguValue1
完整的过滤器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 @Component @Slf4j public class MyGlobalFilter implements GlobalFilter , Ordered{ @Override public int getOrder () { return 0 ; } private static final String BEGIN_VISIT_TIME = "begin_visit_time" ; @Override public Mono<Void> filter (ServerWebExchange exchange, GatewayFilterChain chain) { exchange.getAttributes().put(BEGIN_VISIT_TIME, System.currentTimeMillis()); return chain.filter(exchange).then(Mono.fromRunnable(()->{ Long beginVisitTime = exchange.getAttribute(BEGIN_VISIT_TIME); if (beginVisitTime != null ){ log.info("访问接口主机: " + exchange.getRequest().getURI().getHost()); log.info("访问接口端口: " + exchange.getRequest().getURI().getPort()); log.info("访问接口URL: " + exchange.getRequest().getURI().getPath()); log.info("访问接口URL参数: " + exchange.getRequest().getURI().getRawQuery()); log.info("访问接口时长: " + (System.currentTimeMillis() - beginVisitTime) + "ms" ); log.info("我是美丽分割线: ###################################################" ); System.out.println(); } })); } }
自定义条件Filter 全局是所有都生效,条件是符合条件的才会生效
先参考GateWay内置出厂默认的
然后开始自定义网关过滤器规则步骤套路
1、新建类名XXX需要以GatewayFilterFactory结尾,并继承AbstractGatewayFilterFactory类
1 2 3 @Component 标注不可忘public class MyGatewayFilterFactory extends AbstractGatewayFilterFactory <MyGatewayFilterFactory.Config>{}
2、新建XXXGatewayFilterFactory.Config内部类
1 2 3 4 5 6 7 8 @Component public class MyGatewayFilterFactory extends AbstractGatewayFilterFactory <MyGatewayFilterFactory.Config>{ public static class Config { @Setter @Getter private String status; } }
3、重写apply方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 @Component public class MyGatewayFilterFactory extends AbstractGatewayFilterFactory <MyGatewayFilterFactory.Config>{ @Override public GatewayFilter apply (MyGatewayFilterFactory.Config config) { return new GatewayFilter () { @Override public Mono<Void> filter (ServerWebExchange exchange, GatewayFilterChain chain) { ServerHttpRequest request = exchange.getRequest(); System.out.println("进入自定义网关过滤器MyGatewayFilterFactory,status====" +config.getStatus()); if (request.getQueryParams().containsKey("atguigu" )) { return chain.filter(exchange); }else { exchange.getResponse().setStatusCode(HttpStatus.BAD_REQUEST); return exchange.getResponse().setComplete(); } } }; } public static class Config { @Setter @Getter private String status; } }
4、重写shortcutFieldOrder
1 2 3 4 5 6 @Override public List<String> shortcutFieldOrder () { List<String> list = new ArrayList <String>(); list.add("status" ); return list; }
5、空参构造方法,内部调用super
1 2 3 public MyGatewayFilterFactory () { super (MyGatewayFilterFactory.Config.class); }
完整代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 @Component public class MyGatewayFilterFactory extends AbstractGatewayFilterFactory <MyGatewayFilterFactory.Config>{ public MyGatewayFilterFactory () { super (MyGatewayFilterFactory.Config.class); } @Override public GatewayFilter apply (MyGatewayFilterFactory.Config config) { return new GatewayFilter () { @Override public Mono<Void> filter (ServerWebExchange exchange, GatewayFilterChain chain) { ServerHttpRequest request = exchange.getRequest(); System.out.println("进入了自定义网关过滤器MyGatewayFilterFactory,status:" +config.getStatus()); if (request.getQueryParams().containsKey("atguigu" )){ return chain.filter(exchange); }else { exchange.getResponse().setStatusCode(HttpStatus.BAD_REQUEST); return exchange.getResponse().setComplete(); } } }; } @Override public List<String> shortcutFieldOrder () { return Arrays.asList("status" ); } public static class Config { @Getter @Setter private String status; } }
现在自定义Filter设置好了,现在写YML测试效果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 server: port: 9527 spring: application: name: cloud-gateway cloud: consul: host: localhost port: 8500 discovery: prefer-ip-address: true service-name: ${spring.application.name} gateway: routes: - id: pay_routh1 uri: lb://cloud-payment-service predicates: - Path=/pay/gateway/get/** - After=2023-12-30T23:02:39.079979400+08:00[Asia/Shanghai] - id: pay_routh2 uri: lb://cloud-payment-service predicates: - Path=/pay/gateway/info/** - id: pay_routh3 uri: lb://cloud-payment-service predicates: - Path=/pay/gateway/filter/** filters: - My=atguigu - AddRequestHeader=X-Request-atguigu1,atguiguValue1
Gateway整合阿里巴巴Sentinel实现容错 10、SpringCloud Alibaba入门简介 SpringCloud Alibaba是SpringCloud的Alibaba版本,和阿里云融合很好。
里面整合了一站式解决方案,以及必须组件。什么是必须组件
后面SchedulerX相当于xxl-job
能干嘛
怎么玩
11、SpringCloudAlibaba Nacos服务注册和配置中心
Nacos简介 就是服务注册和服务配置中心
Nacos下载安装 Nacos 快速开始 | Nacos 官网
各种注册中心比较
据说 Nacos 在阿里巴巴内部有超过 10 万的实例运行,已经过了类似双十一等各种大型流量的考验,Nacos默认是AP模式,但也可以调整切换为CP,我们一般用默认AP即可。
先从官网下载Nacos 这里先2.2.3版本的
解压安装包然后运行bin目录下的startup.cmd
1 startup.cmd -m standalone
然后访问localhost:8848/nacos 默认账号密码都是nacos然后就能进入页面了
关闭服务器
Nacos Discovery 服务注册中心 通过Nacos Server 和 spring-cloud-starter-alibaba-nacos-discovery 实现服务的注册与发现
基于Nacos的服务提供者 新建Module cloudalibaba-provider-payment9001
改POM
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 <?xml version="1.0" encoding="UTF-8" ?> <project xmlns ="http://maven.apache.org/POM/4.0.0" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" > <modelVersion > 4.0.0</modelVersion > <parent > <groupId > com.bitzh</groupId > <artifactId > cloud2024</artifactId > <version > 1.0-SNAPSHOT</version > </parent > <artifactId > cloudalibaba-provider-payment9001</artifactId > <properties > <maven.compiler.source > 17</maven.compiler.source > <maven.compiler.target > 17</maven.compiler.target > <project.build.sourceEncoding > UTF-8</project.build.sourceEncoding > </properties > <dependencies > <dependency > <groupId > com.alibaba.cloud</groupId > <artifactId > spring-cloud-starter-alibaba-nacos-discovery</artifactId > </dependency > <dependency > <groupId > com.bitzh.cloud</groupId > <artifactId > cloud-api-commons</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-actuator</artifactId > </dependency > <dependency > <groupId > cn.hutool</groupId > <artifactId > hutool-all</artifactId > </dependency > <dependency > <groupId > org.projectlombok</groupId > <artifactId > lombok</artifactId > <version > 1.18.28</version > <scope > provided</scope > </dependency > <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 > </plugin > </plugins > </build > </project >
写YML
1 2 3 4 5 6 7 8 9 10 server: port: 9001 spring: application: name: nacos-payment-provider cloud: nacos: discovery: server-addr: localhost:8848
主启动
1 2 3 4 5 6 7 8 9 @SpringBootApplication @EnableDiscoveryClient public class Main9001 { public static void main (String[] args) { SpringApplication.run(Main9001.class,args); } }
业务类
1 2 3 4 5 6 7 8 9 10 11 12 @RestController public class PayAlibabaController { @Value("${server.port}") private String serverPort; @GetMapping(value = "/pay/nacos/{id}") public String getPayInfo (@PathVariable("id") Integer id) { return "nacos registry, serverPort: " + serverPort+"\t id" +id; } }
测试
基于Nacos的服务消费者 新建Module cloudalibaba-consumer-nacos-order83
POM
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 <?xml version="1.0" encoding="UTF-8" ?> <project xmlns ="http://maven.apache.org/POM/4.0.0" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" > <modelVersion > 4.0.0</modelVersion > <parent > <groupId > com.bitzh</groupId > <artifactId > cloud2024</artifactId > <version > 1.0-SNAPSHOT</version > </parent > <artifactId > cloudalibaba-consumer-nacos-order83</artifactId > <properties > <maven.compiler.source > 17</maven.compiler.source > <maven.compiler.target > 17</maven.compiler.target > <project.build.sourceEncoding > UTF-8</project.build.sourceEncoding > </properties > <dependencies > <dependency > <groupId > com.alibaba.cloud</groupId > <artifactId > spring-cloud-starter-alibaba-nacos-discovery</artifactId > </dependency > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-loadbalancer</artifactId > </dependency > <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 > </dependencies > <build > <plugins > <plugin > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-maven-plugin</artifactId > </plugin > </plugins > </build > </project >
YML
1 2 3 4 5 6 7 8 9 10 11 12 13 server: port: 83 spring: application: name: nacos-order-consumer cloud: nacos: discovery: server-addr: localhost:8848 service-url: nacos-user-service: http://nacos-payment-provider
主启动
1 2 3 4 5 6 7 8 9 @EnableDiscoveryClient @SpringBootApplication public class Main83 { public static void main (String[] args) { SpringApplication.run(Main83.class,args); } }
配置config
1 2 3 4 5 6 7 8 @Configuration public class RestTemplateConfig { @Bean @LoadBalanced public RestTemplate restTemplate () { return new RestTemplate (); } }
业务类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @RestController public class OrderNacosController { @Resource private RestTemplate restTemplate; @Value("${service-url.nacos-user-service}") private String serverURL; @GetMapping("/consumer/pay/nacos/{id}") public String paymentInfo (@PathVariable("id") Integer id) { String result = restTemplate.getForObject(serverURL + "/pay/nacos/" + id, String.class); return result+"\t" +" 我是OrderNacosController83调用者。。。。。。" ; } }
负载均衡 参照9001新建9002
要么老实新建,要么取巧,直接拷贝虚拟端口映射,之前用的是老实方法,这次用拷贝虚拟端口映射的方法
然后添加-DServer.port=9002
然后就有个虚拟的service是9002了
然后测试,发现9001和9002交替出现,负载均衡达到
Nacos Config服务配置中心 之前案例Consul8500服务配置动态变更功能可以被Nacos取代
通过Nacos和spring-cloud-starter-alibaba-nacos-config实现中心化全局配置的动态变更
Nacos作为配置中心配置步骤 建Module cloudalibaba-config-nacos-client3377
POM
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 <?xml version="1.0" encoding="UTF-8" ?> <project xmlns ="http://maven.apache.org/POM/4.0.0" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" > <modelVersion > 4.0.0</modelVersion > <parent > <groupId > com.bitzh</groupId > <artifactId > cloud2024</artifactId > <version > 1.0-SNAPSHOT</version > </parent > <groupId > com.bitzh.cloud</groupId > <artifactId > cloudalibaba-config-nacos-client3377</artifactId > <properties > <maven.compiler.source > 17</maven.compiler.source > <maven.compiler.target > 17</maven.compiler.target > <project.build.sourceEncoding > UTF-8</project.build.sourceEncoding > </properties > <dependencies > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-bootstrap</artifactId > </dependency > <dependency > <groupId > com.alibaba.cloud</groupId > <artifactId > spring-cloud-starter-alibaba-nacos-config</artifactId > </dependency > <dependency > <groupId > com.alibaba.cloud</groupId > <artifactId > spring-cloud-starter-alibaba-nacos-discovery</artifactId > </dependency > <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 > </dependencies > <build > <plugins > <plugin > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-maven-plugin</artifactId > </plugin > </plugins > </build > </project >
引入bootstrap,记得之前的bootstrap.yml就知道这个依赖是做什么的了。
YML
配置两个,为啥要配置两个
Nacos同Consul一样,在项目初始化时,要保证先从配置中心进行配置拉取,
拉取配置之后,才能保证项目的正常启动,为了满足动态刷新和全局广播通知
springboot中配置文件的加载是存在优先级顺序的,bootstrap优先级高于application
bootstrap
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 spring: application: name: nacos-config-client cloud: nacos: discovery: server-addr: localhost:8848 config: server-addr: localhost:8848 file-extension: yaml
application.yml
1 2 3 4 5 6 7 8 server: port: 3377 spring: profiles: active: dev
主启动
1 2 3 4 5 6 7 8 9 @EnableDiscoveryClient @SpringBootApplication public class NacosConfigClient3377 { public static void main (String[] args) { SpringApplication.run(NacosConfigClient3377.class,args); } }
业务类
NacosConfigClientController
1 2 3 4 5 6 7 8 9 10 11 12 @RestController @RefreshScope public class NacosConfigClientController { @Value("${config.info}") private String configInfo; @GetMapping("/config/info") public String getConfigInfo () { return configInfo; } }
Nacos的数据会持久化到内嵌数据库derby中,可以自行配置持久化到MySQL中。此外,Nacos不想Consul持久化后能够开机自启动,在服务列表没有Nacos服务
Nacos中添加配置信息 Nacos中的匹配规则 设置DataId理论 公式:
配置DataId实操 创建配置
测试一下,然后也是动态刷新的。
历史配置
Nacos会记录配置文件的历史版本,默认保留30天,此外还有一键回滚功能,回滚会触发配置更新。
Nacos数据模型之Namespace-Group-DataId 问题 多环境多项目管理
问题1:
实际开发中,通常一个系统会准备
dev开发环境
test测试环境
prod生产环境。
如何保证指定环境启动时服务能正确读取到Nacos上相应环境的配置文件呢?
问题2:
一个大型分布式微服务系统会有很多微服务子项目,
每个微服务项目又都会有相应的开发环境、测试环境、预发环境、正式环境……
那怎么对这些微服务配置进行分组和命名空间管理呢?
Namespace+Group+DataId三者关系?为什么这么设计
概念
核心解释
默认值
Namespace
类似 Java 的 package 名 / 类名,最外层维度,用于区分部署环境(如开发 / 测试 / 生产),实现环境隔离
public
Group
逻辑上区分目标对象,可将不同微服务划分到同一个分组中
DEFAULT_GROUP
DataID
与 Namespace、Group 配合,逻辑上区分两个目标对象(补充自概念 1 的描述)
-
Service
即微服务,是 Nacos 服务注册发现的核心对象
-
Cluster
对指定微服务的虚拟划分,一个 Service 可包含一个或多个 Cluster
DEFAULT
Namespace 核心价值是环境隔离 :如创建 dev/test/prod 三个 Namespace,不同环境的配置 / 服务互相隔离;
Group 核心价值是业务分组 :如将订单、支付微服务归到 “BUSINESS_GROUP”,将基础组件归到 “BASE_GROUP”;
Cluster 核心价值是微服务集群划分 :如一个 Service 下的 Cluster 可区分华北集群、华南集群等。
dataID有具体的命名规则,每个微服务不同。
三种方案加载配置 1、DataID方案 指定spring.profile.active和配置文件的DataID来使不同环境下读取不同的配置
默认空间Public+默认分组DEFAULT_GROUP+新建DataID
新建test配置DataID nacos-config-client-test.yaml
修改application.yml
2、Group方案 通过Group实现环境区分
默认空间public+新建PROD_GROUP+新建DataID
新建prod配置DataID nacos-config-client-prod.yaml
新建Group PROD_GROUP
修改YML
在config下新增一条group配置即可
3、Namespace方案 通过Namespace实现明明空间环境区分
新建Namespace: Prod_Namespace
Prod_Namespace+PROD_GROUP+DataID(nacos-config-client-prod.yaml)
然后选定Prod_Namespace后新建配置
然后修改YML,在config下面新增一条namespace
12、SpringCloud Alibaba Sentinel实现熔断和限流 Sentinel
从流量路由、流量控制、流量整形、熔断降级、系统自适应过载保护、热点流量防护等多个维度来帮助开发者保障微服务的稳定性
怎么玩
面试题
缓存穿透+缓存击穿+缓存雪崩如何解决。回顾redis
这里大概讲讲。
缓存穿透就是访问不存在的值直接打到mysql里面。解决方案一就是存空白或者不存在的值,方案二就是布隆过滤器,让过滤器充当黑名单,不存在的值存入过滤器里直接过滤大部分非法请求。
击穿就是热点key失效,这里单机版就用jvm锁,分布式就用redis分布式锁,然后来进行双检加锁。大概;意思就是更新热点key的时候先把热点key缓存,再放锁,否则就等待,防止暴打mysql。
缓存雪崩就是大面积redis缓存失效。redis 中 key 设置为永不过期 or 过期时间错开
redis 缓存集群实现高可用
主从 + 哨兵
Redis Cluster
开启Redis持久化机制aof / rdb ,尽快回复缓存集群
多缓存结合预防雪崩
caffeine或者ehcache本地缓存 + redis缓存
服务降级
Hystrix 或者 阿里sentinel限流 & 降级
人民币玩家 阿里云-云数据库Redis版上面全部都提供服务
服务雪崩 A服务调用B服务,然后各种调用,如果一个服务出错,整个链上的服务全部失败,这就是服务雪崩。
服务降级 服务降级,说白了就是一种服务托底方案,如果服务无法完成正常的调用流程,就使用默认的托底方案来返回数据。
例如,在商品详情页一般都会展示商品的介绍信息,一旦商品详情页系统出现故障无法调用时,会直接获取缓存中的商品介绍信息返回给前端页面。
服务熔断 在分布式与微服务系统中,如果下游服务因为访问压力过大导致响应很慢或者一直调用失败时,上游服务为了保证系统的整体可用性,会暂时断开与下游服务的调用连接。这种方式就是熔断。类比保险丝达到最大服务访问后,直接拒绝访问,拉闸限电,然后调用服务降级的方法并返回友好提示。
服务熔断一般情况下会有三种状态:闭合、开启和半熔断;
闭合状态(保险丝闭合通电OK):服务一切正常,没有故障时,上游服务调用下游服务时,不会有任何限制。
开启状态(保险丝断开通电Error):上游服务不再调用下游服务的接口,会直接返回上游服务中预定的方法。
半熔断状态:处于开启状态时,上游服务会根据一定的规则,尝试恢复对下游服务的调用。此时,上游服务会以有限的流量来调用下游服务,同时,会监控调用的成功率。如果成功率达到预期,则进入关闭状态。如果未达到预期,会重新进入开启状态。
服务限流 服务限流就是限制进入系统的流量,以防止进入系统的流量过大而压垮系统。其主要的作用就是保护服务节点或者集群后面的数据节点,防止瞬时流量过大使服务和数据崩溃(如前端缓存大量实效),造成不可用;还可用于平滑请求,类似秒杀高并发等操作,严禁一窝蜂的过来拥挤,大家排队,一秒钟N个,有序进行。
限流算法有两种,一种就是简单的请求总量计数,一种就是时间窗口限流 (一般为1s),如令牌桶算法和漏牌桶算法就是时间窗口的限流算法。漏桶无法应对突发状况但是能控制速率,速率非常稳定,就是只能固定设置桶大小和流速,令牌桶用的是预存令牌,一个请求消耗一个令牌,有令牌的才能进入,否则就排队而不是超出就立刻拒绝。
服务隔离 有点类似于系统的垂直拆分,就按照一定的规则将系统划分成多个服务模块,并且每个服务模块之间是互相独立的,不会存在强依赖的关系。如果某个拆分后的服务发生故障后,能够将故障产生的影响限制在某个具体的服务内,不会向其他服务扩散,自然也就不会对整体服务产生致命的影响。
互联网行业常用的服务隔离方式有:线程池隔离和信号量隔离。
信号量舱壁就是利用调用方的线程,对并发量限制,固定线程池舱壁就是服务器里面专属线程池,和调用方线程隔离
服务超时 整个系统采用分布式和微服务架构后,系统被拆分成一个个小服务,就会存在服务与服务之间互相调用的现象,从而形成一个个调用链。
形成调用链关系的两个服务中,主动调用其他服务接口的服务处于调用链的上游,提供接口供其他服务调用的服务处于调用链的下游。服务超时就是在上游服务调用下游服务时,设置一个最大响应时间,如果超过这个最大响应时间下游服务还未返回结果,则断开上游服务与下游服务之间的请求连接,释放资源。
安装Sentinel sentinel组件由2部分构成
后台8719默认
前台8080开启
安装步骤 先下载到本地,然后运行命令,前提8080端口不能被占用
1 java -jar sentinel-dashboard-1.8.6.jar
账号密码都为sentinel
微服务8401整合Sentinel入门案例 启动Nacos8848成功 1 startup.cmd -m standalone
启动Sentinel8080成功 1 java -jar sentinel-dashboard-1.8.6.jar
新建微服务8401 cloudalibaba-sentinel-service8401 将被哨兵纳入管控的8401微服务提供者
POM
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 <?xml version="1.0" encoding="UTF-8" ?> <project xmlns ="http://maven.apache.org/POM/4.0.0" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" > <modelVersion > 4.0.0</modelVersion > <parent > <groupId > com.bitzh.cloud</groupId > <artifactId > cloud2024</artifactId > <version > 1.0-SNAPSHOT</version > </parent > <artifactId > cloudalibaba-sentinel-service8401</artifactId > <properties > <maven.compiler.source > 17</maven.compiler.source > <maven.compiler.target > 17</maven.compiler.target > <project.build.sourceEncoding > UTF-8</project.build.sourceEncoding > </properties > <dependencies > <dependency > <groupId > com.alibaba.cloud</groupId > <artifactId > spring-cloud-starter-alibaba-sentinel</artifactId > </dependency > <dependency > <groupId > com.alibaba.cloud</groupId > <artifactId > spring-cloud-starter-alibaba-nacos-discovery</artifactId > </dependency > <dependency > <groupId > com.bitzh.cloud</groupId > <artifactId > cloud-api-commons</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-actuator</artifactId > </dependency > <dependency > <groupId > cn.hutool</groupId > <artifactId > hutool-all</artifactId > </dependency > <dependency > <groupId > org.projectlombok</groupId > <artifactId > lombok</artifactId > <version > 1.18.28</version > <scope > provided</scope > </dependency > <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 > </plugin > </plugins > </build > </project >
YML
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 server: port: 8401 spring: application: name: cloudalibaba-sentinel-service cloud: nacos: discovery: server-addr: localhost:8848 sentinel: transport: dashboard: localhost:8080 port: 8719
主启动
1 2 3 4 5 6 7 8 9 10 11 @EnableDiscoveryClient @SpringBootApplication public class Main8401 { public static void main (String[] args) { SpringApplication.run(Main8401.class,args); } }
业务类FlowLimitController
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @RestController public class FlowLimitController { @GetMapping("/testA") public String testA () { return "------testA" ; } @GetMapping("/testB") public String testB () { return "------testB" ; } }
启动后查看控制台 发现空空如也啥也没有。
Sentinel采用的懒加载 说明。
注意 想要使用Sentinel对某个接口进行限流和降级等操作,一定要先访问下接口,使用Sentinel检测出相应的接口。访问一次接口即可。
然后看到一些指标
指标
全称
含义
与 QPS 的关系
QPS
Queries Per Second
每秒处理请求数
吞吐量的核心体现
RT
Response Time
响应时间(毫秒)
QPS ↑ 通常 RT ↑(资源竞争)
并发数(Concurrency)
—
同时处理的请求数
近似公式:并发数 ≈ QPS × 平均 RT(秒)
TPS
Transactions Per Second
每秒事务数
在数据库/支付场景更常用,1 事务可能含多个查询
RPS
Requests Per Second
每秒请求数
与 QPS 常混用,但 RPS 可包含失败请求