SpringCloud微服务架构
一、基础概念 基于分布式的微服务架构—整体架构
分布式的整体维度:
服务注册与发现
服务调用
服务熔断
负载均衡
服务降级
服务消息队列
配置中心管理
服务网关
服务监控
全链路追踪
自动化构建部署
服务定时任务调度操作
1.SpringCloud简介 SpringCloud=分布式微服务架构的一站式解决方案,是多种微服务架构落地技术的集合体,俗称微服务全家桶
由n多个springboot组成
2.基本使用技术 (2020.2之后每部分技术都有所更新)
3.版本选择 SpringBoot版本使用数字标识 SpringCloud版本使用字母标识(采用伦敦地铁站的名字命名)
由 SpringCloud 版本选择 SpringBoot版本
boot 和 cloud 适配的版本 按照官网给的标准来(解析json)https://start.spring.io/actuator/info
4.教程使用版本
5.组件停更说明 基本全部组件停更不停用,有新的组件去替换
SpringCloud 中文知道手册 Spring Cloud Greenwich 中文文档 参考手册 中文版
二、聚合父工程 创建项目 1.新建项目,选择Maven版本
2.字符编码
3.注解生效激活
4.Java版本选8
5.file type 过滤
父工程pom文件 删掉生成的src文件夹
1.修改打包方式为pom (默认是jar)
2.统一jar包版本 3.配置 dependencyManagement
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 <properties > <project.build.sourceEncoding > UTF-8</project.build.sourceEncoding > <maven.compiler.source > 1.8</maven.compiler.source > <maven.compiler.target > 1.8</maven.compiler.target > <junit.version > 4.12</junit.version > <log4j.version > 1.2.17</log4j.version > <lombok.version > 1.16.18</lombok.version > <mysql.version > 8.0.21</mysql.version > <druid.version > 1.1.16</druid.version > <mybatis.spring.boot.version > 1.3.0</mybatis.spring.boot.version > </properties > <dependencyManagement > <dependencies > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-dependencies</artifactId > <version > 2.2.2.RELEASE</version > <type > pom</type > <scope > import</scope > </dependency > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-dependencies</artifactId > <version > Hoxton.SR1</version > <type > pom</type > <scope > import</scope > </dependency > <dependency > <groupId > com.alibaba.cloud</groupId > <artifactId > spring-cloud-alibaba-dependencies</artifactId > <version > 2.1.0.RELEASE</version > <type > pom</type > <scope > import</scope > </dependency > <dependency > <groupId > mysql</groupId > <artifactId > mysql-connector-java</artifactId > <version > ${mysql.version}</version > <scope > runtime</scope > </dependency > <dependency > <groupId > com.alibaba</groupId > <artifactId > druid</artifactId > <version > ${druid.version}</version > </dependency > <dependency > <groupId > org.mybatis.spring.boot</groupId > <artifactId > mybatis-spring-boot-starter</artifactId > <version > ${mybatis.spring.boot.version}</version > </dependency > <dependency > <groupId > junit</groupId > <artifactId > junit</artifactId > <version > ${junit.version}</version > </dependency > <dependency > <groupId > log4j</groupId > <artifactId > log4j</artifactId > <version > ${log4j.version}</version > </dependency > </dependencies > </dependencyManagement > <build > <plugins > <plugin > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-maven-plugin</artifactId > <version > 2.7.2</version > <configuration > <fork > true</fork > <addResources > true</addResources > </configuration > </plugin > </plugins > </build >
如果导入的依赖爆红,先把 dependencyManagement 注释掉再导入(因为dependencyManagement只是锁定版本) 需要修改的话,就是改变三个标配的版本
通常在最顶层的父 pom 会看到 dependencyManagement Maven会沿着父子层次向上走,直到找到一个拥有 dependencyManagement 元素的项目,然后会使用这个 dependencyManagement 元素中指定的版本号如果子项目需要其他版本,自己加version即可
dependencyManagement 里只声明依赖,并不实现引入;只有在子项目中写入该依赖,并没有指定具体版本,才会从父项目中继承该项。并且version和scope都读取自父pom
跳过测试单元
三、构建支付模块 微服务模块:
建module
改POM
写YML
主启动
业务类
1. 建moudle 选择maven项目
查看父pom发现已有改变
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 <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 > <version > 2.7.3</version > </dependency > <dependency > <groupId > org.mybatis.spring.boot</groupId > <artifactId > mybatis-spring-boot-starter</artifactId > </dependency > <dependency > <groupId > com.alibaba</groupId > <artifactId > druid-spring-boot-starter</artifactId > <version > 1.2.1</version > </dependency > <dependency > <groupId > mysql</groupId > <artifactId > mysql-connector-java</artifactId > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-jdbc</artifactId > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-devtools</artifactId > <scope > runtime</scope > <optional > true</optional > </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 > </dependency > </dependencies >
3. 配置yml文件(自己新建)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 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/reboot?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&serverTimezone=UTC username: root password: root mybatis: mapper-locations: classpath:/mapper/*.xml type-aliases-package: com.atguigu.springcloud.entities
4. 写启动类
1 2 3 4 5 6 @SpringBootApplication public class PaymentMain8001 { public static void main (String[] args) { SpringApplication.run(PaymentMain8001.class,args); } }
5.业务类 1> 创数据库和表
2> 创建实体类
主实体Payment
json封装体CommonResult
实现 Serializable 对象序列化接口;作用:
便于存储
便于传输
保障数据可读性;(内存不够时,暂时存储到硬盘中)
1 2 3 4 5 6 7 8 @Data @AllArgsConstructor @NoArgsConstructor public class Payment implements Serializable { private Long id; private String serial; }
1 2 3 4 5 6 7 8 9 10 11 12 @Data @AllArgsConstructor @NoArgsConstructor public class CommonResult <T > { private Integer code; private String message; private T data; public CommonResult (Integer code,String message) { this (code,message,null ); } }
3> dao
@Mapper Apache的,推荐使用
@Repository spring的,容易出现问题
1 2 3 4 5 6 7 8 @Mapper public interface PaymentDao { public int create (Payment payment) ; public Payment getPaymentById (@Param("id") Long id) ; }
XML文件
namespace —- 强调关联到哪个dao
parameterType —- 映射实体类(传参) 可传Java对象,数组,map…
useGeneratedKeys —– 返回一个数字,成功—1;失败—0
映射数据库和Java实体类
1 <id column ="order_status" property ="orderStatus" javaType ="INT" />
字段名 —- 变量名 —- 类型(类型有的时候加了会报错)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace ="com.atguigu.springcloud.dao.PaymentDao" > <insert id ="create" parameterType ="Payment" useGeneratedKeys ="true" keyProperty ="id" > insert into payment(serial) values (#{serial}) </insert > <resultMap id ="BaseResultMap" type ="com.atguigu.springcloud.entities.Payment" > <id column ="id" property ="id" jdbcType ="BIGINT" /> <id column ="serial" property ="serial" jdbcType ="VARCHAR" /> </resultMap > <select id ="getPaymentById" parameterType ="Long" resultMap ="BaseResultMap" > select * from payment where id=#{id} </select > </mapper >
4> service
1 2 3 4 5 6 7 public interface PaymentService { public int create (Payment payment) ; public Payment getPaymentById (@Param("id") Long id) ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @Service public class PaymentServiceImpl implements PaymentService { @Resource private PaymentDao paymentDao; public int create (Payment payment) { return paymentDao.create(payment); } public Payment getPaymentById (Long id) { return paymentDao.getPaymentById(id); } }
5> 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 PaymentController { @Resource private PaymentService paymentService; @PostMapping(value = "/payment/create") public CommonResult create (@RequestBody Payment payment) { int result = paymentService.create(payment); log.info("***插入结果:" +result); if (result > 0 ) { return new CommonResult(200 ,"插入成功" ,result); } else { return new CommonResult(444 ,"插入失败" ,null ); } } @GetMapping(value = "/payment/get/{id}") public CommonResult getPaymentById (@PathVariable("id") Long id) { Payment payment = paymentService.getPaymentById(id); log.info("***插入结果:" +payment); if (payment != null ) { return new CommonResult(200 ,"查询成功" ,payment); } else { return new CommonResult(444 ,"没有对应记录,查询ID:" +id,null ); } } }
6> 测试
浏览器可支持Get请求,但不太支持Post请求,需要使用postma
测就完了
部署后就不需要每次修改完项目都手动重启
只在开发测试阶段使用,上线后关闭
1> 添加依赖(在子工程的pom文件中)
指定了依赖(第三方jar包)的 作用范围
A项目添加该选项后,在B中就不会出现此依赖,除非删掉或写为false
1 2 3 4 5 6 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-devtools</artifactId > <scope > runtime</scope > <optional > true</optional > </dependency >
2> 添加插件到父工程 pom 中
1 2 3 4 5 6 7 8 9 10 11 12 13 <build > <plugins > <plugin > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-maven-plugin</artifactId > <version > 2.7.2</version > <configuration > <fork > true</fork > <addResources > true</addResources > </configuration > </plugin > </plugins > </build >
3> 开启自动编译选项
4> 更新值
Ctrl + Shift + Alt + /
选择 Register
2021新版 compiler automake allow when app running 移到setting里了
5> 重启 idea
四、客户端(在cloud下创建另一个工程) 1. 还是老样子,5步走
建 moudle
导 pom
写 yml
写启动类
业务类
pom.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 <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 > <version > 2.7.3</version > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-devtools</artifactId > <scope > runtime</scope > <optional > true</optional > </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 > </dependency > </dependencies >
application.yml
主启动类
1 2 3 4 5 6 @SpringBootApplication public class OrderMain8002 { public static void main (String[] args) { SpringApplication.run(OrderMain8002.class,args); } }
把实体类的内容从8001项目里copy过来,创建controller
2. 配置RestTemplat 该项目的目的是调用8001端口,来实现客户端的功能
RestTemplat: 提供了多种便捷访问远程Http服务的方法;是一种简单便捷的访问restful服务模板类,是Spring提供的用于访问Rest服务的客户端模板工具集
1 2 3 4 5 6 7 8 @Configuration public class ApplicationContextConfig { @Bean public RestTemplate getRestTemplate () { return new RestTemplate(); } }
3. 业务类 只要一个controller就行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @RestController @Slf4j public class OrderController { public static final String PAYMENT_URL = "http://localhost:8001" ; @Resource private RestTemplate restTemplate; @GetMapping("/consumer/payment/create") public CommonResult<Payment> create (Payment payment) { return restTemplate.postForObject(PAYMENT_URL + "/payment/create" ,payment,CommonResult.class); } @GetMapping("/consumer/payment/get/{id}") public CommonResult<Payment> getPayment (@PathVariable("id") Long id) { return restTemplate.getForObject(PAYMENT_URL+"/payment/get/" +id,CommonResult.class); } }
postman测试
4. 启动多个微服务
五、工程重构 1. 问题 多个工程有很多相同的部分,比如实体类,大家都要用到,所以为了避免冗余,可以集成到一个项目中
2. 创建新 moudle
3. 改 pom 依赖 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <dependencies > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-devtools</artifactId > <scope > runtime</scope > <optional > true</optional > </dependency > <dependency > <groupId > org.projectlombok</groupId > <artifactId > lombok</artifactId > <optional > true</optional > </dependency > <dependency > <groupId > cn.hutool</groupId > <artifactId > hutool-all</artifactId > <version > 5.4.7</version > </dependency > </dependencies >
4. 把entities粘贴过来
5. maven命令 clean,install 点闪电符号跳过测试模块
6. 删除8001、8002两个项目的entities;并在各自pom中导入依赖 maven install 是把自定义的maven项目导入到本地仓库
导入新建的maven项目
1 2 3 4 5 <dependency > <groupId > com.atguigu.springcloud</groupId > <artifactId > cloud-api-commons</artifactId > <version > ${project.version}</version > </dependency >
8001和8002的pom都要导入,然后可以删除实体类的文件夹了
启动后,测试——-没有问题
六、 Eureka 服务注册与发现 1、 Eureka基础知识 Eurkea采用C/S的设计架构
避免单点故障,所以使用集群
1 2 3 4 什么是服务治理: springcloud封装了Netflix公司开发的Eureka模块来实现服务治理 当客户端和服务端过多时,管理每个服务之间的依赖关系比较复杂,需要使用服务治理。可以实现服务调用、负载均衡、容错等
系统中的其他微服务,使用 Eureka 的客户端连接到 Eureka Server 并维持心跳连接
客户端好比学生,服务端集群好比老师们 Eureka Client,Eureka Server 就是学校;心跳接口就是学校会看老师和学生是否在正常上课 学生也可以作为 Eureka Client 注册到学校,然后找老师们寻求服务
1 “心跳”指的是一段定时发送的自定义信息,让对方知道自己“存活”,以确保连接的有效性。大部分 CS 架构的应用程序都采用了心跳机制,服务端和客户端都可以发心跳。通常情况下是客户端向服务器端发送心跳包,服务端用于判断客户端是否在线。
Eureka 包含两个组件:Eureka Server 和 Eureka Client
Eureka Server: Eureka 服务注册中心,主要用于提供服务注册功能。当微服务启动时,会将自己的服务注册到 Eureka Server。Eureka Server 维护了一个可用服务列表,存储了所有注册到 Eureka Server 的可用服务的信息,这些可用服务可以在 Eureka Server 的管理界面中直观看到。
Eureka Client: Eureka 客户端,通常指的是微服务系统中各个微服务,主要用于和 Eureka Server 进行交互。在微服务应用启动后,Eureka Client 会向 Eureka Server 发送心跳(默认周期为 30 秒)。若 Eureka Server 在多个心跳周期内没有接收到某个 Eureka Client 的心跳,Eureka Server 将它从可用服务列表中移除(默认 90 秒)。
2、 单机版Eureka构建 按照图示一点一点构建整个项目
1> IDEA生成 eurekaServer端服务中心 新建moudle工程,改pom,写yml,添加主启动类
eurekaServer端结构:
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 <dependencies > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-netflix-eureka-server</artifactId > </dependency > <dependency > <groupId > com.atguigu.springcloud</groupId > <artifactId > cloud-api-commons</artifactId > <version > ${project.version}</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 > <version > 2.7.3</version > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-devtools</artifactId > <scope > runtime</scope > <optional > true</optional > </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 > </dependency > <dependency > <groupId > junit</groupId > <artifactId > junit</artifactId > <scope > test</scope > </dependency > </dependencies >
yml:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 server: port: 7001 eureka: instance: hostname: localhost client: register-with-eureka: false fetch-registry: false service-url: defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
主启动类:
1 2 3 4 5 6 7 8 @SpringBootApplication @EnableEurekaServer public class EurekaMain7001 { public static void main (String[] args) { SpringApplication.run(EurekaMain7001.class, args); } }
页面测试:http://localhost:7001
2> payment8001注册进eurekaServer EurekaClient 端cloud-provider-payment8001将注册进 EurekaServer 成为服务提供者 provider,供8002使用
在8002的pom文件中引入新的jar包,eureka-client
1 2 3 4 5 <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-netflix-eureka-client</artifactId > </dependency >
修改yml文件,在原基础上添加 eureka 配置:
1 2 3 4 5 6 7 8 eureka: client: register-with-eureka: true fetch-registry: true service-url: defaultZone: http://localhost:7001/eureka/
给主启动类加注释,标名为客户端
启动测试:先启动 eureka-server
localhost:7001
3> consmer8002注册进eurekaServer EurekaClient 端cloud-consmer-8002将注册进 EurekaServer 成为服务消费者 consumer,调用8001业务
跟上面一样,导 jar 包,改 yml,改启动类
1 2 3 4 5 <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-netflix-eureka-client</artifactId > </dependency >
yml:加个名字和配置
1 2 3 4 5 6 7 8 9 10 spring: application: name: cloud-order-service eureka: client: register-with-eureka: true fetch-registry: true service-url: defaultZone: http://localhost:7001/eureka/
给主启动类加注释,@EnableEurekaClient
测试,先启动 EurekaServer7001,再启动服务提供者provider-8001;
localhost:7001
爆红是Eurkea的自我保护机制
postman正常,哦耶
1 2 3 4 eureka: client: register-with-eureka: false
3、 集群Eureka构建 1> 集群原理
如果是3个的话,就两两互相注册 (有个问题:那要几百个这么一个一个操作得累死,应该有什么便捷方法)
2> 集群环境搭建 新建一个 Eureka Server (moudle)
项目结构:
pom;和 cloud-eureka-server7001 相同;贴过来就行
配置文件 ,需要改为集群式的配置文件 互相注册
本地的话,集群互相注册需要不同的主机名,所以本地想模拟需要修改 host 文件(因为都是localhost,只是端口不同)
把需要的2个加进去
7001的 application.yml
其他不变,defaultZone路径要变为要注册的新地址
1 2 3 4 5 6 7 8 9 10 11 12 13 server: port: 7001 eureka: instance: hostname: eureka7001.com client: register-with-eureka: false fetch-registry: false service-url: defaultZone: http://eureka7002.com:7002/eureka/
7002的 application.yml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 server: port: 7002 eureka: instance: hostname: eureka7002.com client: register-with-eureka: false fetch-registry: false service-url: defaultZone: http://eureka7001.com:7001/eureka/
测试
localhost:7001 或 eureka7001.com:7001
localhost:7002 或 eureka7002.com:7002
3> 把微服务注册到 Eureka 集群中 把 8001 和 8002 的 yml 配置修改即可,将注册的地址改为集群版本
1 defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/ # 集群版
4> 测试-01 先启动 eureka 集群 7001 和 7002;再启动 8001 ,然后 8002
postman 测试调用业务功能
5> 搭建服务端集群(支付者) 新建 moudle cloud-provider-payment8002 模拟多个服务端集群的效果
跟8001除了端口号其他完全一样,粘过来即可
在controller中添加便于查看端口的
把客户端端口号改一下,和服务端这个冲突了(我改成888了)
修改服务端的 controller 服务端集群后,客户端调用服务时只需要关注名字;不用在乎到底调用的是哪一个端口
修改后会出现错误;因为同名下有两个微服务,并不知道该调用哪个。
在 ApplicationContextConfig 使用 @LoadBalanced
1 2 3 4 5 6 7 8 9 @Configuration public class ApplicationContextConfig { @Bean @LoadBalanced public RestTemplate getRestTemplate () { return new RestTemplate(); } }
测试:
先启动EurekaServer 7001/7002
再启动服务端 8001/8002
客户端测试 888
不停请求服务,8001和8002会相继出现,说明实现负载均衡
负载均衡默认是轮询机制,所以会依次调用不同服务端
ApplicationContextBean Ribbon的负载均衡功能
Ribbon和Eureka整合后Consumer可以直接调用服务而不用再关心地址和端口号,且服务还有负载均衡功能
6> actuator微服务信息完善 对整体并没有影响,但是一些细节的修改
主机名称:服务名称修改
问题:主机名称直接暴露在外
访问信息时,希望有IP显示
找业务时,一般是,哪个IP,哪个端口,哪个微服务名称
8001、8002的 yml 文件修改 eureka 下添加配置 8002对照着改相关信息即可
1 2 3 instance: instance-id: payment8001 prefer-ip-address: 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 27 28 29 30 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/oldchen?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&serverTimezone=UTC username: root password: root eureka: client: register-with-eureka: true fetch-registry: true service-url: defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/ instance: instance-id: payment8001 prefer-ip-address: true mybatis: mapper-locations: classpath:/mapper/*.xml type-aliases-package: com.atguigu.springcloud.entities
注:服务端使用时,web 和 actuator 依赖一般绑定同时使用
actuator 的基本命令
监控端口是否正常运行
7> 服务发现 discovery 用来让对方直观的看到有哪些微服务
8001端为例(8002相同的)
1 2 3 @Resource private DiscoveryClient discoveryClient;
添加新的 GetMapping
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @GetMapping(value = "/payment/discovery") public Object discovery () { List<String> services = discoveryClient.getServices(); for (String element : services) { log.info("*****element: " + element); } List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE" ); for (ServiceInstance instance : instances) { log.info(instance.getServiceId()+"\t" +instance.getHost()+"\t" +instance.getPort()+"\t" +instance.getUri()); } return this .discoveryClient; }
全部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 45 46 47 48 49 50 51 52 53 54 55 56 57 @RestController @Slf4j public class PaymentController { @Resource private PaymentService paymentService; @Value("${server.port}") private String serverPort; @Resource private DiscoveryClient discoveryClient; @PostMapping(value = "/payment/create") public CommonResult create (@RequestBody Payment payment) { int result = paymentService.create(payment); log.info("***插入结果:" +result); if (result > 0 ) { return new CommonResult(200 ,"插入成功,serverPort " +serverPort,result); } else { return new CommonResult(444 ,"插入失败" ,null ); } } @GetMapping(value = "/payment/get/{id}") public CommonResult getPaymentById (@PathVariable("id") Long id) { Payment payment = paymentService.getPaymentById(id); log.info("***插入结果:" +payment); if (payment != null ) { return new CommonResult(200 ,"查询成功,serverPort " +serverPort,payment); } else { return new CommonResult(444 ,"没有对应记录,查询ID:" +id,null ); } } @GetMapping(value = "/payment/discovery") public Object discovery () { List<String> services = discoveryClient.getServices(); for (String element : services) { log.info("*****element: " + element); } List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE" ); for (ServiceInstance instance : instances) { log.info(instance.getServiceId()+"\t" +instance.getHost()+"\t" +instance.getPort()+"\t" +instance.getUri()); } return this .discoveryClient; } }
主启动类下添加 @EnableDiscoveryClient (以后会常用)
在 客户端 888 添加该接口后,以后可以直接知道有哪些服务;这样 服务端 也只对外展示一个整体的信息
8> Eureka 自我保护机制 概述:保护模式主要用于一组客户端和Eureka Server 之间存在网络分区场景下的保护
进入保护模式后,Eureka Server 会尝试保护注册表中的内容,不再删除服务注册表中的内容,就是不会注销任何微服务
看到这段提示,既是进入保护模式
一句话概括:某时刻某一微服务不能用了,Eureka 不会立刻清理,依旧会对该微服务的信息进行保存
属于CAP里面的AP分支
为什么会产生Eureka自我保护机制
防止当EurekaClient正常运行,但 EurekaServer 网络不通的情况下,EurekaServe不会立即将EurekaClient服务删除刷
什么是自我保护模式
默认情况下,如果 EurekaServer 在一定时间内没有收到么某个微服务实例的心跳,EurekaServer 将会注销该实例(默认90秒)。但当网络分区发生故障(延时、卡顿、拥挤)时,微服务与 EurekaServer 之间无法正常通信,以上行为就变得很危险——–因为微服务本身是正常的,此时不应该被注销掉 。Eureka通过”自我保护模式”来解决这个问题——当EurekaServer 节点短时间丢失过多客户端时(可能发生网络分区故障),那么这个节点会进入自我保护模式
它的设计哲学是宁可保留错误的注册信息,也不盲目注销任何可能健康的服务实例 好死不如赖活
综上:自我保护是一种应对网络异常的保护措施
9> 关闭自我保护机制 7001端—–EurekaServer端
yml 修改配置 eureka.server.enable-self-preservation 默认为 true
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 eureka: instance: hostname: eureka7001.com client: register-with-eureka: false fetch-registry: false service-url: defaultZone: http://eureka7002.com:7002/eureka/ server: enable-self-preservation: false eviction-interval-timer-in-ms: 2000
8001端—–EurekaClient端
修改 8001 yml
1 2 3 4 lease-renewal-interval-in-seconds: 1 lease-expiration-duration-in-seconds: 2
七、 Zookeeper注册与发现 SpringCloud整合Zookeeper代替Eureka
机构和 Eureka 是完全相同的
1. 注册中心zookeeper 在虚拟机,centos上安装zookeeper;关闭防火墙
查下zookeeper的 ip 地址,保证可以和 win 系统进行 ping 通
2. 服务提供者 建新的 moudle cloud-provider-payment8004
pom
yml
1 2 3 4 5 6 7 8 9 server: port: 8001 spring: application: name: cloude-provider-payment cloud: zookeeper: connect-string: 192.168 .111 .144 :2181
main 函数
1 2 @SpringBootApplication @EnableDiscoveryClient
controller 可以不带日志的注释,后头可能会报错
1 UUID.randomUUID().toString();
启动zookeeper服务,再启动该微服务
启动会有bug出现,原因:8成是环境问题;maven 导入的 jar 包和虚拟机的 zookeeper 版本不一样
zookeeper 作为注册中心,微服务是临时节点。当微服务停止发送心跳后,在一定的等待时间后,注册中心会把微服务踢掉
3. 服务消费者 和提供者差不多;跟Eureka逻辑都一样
八、 Consul注册 1. Consul简介 Consul 是一套开源的分布式服务发现和配置管理系统,由Go语言开发
提供了微服务系统中的服务治理、配置中心、控制总线等功能。这些功能中的每一个都可以根据单独需要使用,也可以一起使用以构建全方位的服务网格。总之 Consul 是一套完整的服务网格解决方案
优点:基于 raft 协议,比较简介;支持健康检查,同时支持 HTTP 和 DNS 协议;支持跨数据中心的 WAN 集群;提供图形界面;跨平台。支持 Linux、Mac、Windows
怎么用: Spring Cloud Consul 中文文档 参考手册 中文版
2. Consul 下载和安装 官网下载,解压后直接就是一个exe文件
我把他放在 D 盘了
运行 cmd
localhost:8500 访问图形化界面
3. 服务提供者注册进consul 熟悉的5步走
新的工程名:cloud-providerconsul-payment8006
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 <dependencies > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-consul-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 > <version > 2.7.3</version > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-devtools</artifactId > <scope > runtime</scope > <optional > true</optional > </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 > </dependency > </dependencies >
yml:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 server: port: 8006 spring: application: name: consul-provider-payment cloud: consul: host: localhost port: 8500 discovery: service-name: ${spring.application.name}
主启动类:
1 2 3 4 5 6 7 @SpringBootApplication @EnableDiscoveryClient public class PaymentMain8006 { public static void main (String[] args) { SpringApplication.run(PaymentMain8006.class,args); } }
controller:
1 2 3 4 5 6 7 8 9 10 11 12 13 @RestController public class PaymentController { @Value("${server.port}") private String serverPort; @RequestMapping("/payment/consul") public String paymentConsul () { return "springcloud with consul: " + serverPort + "\t" + UUID.randomUUID().toString(); } }
测试: 启动微服务和consul
点进去可以看到它的安全状态
4. 服务消费者注册进consul 建moudle、改pom、写yml、主启动、写config、业务层
cloud-consumerconsul-order889
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 <dependencies > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-consul-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 > <version > 2.7.3</version > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-devtools</artifactId > <scope > runtime</scope > <optional > true</optional > </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 > </dependency > </dependencies >
yml:(就换了一个名字)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 server: port: 889 spring: application: name: consul-consumer-order cloud: consul: host: localhost port: 8500 discovery: service-name: ${spring.application.name}
config:
1 2 3 4 5 6 7 8 9 @Configuration public class ApplicationContextConfig { @Bean @LoadBalanced public RestTemplate getRestTemplate () { return new RestTemplate(); } }
controller:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @RestController public class OrderConsulController { public static final String INVOKE_URL = "http://ApplicationContextConfig" ; @Resource private RestTemplate restTemplate; @GetMapping("/consumer/payment/consul/") public String paymentInfo () { String result = restTemplate.getForObject(INVOKE_URL+"/payment/consul" ,String.class); return result; } }
九、 三种注册中心的异同点
组件名
语言
CAP
服务健康检查
对外暴露接口
SpringCloud集成
Eureka
Java
AP
可配支持
HTTP
已集成
Consul
Go
CP
支持
HTTP/DNS
已集成
Zookeeper
Java
CP
支持
客户端
已集成
A:高可用
C:数据一致
P:分区容错性
CAP理论:关注粒度是数据,而不是整体系统设计的策略
最多只能较好的满足两个 (实际情况好像要看网络情况来判断)
CA:单点集群,满足一致性,高可用系统,可扩展性上不太强大
CP:满足一致性,分区容灾性的系统,一般性能不是太高
AP:满足一致性,分区容灾性的系统,对一致性要求比较低
十、Ribbon负载均衡服务调用 1. 概述 Ribbon是一套客户端 ,实现负载均衡的工具
主要功能是提供负载均衡算法和服务调用;Ribbon会自动帮助你基于某种规则(简单的轮询或随机连接等)连接机器。也可以使用Ribbon实现自定义的负载均衡算法
Ribbon目前进入维护状态,但依然大规模部署(目前依然主流和成熟) 未来–> springcloud loadbalancer
LB(负载均衡): 集中式LB和进程内LB
LB负载均衡(Load Balance)是什么
是将用户请求平摊分到多个服务响应端,来实现系统高可用的目的(HV)
常见的负载均衡软件有:Nginx、LVS(Linux虚拟服务器)、硬件F5等
Ribbon本地负载均衡客户端 VS Nginx服务端负载均衡区别
Nginx是服务器负载均衡(集中式LB),客户端所有的请求都发送给Nginx;然后由Nginx实现转发请求。即负载均衡由服务器实现
Ribbon本地负载均衡(进程内LB),在调用微服务接口时,会在注册中心获取服务列表并缓存到JVM本地;在本地实现RPC远程调用技术
集中式LB: 在服务的消费方和提供方之间使用的独立的LB设施(硬件如:F5;软件如:Nginx)。该设施通过某种策略把请求转发到服务提供方(类似于一个医院,进去之后,去哪由医院来分配)
进程式LB: 将LB逻辑集成到消费方,消费方从注册中心得知哪些地址可用,自己挑选一个可用服务器(类似于一个科室,大夫是现成的,进去自己挑)
Ribbon属于进程内LB,它只是一个类库,集成于消费方进程,消费方通过它获取服务提供方地址
Ribbon:负载均衡 + RestTemplate调用
2. 怎么用 架构:Ribbon是一个软负载均衡客户端组件(软负载均衡即软件负载均衡)
可以和其他所需请求客户端结合使用,和eureka结合只是一个实例
1 2 3 4 5 <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-netflix-eureka-client</artifactId > </dependency >
RestTemplate的使用 常用: getForObject/getForEntity postForObject/postForEntity
Object:返回对象为响应体中数据转换的对象,基本上可以理解为json
Entity:返回对象为ResponseEntity对象,包含了响应中的一些重要信息;响应头、响应状态码、响应体(想知道详细信息就用entity,调用get方法可获得详细的信息)
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 { public static final String PAYMENT_URL = "http://CLOUD-PAYMENT-SERVICE" ; @Resource private RestTemplate restTemplate; @GetMapping("/consumer/payment/create") public CommonResult<Payment> create (Payment payment) { return restTemplate.postForEntity(PAYMENT_URL + "/payment/create" ,payment,CommonResult.class).getBody(); } @GetMapping("/consumer/payment/get/{id}") public CommonResult getPayment (@PathVariable("id") Long id) { return restTemplate.getForObject(PAYMENT_URL+"/payment/get/" +id,CommonResult.class); } @GetMapping("/consumer/payment/getEntity/{id}") public CommonResult<Payment> getPayment2 (@PathVariable("id") Long id) { ResponseEntity<CommonResult> entity = restTemplate.getForEntity(PAYMENT_URL+"/payment/get/" +id,CommonResult.class); if (entity.getStatusCode().is2xxSuccessful()) { return entity.getBody(); } else { return new CommonResult<>(444 ,"操作失败" ); } } }
测试结果,两者是一样的;只是Entity可以返回单个详细的部分
3. Ribbon 默认的负载规则 默认规则是:轮询
IRule接口:
该接口的实现类:
自带的常见的用法:
4. 替换Ribbon的规则 这个自定义配置类不能放在@ComponentScan所扫描的子包下面,否则达不到特殊化的目的
即 @SpringBootApplication 注释中带有的 @ComponentScan 会扫描本包下的所有配置;本例中就是在com.atguigu.springcloud 之外建包
换一种替换规则需要自己指定
1 2 3 4 5 6 7 @Configuration public class MySelfRule { @Bean public IRule myRule () { return new RandomRule(); } }
在主启动中配置,当启动时,Ribbon执行自定义的规则
1 2 @RibbonClient(name = "CLOUD-PAYMENT-SERVICE", configuration = MySelfRule.class)
5. 轮询规则的原理 负载均衡算法: rest接口第几次发起请求 % 服务器集群总数 = 实际调用服务器的位置下标
当服务重启时,计数从1算起
1 2 3 4 5 6 7 8 List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE" ); 如:List[0 ] instances = 127.0 .0 .1 :8002 List[1 ] instances= 127.0 .0 .1 :8001 8002 和 8001 组成集群,按照轮询的算法: 1 % 2 = 1 --》 index = 1 list.get(index); 获得服务地址就是 127.0 .0 .1 :8001 2 % 2 = 0 --》 index = 0 list.get(index); 获得服务地址就是 127.0 .0 .1 :8002 3 % 2 = 1 --》 index = 1 list.get(index); 获得服务地址就是 127.0 .0 .1 :8001 ...
源码-查看:
6. 手写轮询算法 启动7001,7002 eureka 集群
给8001,8002 添加一个测试接口;启动
1 2 3 4 @GetMapping("/payment/lb") public String getPaymentLB () { return serverPort; }
888 消费者改造
把 ApplicationContextConfig 里的 @LoadBalanced 注释掉;(因为接下来使用自己写的轮询规则)
新建接口类,以及实现类
接口类
1 2 3 4 public interface LoadBalancer { ServiceInstance instances (List<ServiceInstance> serviceInstances) ; }
实现类
源码中也使用了自旋锁,例如:for( ; ; ) 内部有自己跳出循环的条件
这里自定义的方法使用 do-while 实现自旋锁
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 @Component public class MyLB implements LoadBalancer { private AtomicInteger atomicInteger = new AtomicInteger(0 ); public final int getAndIncrement () { int current; int next; do { current = this .atomicInteger.get(); next = current >= 2147483647 ? 0 : current + 1 ; }while (!this .atomicInteger.compareAndSet(current,next)); System.out.println("******第几次访问,next: " +next); return next; } @Override public ServiceInstance instances (List<ServiceInstance> serviceInstances) { int index = getAndIncrement() % serviceInstances.size(); return serviceInstances.get(index); } }
OrderController 新增接口
@Resource 和 @Autowired
@Autowired:属于Spring,按类型匹配,byType
缺点: 当实现类有两个以上时,容易有冲突;要使用它注入要确保只有一个实现类
@Resource:属于J2EE,按名称匹配,byName。更准确一些,减少了与spring的耦合。查名字的速度也要更快
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @Resource private LoadBalancer loadBalancer;@Resource private DiscoveryClient discoveryClient;@GetMapping("/consumer/payment/lb") public String getPaymentLB () { List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE" ); if (instances == null || instances.size() <= 0 ) { return null ; } ServiceInstance serviceInstance = loadBalancer.instances(instances); URI uri = serviceInstance.getUri(); return restTemplate.getForObject(uri+"/payment/lb" ,String.class); }
测试
十一、OpenFeign的服务调用 用在客户端,使用方法是定义一个服务接口,然后在上面添加注解。
Feign可以和Eureka和Ribbon组合使用来实现负载均衡
Feign干什么: (服务接口绑定器)
旨在使编写Java Http客户端变得容易,实际情况中,一个接口会被多次调用,所以需要对每个微服务都封装一些客户端调用。在Feign的实现下,我们只需要创建一些接口,使用接口调接口的方式,实现服务方的接口绑定
看微服务的service中使用了哪些服务,那客户端就创建对应的接口
通过Feign,只需要定义服务绑定接口并声明方法,就可实现服务调用
Feign已经被OpenFeign代替
1. 使用步骤 建新的工程 cloud-consumer-feign-order890
OpenFeign对Ribbon进行了集成,依赖中包含负载均衡内容
OpenFeign是Spring官方推出的一种声明式服务调用与负载均衡组件
核心:微服务调用接口 + @FeignClient
pom.xml 新内容只多了一个openfeign 的依赖
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 <dependencies > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-openfeign</artifactId > </dependency > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-netflix-eureka-client</artifactId > </dependency > <dependency > <groupId > com.atguigu.springcloud</groupId > <artifactId > cloud-api-commons</artifactId > <version > ${project.version}</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 > <version > 2.7.3</version > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-devtools</artifactId > <scope > runtime</scope > <optional > true</optional > </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 > </dependency > </dependencies >
application.yml
1 2 3 4 5 6 7 8 9 server: port: 890 eureka: client: register-with-eureka: false service-url: defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/
1 2 主启动: @EnableFeignClients (激活) 业务类: @FeignClient (启动)
主启动:
1 2 3 4 5 6 7 8 @SpringBootApplication @EnableFeignClients public class OrderFeignMain890 { public static void main (String[] args) { SpringApplication.run(OrderFeignMain890.class,args); } }
业务类:
Service:使用8001暴露的接口,有什么就调什么
1 2 3 4 5 6 7 8 @Component @FeignClient(value = "CLOUD-PAYMENT-SERVICE") public interface PaymentFeignService { @GetMapping("/payment/get/{id}") CommonResult<Payment> getPaymentById (@PathVariable("id") Long id) ; }
Controller:
1 2 3 4 5 6 7 8 9 10 11 @RestController public class OrderFeignController { @Resource private PaymentFeignService paymentFeignService; @GetMapping("/consumer/payment/get/{id}") public CommonResult<Payment> getPaymentById (@PathVariable("id") Long id) { return paymentFeignService.getPaymentById(id); } }
测试:
先启动7001、7002;在启动8001、8002
启动890测试,服务调用能力和负载均衡功能都正常
总结:面向接口编程
2. OpenFeign 超时控制 会出现一种情况,消费者需要调用服务提供者服务;服务方完成这次服务需要 3s,但消费者只能等待 2s;那超时的话,消费者方就会直接报错
OpenFeign 客户端默认等待 1s
1> 服务提供方8001故意写一个暂停程序接口
1 2 3 4 5 6 7 8 9 10 11 @GetMapping("/payment/feign/timeout") public String paymentFeignTimeout () { try { TimeUnit.SECONDS.sleep(3 ); } catch (InterruptedException e) { e.printStackTrace(); } return serverPort; }
2> 80 添加超时方法
service:
1 2 @GetMapping("/payment/feign/timeout") String paymentFeignTimeout () ;
controller:
1 2 3 4 5 @GetMapping("/consumer/payment/feign/timeout") public String paymentFeignTimeout () { return paymentFeignService.paymentFeignTimeout(); }
3> 错误页面
4> yml中开启配置
1 2 3 4 5 6 ribbon: ReadTimeout: 5000 ConnectTimeout: 5000
3. OpenFeign 日志打印功能 我们可以通过调整日志级别,从而了解 Feign 中的 Http 请求细节。
对Feign接口的调用情况进行监控和输出
1 2 3 4 5 日志级别: NONE: 默认的,不显示日志 BASIC:仅记录请求方法、URL、响应状态码和执行时间 HEADERS:除了 BASIC 中定义的信息外,还有请求和响应的头信息 FULL:除了 HEADERS 中定义的信息外,还有请求响应的正文和元数据
config类:
1 2 3 4 5 6 7 8 9 10 11 import feign.Logger;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;@Configuration public class FeignConfig { @Bean Logger.Level feignLoggerLevel () { return Logger.Level.FULL; } }
yml:
1 2 3 4 logging: level: com.atguigu.springcloud.Service.PaymentFeignService: debug
测试:调用某一接口后,查看打印的日志信息
十二、 Hystrix 服务熔断器 分布式系统面临的问题:
复杂分布式体系结构中的应用程序有数十个依赖关系,每个依赖关系在某些时候不可避免的的会失败
服务雪崩:多个微服务之间调用的时候,假设微服务A调用微服务B和微服务C,微服务B和微服务C又调用其他微服务,这就是所谓的“扇出” 。如果扇出的链路上某个微服务的调用响应时间过长或不可用,对微服务A就会占用更多资源,进而引起系统崩溃,所谓“雪崩效应”
对于高流量的应用来说,单一的后端依赖可能导致所有服务器上的所有资源在几秒内饱和。
Hystrix 是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统中,许多依赖不可避免的会出现调用失败,比如超时、异常。Hystrix能够保证在一个依赖出现问题的情况下,不会导致整体服务失败,避免级联故障
“断路器”是一种开关,当某个单元发生故障后,向调用方返回一个符合预期的、可处理的备选响应(FallBack),而不是长时间等待或抛出调用方无法处理的异常。
Hystrix的主要功能: 服务降级、服务熔断、接近实时的监控…
官方宣布,停更进行维护
1. Hystrix重要概念 1 2 3 4 5 6 7 fallback--服务降级;服务器忙,请稍后再试;不让客户端等待,并返回一个友好提示 哪些情况后触发降级:1.程序运行异常 2.超时 3.服务熔断触发服务降级 4.线程池/信号量打满也会导致服务降级 break--服务熔断;类比保险丝,当达到最大访问服务时,直接拒绝访问,拉闸限电,并调用服务降级返回友好提示 服务降级->熔断->恢复链路 flowlimit--秒杀高并发等操作,严禁一窝蜂的拥挤,大家排队,一秒几个
2. 构建 先把 Eureka7001改回单机版,方便调试
新工程:cloud-provider-hystrix-payment8001
pom:引入hystrix的jar包
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 <dependencies > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-netflix-hystrix</artifactId > </dependency > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-netflix-eureka-client</artifactId > </dependency > <dependency > <groupId > com.atguigu.springcloud</groupId > <artifactId > cloud-api-commons</artifactId > <version > ${project.version}</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 > <version > 2.7.3</version > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-devtools</artifactId > <scope > runtime</scope > <optional > true</optional > </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 > </dependency > </dependencies >
application.yml
1 2 3 4 5 6 7 8 9 10 11 12 13 server: port: 8001 spring: application: name: cloud-provider-hystrix-payment eureka: client: register-with-eureka: true fetch-registry: true service-url: defaultZone: http://eureka7001.com:7001/eureka/
主启动
1 2 3 4 5 6 7 8 @SpringBootApplication @EnableEurekaClient public class PaymentHystrixMain8001 { public static void main (String[] args) { SpringApplication.run(PaymentHystrixMain8001.class,args); } }
service和controller
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @Service public class PaymentService { public String payment_OK (Integer id) { return "线程池: " +Thread.currentThread().getName()+" payment_OK,id: " +id+"\t" +"hhhhh" ; } public String payment_Timeout (Integer id) { int timeNumber = 3 ; try { TimeUnit.SECONDS.sleep(timeNumber); } catch (InterruptedException e) { e.printStackTrace(); } return "线程池: " +Thread.currentThread().getName()+" payment_Timeout,id: " +id+"\t" +"错了错了,耗时:" +timeNumber; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @RestController @Slf4j public class PaymentController { @Resource private PaymentService paymentService; @Value("${server.port}") private String serverPort; @GetMapping("/payment/hystrix/ok/{id}") public String payment_OK (@PathVariable("id") Integer id) { String result = paymentService.payment_OK(id); log.info("****result: " +result); return result; } @GetMapping("/payment/hystrix/timeout/{id}") public String payment_Timeout (@PathVariable("id") Integer id) { String result = paymentService.payment_Timeout(id); log.info("****result: " +result); return result; } }
测试:启动7001,再8001;两个接口均正常,timeout会延迟3s后返回;模拟一个复杂进程需要的处理时间
以上述Moudle为根基平台,正确->错误->降级熔断->恢复
3. 高并发测试 Jmeter工具–性能测试,20000个线程同时运行,去访问接口,会出现卡顿现象;因为tomcat默认的工作线程数被打满了,没有多余的线程缓解压力和处理(tomcat最大活跃线程数200)
结论:这还只是服务提供者8001自己测试,假如外部消费者也来调用服务,那消费者只能干等,最终导致消费端不满意,服务端被拖死
4. 新建消费端加入 hystrix一般用在消费端做降级 (服务端也能用)
建新的消费端:cloud-consumer-feign-hystrix-order891
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 <dependencies > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-openfeign</artifactId > </dependency > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-netflix-hystrix</artifactId > </dependency > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-netflix-eureka-client</artifactId > </dependency > <dependency > <groupId > com.atguigu.springcloud</groupId > <artifactId > cloud-api-commons</artifactId > <version > ${project.version}</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 > <version > 2.7.3</version > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-devtools</artifactId > <scope > runtime</scope > <optional > true</optional > </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 > </dependency > </dependencies >
application.yml
1 2 3 4 5 6 7 8 9 server: port: 891 eureka: client: register-with-eureka: false service-url: # 没开集群 defaultZone: http:
主启动:
1 2 3 4 5 6 7 8 @SpringBootApplication @EnableFeignClients public class OrderHystrixMain891 { public static void main (String[] args) { SpringApplication.run(OrderHystrixMain891.class,args); } }
service接口:
1 2 3 4 5 6 7 8 9 10 @Component @FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT") public interface PaymentHystrixService { @GetMapping("/payment/hystrix/ok/{id}") public String payment_OK (@PathVariable("id") Integer id) ; @GetMapping("/payment/hystrix/timeout/{id}") public String payment_Timeout (@PathVariable("id") Integer id) ; }
controller:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @RestController public class OrderHystrixController { @Resource private PaymentHystrixService paymentHystrixService; @GetMapping("/consumer/payment/hystrix/ok/{id}") public String payment_OK (@PathVariable("id") Integer id) { return paymentHystrixService.payment_OK(id); } @GetMapping("/consumer/payment/hystrix/timeout/{id}") public String payment_Timeout (@PathVariable("id") Integer id) { return paymentHystrixService.payment_Timeout(id); } }
测试:
但是 用Jmeter将2W个线程压8001,消费端891要么转圈圈等待,要么返回超时报错
故障现象和导致原因:
8001同一层次的其他接口被困死,因为tomcat线程池里的工作线程已经被挤占,如果891再去访问8001就,客户端就容易出现卡顿。所以出现了降级、容错、限流等技术的诞生
1 2 3 4 5 6 解决问题: 超时导致服务器变慢(转圈)----超时不再等待 出错(宕机或程序运行出错)----出错要有兜底,有一个最终显示 服务(8001)超时或者宕机了,调用者不能卡死一直等待,必须有服务降级 服务(8001)业务ok,但是调用者自己出现故障或有自我要求(自己等待的时间小于服务提供者),自己处理j
5. 服务降级 服务降级一般在消费端
8001 fallback 修改8001:设置自身调用超时时间的峰值,峰值内可以正常运行,超过了需要有兜底的方法处理,做降级处理fallback
业务类启用:@HystrixCommand
主启动类激活:@EnableCircuitBreaker
当前服务不可用了,就做服务降级
修改8001的 PaymentService:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @HystrixCommand(fallbackMethod = "payment_TimeoutHandler",commandProperties = { @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "3000") }) public String payment_Timeout (Integer id) { int timeNumber = 5 ; try { TimeUnit.SECONDS.sleep(timeNumber); } catch (InterruptedException e) { e.printStackTrace(); } return "线程池: " +Thread.currentThread().getName()+" payment_Timeout,id: " +id+"\t" +"哈哈^_^,耗时:" +timeNumber; } public String payment_TimeoutHandler (Integer id) { return "线程池: " +Thread.currentThread().getName()+" 系统繁忙,请稍后再试,id: " +id+"\t" +"oT_To" ; }
8001主启动类添加注解:@EnableCircuitBreaker
测试:设置程序运行峰值为3s,但程序需要5s,应该返回兜底逻辑部分
890 fallback yml:添加配置
1 2 3 feign: hystrix: enabled: true
主启动类添加注解:@EnableHystrix
修改890 controller:
1 2 3 4 5 6 7 8 9 10 @GetMapping("/consumer/payment/hystrix/timeout/{id}") @HystrixCommand(fallbackMethod = "paymentTimeoutFallbackMethod",commandProperties = { @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "1500") }) public String payment_Timeout (@PathVariable("id") Integer id) { return paymentHystrixService.payment_Timeout(id); } public String paymentTimeoutFallbackMethod (@PathVariable("id") Integer id) { return "我是消费者80,对方支付系统繁忙,请10秒钟后再试或者检查自己运行程序,oT_To" ; }
修改Hystrix属性的话,建议重启,而不是热部署
测试:服务端最多等待3s,但是消费端最多只能等待1.5s,所以消费端直接 fallback
存在问题
每个业务方法都对应一个兜底方法,导致代码膨胀
统一和自定义的方法分开
解决办法:设置一个全局服务降级配置
测试:服务降级会走全局配置
降级和业务逻辑混合在一起,耦合性太高,混乱
每个降级处理的业务逻辑都和主要业务混合在一块,非常混乱
服务降级,客户端去调用服务端,碰上服务端宕机或关闭
该案例服务降级处理是在客户端实现 ,与服务端没有关系,只需要为Feign客户端定义的接口添加一个服务降级处理的实现类即可时间解耦
未来我们要面对的异常:运行、超时、宕机
所有的服务是从PaymentHystrixService接口中调用,新建一个类实现该接口并做服务降级
yml
1 2 3 feign: hystrix: enabled: true
1 2 3 4 5 6 7 8 9 10 11 12 13 @Component public class PaymentFallbackService implements PaymentHystrixService { @Override public String payment_OK (Integer id) { return "-----PaymentFallbackService fall back-payment_OK, ·T_T·" ; } @Override public String payment_Timeout (Integer id) { return "-----PaymentFallbackService fall back-payment_Timeout, ·T_T·" ; } }
测试:启动7001,8001,891
正常调用:
关闭8001模仿宕机:
此时服务端已经宕机,但我们做了降级处理,让客户端在服务端不可用也会获得提示,不会挂起耗死服务器
6. 服务熔断 类似保险丝
论文: CircuitBreaker (martinfowler.com)
熔断机制概述:
熔断机制是应对雪崩效应的一种微服务链路保护机制。当某个微服务不可用或响应时间过长时,会进行服务降级,进而熔断该节点的微服务调用,快速返回错误的响应信息。当检测到该节点的微服务调用响应正常后,恢复调用链路 (一种特殊的服务降级吧)
SpringCloud框架中,熔断机制通过Hystrix实现。当失败的调用到一定阈值时,缺省是5秒内20次调用失败,就会启动熔断机制。注解是 @HystrixCommand
修改8001 PaymentService
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @HystrixCommand(fallbackMethod = "paymentCircuitBreaker_fallback",commandProperties = { @HystrixProperty(name = "circuitBreaker.enabled",value = "true"), //是否开启断路器 @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "10"), //请求次数 @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "10000"), //时间窗口期 @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "60"), //失败率达到多少跳闸 }) public String paymentCircuitBreaker (@PathVariable("id") Integer id) { if (id < 0 ) { throw new RuntimeException("******id 不能为负数" ); } String serialNumber = IdUtil.simpleUUID(); return Thread.currentThread().getName()+"\t" +"调用成功,流水号: " + serialNumber; } public String paymentCircuitBreaker_fallback (@PathVariable("id") Integer id) { return "id 不能负数,请稍后再试,/(ToT)/~~ id: " +id; }
修改controller:
1 2 3 4 5 6 7 @GetMapping("/payment/circuit/{id}") public String paymentCircuitBreaker (@PathVariable("id") Integer id) { String result = paymentService.paymentCircuitBreaker(id); log.info("*****result:" + result); return result; }
测试:
id为正数:
id为负数:
多次提交负数后,达到熔断的条件
再当id为正数时,返回依旧是服务降级页面,链路还未恢复:
等一会链路就会自动恢复:
总结:
熔断类型:
熔断打开:请求不再进行调用当前服务,内部设置时钟一般为MTTR(平均故障处理时间),当打开时长达到所设时钟则进入半熔断状态
熔断关闭:熔断关闭不会对服务进行熔断
熔断半开:部分请求根据规则调用当前服务,如果请求成功且符合规则则认为当前服务恢复正常,关闭熔断
断路器开启或关闭的条件:
当满足一定的阀值时(默认是10s 内超过20个请求次数)
当失败率达到一定的时候(默认10s 内超过50%的请求失败)
当到达以上阀值,断路器将会开启
当开启的时候,所有请求都不会转发
一段时间后(默认是5s),这时断路器是半开状态,会让其中一个请求进行转发。(探子)如果成功,断路器会关闭,若失败,继续开启。重复4和5
1 断路器打开后,再有请求调用时,将不会调用主逻辑,而是直接调用降级fallback。通过断路器,实现自动地发现错误并将降级逻辑切换为主逻辑,减少应用响应时间
7. Hystrix Dashboard 图形化监控
新建监控平台 moudle:cloud-consumer-hystrix-dashboard9001
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 <dependencies > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-netflix-hystrix-dashboard</artifactId > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-actuator</artifactId > <version > 2.7.3</version > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-devtools</artifactId > <scope > runtime</scope > <optional > true</optional > </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 > </dependency > </dependencies >
application.yml:只用配置一个9001端口
主启动:
1 2 3 4 5 6 7 @SpringBootApplication @EnableHystrixDashboard public class HystrixDashboardMain9001 { public static void main (String[] args) { SpringApplication.run(HystrixDashboardMain9001.class,args); } }
访问:http://localhost:9001/hystrix
注:要被监控的话,所有的微服务提供者必须有actuator依赖
修改8001的主启动类:主要是添加下头的配置
新版本 Hystrix 要在主启动类中指定监控路径
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 @SpringBootApplication @EnableEurekaClient @EnableCircuitBreaker public class PaymentHystrixMain8001 { public static void main (String[] args) { SpringApplication.run(PaymentHystrixMain8001.class,args); } @Bean public ServletRegistrationBean getServlet () { HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet(); ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet); registrationBean.setLoadOnStartup(1 ); registrationBean.addUrlMappings("/hystrix.stream" ); registrationBean.setName("HystrixMetricsStreamServlet" ); return registrationBean; } }
测试:打开9001、7001、8001
访问:http://localhost:8001/payment/circuit/33 和 http://localhost:8001/payment/circuit/-33
多次的请求成功:
多次的请求失败:断路器打开
怎么看:
7色
1圈:通过颜色变化代表实力的健康程度,绿色<黄色<橙色<红色。流量越大,实心圆就越大,以此可以在大量实例中快速发现故障实例和高压力实例
1线
整图说明
十三、服务网关 gateway (技术快速迭代,构建体系,配置变多了。编码很重要,但得知道是干嘛的)
zuul服务网关和gateway新一代服务网关;zuul不再被维护,所以主要使用gateway
谈谈对微服务网关的了解?
微服务网关是整个微服务API请求的入口,可以实现过滤Api接口。 作用:可以实现用户的验证登录、解决跨域、日志拦截、权限控制、限流、熔断、负载均衡、黑名单与白名单机制等。
过滤器和网关的区别:过滤器适合单个服务实现过滤请求。网关拦截整个的微服务实现过滤请求,能够解决整个微服务中冗余代码。过滤器是局部拦截,网关实现全局拦截
1 2 3 4 网关使用的是哪一个,为什么要用 gateway?比 zuul 好在哪? Gateway,该项目借助Spring WebFlux的能力,打造了一个API网关。旨在提供一种简单而有效的方法来作为API服务的路由,并为它们提供各种增强功能,例如:安全性,监控和可伸缩性。 Zuul网关属于netflix公司开源产品,属于第一代微服务网关;Gateway属于SpringCloud自研发的网关框架,属于第二代微服务网关。 Zuul网关底层基于Servlet实现,阻塞式Api,不支持长连接。SpringCloudGateway基于Spring5构建,能够实现响应式非阻塞式的Api,支持长连接,能够更好的整合Spring体系的产品,依赖SpringBooot-WebFux
Cloud全家桶中有个很重要的组件就是网关,在1.x版本中采用的都是Zuul网关,2.x版本中,zuul的升级一直跟不上,SpringCloud自己研发了一个网关替代Zuulgateway是原zuul1.x版的替代
SpringCloud Gateway 作为SpringCloud生态系统中的网关,目标是代替 Zuul,在SpringCloud 2.0以上版本中,没有对新版本的Zuul 2.0以上最新高性能版本集成,仍然使用Zuul 1.x非Reactor模式的老版本。而为了提升网关性能,SpringCloud Gateway是基于WebFlux框架实现的,而WebFlux框架底层则使用了高性能的Reactor模式通信框架Netty
SpringCloud Gateway 使用的Webflux中的reactor-netty响应式编程组件,底层使用了Netty通讯框架
1 2 微服务网关能干嘛? 反向代理、鉴权、流量控制、熔断、日志监控
1 2 3 4 5 6 7 8 9 SpringCloud Gateway具有如下特性: 1.基于Spring Framework 5,Project Reactor 和SpringBoot 2.0 进行创建 2.动态路由:能够匹配任何请求属性。(重要) 3.可以对路由指定 Predicate(断言)和 Filter(过滤器)。(重要) 4.集成Hystrix的断路器功能 5.集成 SpringCloud 服务发现功能 6.易于编写 Predicate 和 Filter 7.请求限流功能 8.支持路径重写
Zuul1.x模型:
SpringCloud中所集成的Zuul版本,采用的式Tomcat容器,使用的是传统的Servlet IO处理模型
Servlet的生命周期? servlet由servlet container进行生命周期管理
1 2 3 container启动时创造servlet对象并调用servlet init()进行初始化; container运行时接受请求,并为每个请求分配一个线程(一般从线程池中获取空闲线程)然后调用service(); container关闭时调用servlet destory()销毁servlet;
上述模式的缺点:
servlet是一个简单的网络IO模型,当请求进入servlet container容器中时,servlet container就会为其绑定一个线程,在并发不高的场景下 这种模型是适用的(进来一个请求,就绑定一个线程)。一旦是高并发场景下,线程数量就会上涨,而线程资源代价是昂贵的,严重影响请求的处理时间。在业务场景下,希望只用1个或几个线程就能应对极大并发的请求,这种业务场景下servlet模型没有优势
所以Zuul 1.x是基于servlet之上的一个阻塞式处理模型 ,即spring实现了处理所有request请求的一个servlet并由该servlet阻塞式处理。
Gateway模型:
传统的web框架,如:struts2,springmvc等都是基于Servlet API与Servlet容器基础上运行的
但Servlet3.1之后有了异步非阻塞的支持 。而WebFlux是一个典型非阻塞异步的框架,核心是基于Reactor的相关API实现的。相对于传统web框架来说,它可以运行在诸如Netty,Undertow及支持Servlet3.1的容器上。非阻塞式+函数式编程
现在玩的都是响应式异步非阻塞模型
响应式布局:一个网站能够兼容多个终端。面对不同分辨率设备灵活性强,能够快捷解决多设备显示适应问题。
异步:多个任务执行或发生,可以并发地执行。(同步:多个任务执行或发生,必须逐个地进行执行)
阻塞:当某个事件或任务执行过程中,发出一个请求,但由于请求操作需要的条件不满足,会一直等待,直至条件满足
非阻塞:当某个事件或任务执行过程中,发出一个请求,如果请求操作条件不满足,会立即返回一个标志信息(如:暂时无法访问,诸如此类)
1. gateway三大核心概念 Router(路由):路由是构建网关的基本模块,它由ID,目标URI,一系列的断言和过滤器组成,如果断言为true则匹配该路由(符合路由的匹配、转发规则)
Predicate(断言):参考的是Java8的 java.util.function.Predicate,开发人员可以匹配HTTP请求中的所有内容(例如请求头或请求参数),如果请求与断言相匹配则进行路由
Filter(过滤):指的是Spring框架中GatewayFilter的实例,使用过滤器,可以在请求被路由前或之后对请求进行修改(断言过来后,还可以设置一些过滤条件)
Gateway工作流程
客户端向 SpringCloud Gateway 发出请求。然后在 Gateway Handler Mapping 中找到与请求相匹配的路由,将其发送到 Gateway Web Handler。
Handler再通过指定的过滤器链来将请求发送到我们实际的服务执行逻辑中,然后返回。
过滤器间用虚线分开是因为过滤器可能会在发送代理请求之前(“pre”)或之后(“post”)执行业务逻辑
Filter在”pre”类型的过滤器可以做参数校验、权限校验、流量监控、日志输出、协议转换等
在”post”类型的过滤器可以做响应内容、响应头的修改,日志的输出,流量监控等
核心逻辑:路由转发+执行过滤器链
2. 基本配置 新建网关工程:cloud-gateway-gateway9527
pom:没有 web和actuator 依赖
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 <dependencies > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-gateway</artifactId > </dependency > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-netflix-eureka-client</artifactId > </dependency > <dependency > <groupId > com.atguigu.springcloud</groupId > <artifactId > cloud-api-commons</artifactId > <version > ${project.version}</version > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-devtools</artifactId > <scope > runtime</scope > <optional > true</optional > </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 > </dependency > </dependencies >
gateway 依赖中集成了 webflux 和 reactor-netty (非阻塞式响应式编程的高性能框架)
yml:网关也要注册到注册中心(Eureka、Consul、Zookeeper都行)
实现路由映射:cloud-provider-payment8001 controller的两个接口,get和lb
不想暴漏8001,希望在8001外头套一层9527
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: gateway: routes: - id: payment_routh uri: http://localhost:8001 predicates: - Path=/payment/get/** - id: payment_routh2 uri: http://localhost:8001 predicates: - Path=/payment/lb/** eureka: instance: hostname: cloud-gateway-service client: register-with-eureka: true fetch-registry: true service-url: defaultZone: http://eureka7001.com:7001/eureka/
主启动:
1 2 3 4 5 6 7 @SpringBootApplication @EnableEurekaClient public class GateWayMain9527 { public static void main (String[] args) { SpringApplication.run(GateWayMain9527.class,args); } }
测试:
启动7001、8001、9527
两个微服务均被注册到Eurka;自测8001接口没问题,把接口改为9527
断言规则:
3. gateway配置动态路由 存在的问题:yml配置中路径是写死的,我们需要实现负载均衡
通过微服务名实现动态路由
默认情况下Gateway会根据注册中心注册的服务列表,以注册中心上微服务名为路径创建动态路由进行转发,从而实现动态路由的功能
启动:一个Eureka7001 + 两个服务提供 8001/8002
pom:spring-cloud-starter-netflix-eureka-client
yml配置:uri:统一资源标识符
1 2 3 需要注意的是 uri 的协议为lb,表示启用Gateway的负载均衡功能 lb://serviceName是spring cloud gateway 在微服务中自动为我们创建的负载均衡 uri
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: gateway: discovery: locator: enabled: true routes: - id: payment_routh uri: lb://cloud-payment-service predicates: - Path=/payment/get/** - id: payment_routh2 uri: lb://cloud-payment-service predicates: - Path=/payment/lb/** eureka: instance: hostname: cloud-gateway-service client: register-with-eureka: true fetch-registry: true service-url: defaultZone: http://eureka7001.com:7001/eureka/
通过服务名去查找
测试:发现两个接口在不停的互换
4. Predicate的使用 启动时会自动扫描配置;即 yml 中配置的 predicates
1 2 3 4 5 6 7 8 predicates: - After=2022-09-14T18:29:48.268+08:00[Asia/Shanghai] - Cookie=username,yyds - Header=X-Request-Id, \d+ - Host=**.atguigu.com - Method=GET - Path=/payment/lb/** - Query=username, \d+
After Route Predicate
Before Route Predicate
Between Route Predicate
Before 、Between 和After同理
Cookie Route Predicate :带Cookie和不带Cookie
需要两个参数,一个是Cookie name,一个是正则表达式。
路由规则会通过获取对应的Cookie name 值和正则表达式去匹配,如果匹配上就执行路由,没匹配上就不执行
1 测试工具一般有:Jmeter、Postman(图形界面)、curl(看成是postman的底层命令)
使用 curl 测试,cmd中;默认发送的是 Get请求
发送post请求: curl -X POST “http://…”
不带Cookie:curl http://localhost:9527/payment/lb
带Cookie:curl http://localhost:9527/payment/lb –cookie “username=yyds”
Header Route Predicate : 一个属性名称和一个正 表达式,属性值和正则表达式匹配则执行
测试:请求头包含的是正整数,即可正确匹配
curl http://localhost:9527/payment/lb -H “X-Request-Id:123”
Host Route Predicate :加正确的请求地址才能匹配;不加直接报错
curl http://localhost:9527/payment/lb -H “Host: www.atguigu.com"
curl http://localhost:9527/payment/lb -H “Host: new.atguigu.com”
Method Route Predicate :规定请求方法(GET/POST…)
Path Route Predicate :路径
Query Route Predicate :加参数名
curl http://localhost:9527/payment/lb?username=31
总结:Predicate就是为了实现一组匹配规则,让请求过来找到对应的Route进行处理
5. gateway的Filter的使用 是什么:路由过滤器可用于修改进入的HTTP请求和返回的HTTP响应,路由过滤器只能指定路由进行使用
Spring Cloud Gateway 内置了多种路由过滤器,他们都由GatewayFilter的工厂来产生
种类:
1.GatewayFilter 单一的—31钟
2.GlobalFilter 全局的—10种
自定义全局过滤器 GlobalFilter
使用前先把yml里请求中需要携带信息的断言配置注释掉
两个主要接口:implements GlobalFilter,Ordered
能干嘛: 全局日志记录,统一网关鉴权…
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 @Component @Slf4j public class MyLogGatewayFilter implements GlobalFilter , Ordered { @Override public Mono<Void> filter (ServerWebExchange exchange, GatewayFilterChain chain) { log.info("********come in MyLogGatewayFilter: " + new Date()); String uname = exchange.getRequest().getQueryParams().getFirst("uname" ); if (uname == null ) { log.info("******用户名为null,非法用户,(T__T)" ); exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE); return exchange.getResponse().setComplete(); } return chain.filter(exchange); } @Override public int getOrder () { return 0 ; } }
启动7001、8001、8002、9527
正确地址:localhost:9527/payment/lb?uname=sd
uname 的值是多少都行
ServerWebExchange接口:服务网络交换器 ,存放着重要的请求-响应属性、请求实例和响应实例等等
ServerHttpRequest接口:用于承载请求相关的属性和请求体
ServerHttpResponse接口:用于承载响应相关的属性和响应体
gateway小结 过滤器是线性结构,只要设置了都会一个一个进入;一般放在全局配置。断言则是过滤器之后设置在接口上的规则,需要哪些请求信息
断言是用来判断访问是否符合路由规则,不符合报错404
过滤器是判断请求中是否有我要的信息,报错406
十四、 SpringCloud Config 分配式配置中心 大多配合使用:Config+Bus 后期逐渐被 Nacos 所替代
当前微服务工程面对的问题: 项目越来越多,重复的配置文件也越来越多。我们希望做到一次配置,处处生效
微服务意味着将单体应用中的业务拆分成一个个的子服务,每个服务的粒度相对较小,因此系统中会出现大量的服务。由于每个服务都需要必要的配置信息才能运行,所以一个集中式、动态的配置管理设施是必不可少的。
SpringCloud提供了ConfigServer来解决这个问题,如果每一个微服务都有一个application.yml,那上百个配置文件。。。想想都恐怖
配置中心是什么: SpringCloud Config 为微服务架构中的微服务提供集中化的外部配置支持(一般就是Git或Github) ,配置服务器为各个不同微服务应用的所有环境提供了一个中心化的外部配置
怎么用: SpringCloud Config 分为服务端和客户端两部分
服务端也称为分布式配置中心,它是一个独立的微服务应用,用来连接配置服务器并为客户端提供获取配置信息,加密、解密信息等访问接口
客户端通过指定的配置中心来管理应用资源,以及与业务相关的配置内容,并在启动时从配置中心获取和加载配置信息。配置服务器默认采用git来存储信息,这样有助于对环境配置进行版本处理,并可以通过git客户端工具来方便管理和访问配置内容
能干嘛:
集中管理配置文件
不同环境不同配置,动态化更新配置,分环境部署(dev/test/prod/beta/release)–(开发环境/测试/生产/预发布/发布)
运行期间动态调整配置,不再需要在每个服务部署的机器上编写配置文件,服务会向配置中心统一拉取配置自己的信息
当配置发生变动时,服务不需要重启即可感知到配置的变化并应用新的配置
将配置中心以REST接口的形式暴露——post、curl访问刷新均可
和GitHub整合使用: 由于SpringCloud Config默认使用Git来存储配置文件(SVN和本地文件也可),但最推荐Git,而且使用的是http/https访问的形式
项目启动时去配置中心拉自己所需的配置
十五、 SpringCloud Bus消息总线 问题:
config只能手动刷新,我们想做到自动刷新;
假如100台机子,想要实现全部刷新怎么办
怎么精确刷新,比如只更新98台,剩下2台不刷新
Bus支持两种消息代理:RabbitMQ 和 Kafka
通过消息中间件来推送
SpringCloud Bus是用来将分布式系统的节点与轻量级消息系统链接起来的框架,它整合了Java的事件处理机制和消息中间件的功能
第1、2步,推送新的配置到git,更新到配置中心里;第3步,POST请求广播全局刷新
什么是总线:
微服务架构系统中,通常会使用轻量级的消息代理 来构建一个共用的消息主题 ,并让系统中所有微服务实例都连接上来。由于该主题的消息会被所有实例监听和消费,所有称为消息总线 。在总线上的各个实例,都能广播一些让连接在该主题上的其他实例都知道的消息
基本原理:
ConfigClient实例都监听MQ中同一个topic(默认是SpringCloudBus)。当一个服务刷新数据时,它会把这个信息放到topic中,这样其他监听同一个topic的服务就能得到通知,在更新自身配置
安装RabbitMQ RabbitMQ需要Erlang环境支持
下载Erlang
版本对照表:RabbitMQ Erlang Version Requirements — RabbitMQ
十六、 SpringCloud Stream 消息驱动 消息中间件:基于队列与消息传递技术,在网络环境中为应用系统提供同步或异步、可靠的信息传输的支撑性软件系统
解决的问题:不同端之间(后端和大数据端使用不同的消息中间件)会存在多种MQ(消息中间件),如:ActiveMQ、RabbitMQ、RocketMQ、Kafka。需要一种新技术,让我们不在关注具体的MQ细节,我们只需要用一种适配绑定的方式,自动可以在各种MQ内切换。
SpringCloud Stream
是一个构建消息驱动微服务的框架,我们通过配置来binding(绑定),而SpringCloud Stream的binder 对象负责与消息中间件交互。
目前仅支持RabbitMQ、Kafka
标准的MQ:
生产者、消费者之间靠消息 媒介传递信息内容——Message
消息必须走特定的通道 ——–消息通道MessageChannel
消息通道里的消息如何被消费,谁负责收发处理 ——–消息通道MessageChannel的子接口SubscribableChannel,由MessageHandler消息处理器所订阅(订阅就发送,不订阅就不发送)
引入Stream
这些中间件的差异性,对我们实际项目开发造成一定的困扰性,如果我们用了两个消息队列的其中一种,后面的业务需求需要使用另外一种,很多东西都要推倒重做,因为和系统耦合了,这时SpringCloud Stream给我们提供了一种解耦合的方式
通过定义绑定器作为中间层,完美实现应用程序与消息中间件细节之间的隔离 。通过向应用程序暴露统一的Channel通道,使应用程序不需要考虑各种不同的消息中间件实现
通过定义绑定器Binder作为中间层,实现了应用程序与消息中间件细节之间的隔离
Binder:INPUT对应消费者 OUTPUT对应生产者
1. Stream标准流程套路
Binder—–方便的连接中间件,屏蔽差异
Channel——通道,是队列Queue的一种抽象,在消息通讯系统中就是实现存储和转发的媒介,通过Channel对队列进行配置
Source和Sink—– 简单理解为参照对象是SpringCloud Stream自身,从Stream发布消息就是输出,接受消息就是输入
2. 消息驱动生产者 cloud-steam-rabbitmq-provider8801
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 <dependencies > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-stream-rabbit</artifactId > </dependency > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-netflix-eureka-client</artifactId > </dependency > <dependency > <groupId > com.atguigu.springcloud</groupId > <artifactId > cloud-api-commons</artifactId > <version > ${project.version}</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 > <version > 2.7.3</version > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-devtools</artifactId > <scope > runtime</scope > <optional > true</optional > </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 > </dependency > </dependencies >
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 server: port: 8801 spring: application: name: cloud-stream-provider cloud: stream: binders: defaultRabbit: type: rabbit environment: spring: rabbitmq: host: localhost port: 5672 username: guest password: guest bindings: output: destination: studyExchange content-type: application/json binder: defaultRabbit eureka: client: service-url: defaultZone: http://localhost:7001/eureka
主启动:
1 2 3 4 5 6 7 8 @SpringBootApplication public class CloudStreamRabbitmqProvider8801Application { public static void main (String[] args) { SpringApplication.run(CloudStreamRabbitmqProvider8801Application.class, args); System.out.println("启动成功" ); } }
service: 和消息中间件打交道的逻辑
1 2 3 4 public interface IMessageProviderService { String send () ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import org.springframework.cloud.stream.messaging.Source;@EnableBinding(Source.class) public class MessageProviderServiceImpl implements IMessageProviderService { @Resource private MessageChannel output; @Override public String send () { String serial = UUID.randomUUID().toString(); output.send(MessageBuilder.withPayload(serial).build()); System.out.println("*****serial: " + serial); return serial; } }
controller:
1 2 3 4 5 6 7 8 9 10 @RestController public class SendMessageController { @Resource private IMessageProviderService messageProviderService; @GetMapping(value = "/sendMessage") public String sendMessage () { return messageProviderService.send(); } }
3. 消息驱动消费者 cloud-stream-rabbitmq-consumer8802
pom和上头一样
yml: 主要是在 bindings 处修改为 input
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: 8802 spring: application: name: cloud-stream-consumer cloud: stream: binders: defaultRabbit: type: rabbit environment: spring: rabbitmq: host: localhost port: 5672 username: guest password: guest bindings: input: destination: studyExchange content-type: application/json binder: defaultRabbit eureka: client: service-url: defaultZone: http://localhost:7001/eureka
主启动
controller:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import org.springframework.cloud.stream.messaging.Sink;@Component @EnableBinding(Sink.class) public class ReceiveMessageListener { @Value("${server.port}") private String serverPort; @StreamListener(Sink.INPUT) public void input (Message<String> message) { System.out.println("port:" + serverPort + "\t接受:" + message.getPayload()); } }
4. 分组消费与持久化 重要* 存在问题:
重复消费
消息持久化
默认分组或者还没分组时,提供服务方是集群环境,可能会导致一个服务发送过来后,被两个或多个服务获取到。就会造成数据错误;比如可能会扣除两次款
不同组是可以全面消费的(重复消费)
同一个组内是竞争关系,只有其中一个可以消费
自定义分组 :A、B (不自定义就是随机的流水号) 此组即为消费组
同一个组的多个微服务案例,每次只会有一个获取调用,会轮询调用
持久化 :避免消息丢失(添加分组后)
在业务死掉后重启,依然可以获取业务停止时请求的服务信息
十七、 SpringCloud Sleuth 分布式请求链路跟踪 为什么会出现这项技术:
在微服务框架中,一个由客户端发起的请求在后端会调用多个不同的服务节点来协同得到最后的结果,每一个前端请求都会形成一个复杂的分布式服务调用链路,链路中任何一环出现高延时或错误都会引起整个请求的最后失败
获得每一个调用节点的完整的轨迹图
SpringCloud Sleuth提供了一套完整的服务跟踪的解决方案,在分布式系统中提供追踪解决方案并兼容支持zipkin
使用 下载zipkin jar包Central Repository: io/zipkin/zipkin-server (maven.org)
也就是 sleuth 整合了 zipkin;或者说SpringCloud借鉴了zipkin然后换了个名字
运行jar包
1 java -jar zipkin-server-2.23.9-exec.jar
访问:localhost:9411
一个完整的请求链路,每条链路都用一个Trace Id唯一标识,Span标识发起的请求信息,各Span通过 parent id 关联起来
Trace:类似于树结构的Span集合,表示一条调用链路,存在唯一标识
span:标识调用链路来源,通俗理解span就是一次请求信息
案例:使用8001和888做示范
pom:两个都新增
1 2 3 4 5 <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-zipkin</artifactId > </dependency >
8001 yml:
1 2 3 4 5 6 7 8 spring: zipkin: base-url: http://localhost:9411 sleuth: sampler: probability: 1
8001 controller: 写一个简单的测试接口
1 2 3 4 @GetMapping("/payment/zipkin") public String paymentZipkin () { return "hi, I am paymentZipkin server fall back, welcome to here ^_^" ; }
888 yml:
888 controller:
1 2 3 4 5 6 @GetMapping("/consumer/payment/zipkin") public String paymentZipkin () { String result = restTemplate.getForObject("http://localhost:8001" + "/payment/zipkin/" , String.class); return result; }
依次启动 7001/8001/888
多次调用查看zipkin的内容
十八、 SpringCloud Alibaba简介 SpringCloud Netflix项目进入维护模式(不再开发新的组件)
官网:Spring Cloud Alibaba
英文:https://github.com/alibaba/spring-cloud-alibaba
https://spring-cloud-alibaba-group.github.io/github-pages/greenwich/spring-cloud-alibaba.html
中文:https://github.com/alibaba/spring-cloud-alibaba/blob/2.2.x/README-zh.md
十九、 SpringCloud Alibaba Nacos服务注册和配置中心 前四个字母是Naming和Configuration的前两个字母,最后s为Service
1 Nacos:一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。
Nacos就是注册中心+配置中心的组合 ====》Nacos = Eureka +Config + Bus
1. 下载和安装 官网:https://github.com/alibaba/nacos
下载:https://nacos.io/zh-cn/
下载后解压:
进入bin目录下,cmd打开,单机模式下启动: startup.cmd -m standalone
访问地址:localhost:8848/nacos
默认用户名和密码:nacos
2. Nacos 作服务注册中心 1> 服务提供者 new Moudle :cloudAlibaba-provider-payment9001
配置的案例跟官网走就好:https://spring-cloud-alibaba-group.github.io/github-pages/greenwich/spring-cloud-alibaba.html
父pom中:springcloud alibaba 的依赖版本是 2.1.0.RELEASE
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 <dependencies > <dependency > <groupId > com.alibaba.cloud</groupId > <artifactId > spring-cloud-starter-alibaba-nacos-discovery</artifactId > </dependency > <dependency > <groupId > com.atguigu.springcloud</groupId > <artifactId > cloud-api-commons</artifactId > <version > ${project.version}</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 > <version > 2.7.3</version > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-devtools</artifactId > <scope > runtime</scope > <optional > true</optional > </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 > </dependency > </dependencies >
application.yml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 server: port: 9001 spring: application: name: nacos-payment-provider cloud: nacos: discovery: server-addr: localhost:8848 management: endpoints: web: exposure: include: '*'
main启动
1 2 3 4 5 6 7 @SpringBootApplication @EnableDiscoveryClient public class PaymentMain9001 { public static void main (String[] args) { SpringApplication.run(PaymentMain9001.class, args); } }
controller
1 2 3 4 5 6 7 8 9 10 @RestController public class PaymentController { @Value("${server.port}") private String serverPort; @GetMapping("/payment/nacos/{id}") public String getPayment (@PathVariable("id") Integer id) { return "nacos registry, serverport: " + serverPort + "\t id" + id; } }
测试:启动 nacos,启动9001
为方便演示负载均衡,创建一个相同的9002
2> 服务消费者 cloudAlibaba-consumer-nacos-order83
pom:都差不多的
nacos默认自带负载均衡
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 <dependencies > <dependency > <groupId > com.alibaba.cloud</groupId > <artifactId > spring-cloud-starter-alibaba-nacos-discovery</artifactId > </dependency > <dependency > <groupId > com.atguigu.springcloud</groupId > <artifactId > cloud-api-commons</artifactId > <version > ${project.version}</version > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > </dependency > <dependency > <groupId > com.atguigu.springcloud</groupId > <artifactId > cloud-api-commons</artifactId > <version > ${project.version}</version > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-actuator</artifactId > <version > 2.7.3</version > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-devtools</artifactId > <scope > runtime</scope > <optional > true</optional > </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 > </dependency > </dependencies >
yml:把微服务地址直接配置在配置文件中,就不用业务类里定义了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 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
config: 负载均衡配置
1 2 3 4 5 6 7 8 9 @Configuration public class ApplicationContextConfig { @Bean @LoadBalanced public RestTemplate getRestTemplate () { return new RestTemplate(); } }
controller
1 2 3 4 5 6 7 8 9 10 11 12 13 @RestController public class OrderNacosController { @Resource private RestTemplate restTemplate; @Value("${service-url.nacos-user-service}") private String serverURL; @GetMapping("/consumer/payment/nacos/{id}") public String paymentInfo (@PathVariable("id") Long id) { return restTemplate.getForObject(serverURL+"/payment/nacos/" +id,String.class); } }
主启动就是正常:
1 2 @SpringBootApplication @EnableDiscoveryClient
启动 nacos/9001/9002/83
3> Nacos 注册中心对比 支持CP 和 AP 之间切换
Nacos的一致性协议: CP+AP
C:所有节点在同一时间看到的数据是一致的(你有的和我有的是一样的)
A:高可用(行不行的都要给我拽一个)
可以使用命令行在 CP 和 AP 之间切换
3. Nacos 作服务配置中心 cloudAlibaba-config-nacos-client3377
pom: 基本以后用 nacos,就导入 config 和 discovery 依赖
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 <dependencies > <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 > <version > 2.7.3</version > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-devtools</artifactId > <scope > runtime</scope > <optional > true</optional > </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 > </dependency > </dependencies >
yml:两个配置文件
Nacos同 springcloud-config 一样,在项目初始化时,要保证先从配置中心进行配置拉取,拉取配置后才能正常启动
springboot中配置文件加载是有优先级的,bootstrap 高于 application
所以全局的放在 bootstrap 自己的放在 application
bootstrap.yml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 server: port: 3377 spring: application: name: nacos-config-client cloud: nacos: discovery: server-addr: localhost:8848 config: server-addr: localhost:8848 file-extension: yaml
配置中心配置的命名公式:
prefix
默认为 spring.application.name
的值,也可以通过配置项spring.cloud.nacos.config.prefix
来配置。
spring.profiles.active
即为当前环境对应的 profile,详情可以参考 Spring Boot文档 。 注意:当 spring.profiles.active
为空时,对应的连接符 -
也将不存在,dataId 的拼接格式变成 ${prefix}.${file-extension}
file-exetension
为配置内容的数据格式,可以通过配置项 spring.cloud.nacos.config.file-extension
来配置。目前只支持 properties
和 yaml
类型。
所以配置文件名为: nacos-config-client-dev.yaml
1 yml是YAML(YAML Ain’t Markup Language)语言的文件
application.yml
1 2 3 spring: profiles: active: dev # 表示开发环境
主启动
1 2 3 4 5 6 7 @SpringBootApplication @EnableDiscoveryClient public class NacosConfigClientMain3377 { public static void main (String[] args) { SpringApplication.run(NacosConfigClientMain3377.class, args); } }
controller
1 2 3 4 5 6 7 8 9 10 11 12 @RestController @RefreshScope public class ConfigClientController { @Value("${config.info}") private String configInfo; @GetMapping("/config/info") public String getConfigInfo () { return configInfo; } }
@Value 中匹配的是从配置中心拉过来的内容;所以写法是按照配置中心里的配置来写的
详情看下头自己写的 YAML 内容
Nacos 配置
名称对应:
启动3377测试:
自带动态刷新:修改Nacos中的yaml配置文件,再次调用查看配置的接口,发现配置已经刷新
nacos中修改配置信息
4. Nacos 多环境配置 就像Maven用groupId、artifactId、version三者来定位jar包在仓库中的位置一样,Nacos也提供了 Namespace 、Data ID 、Group 来确定一个配置文件(或者叫配置集)
实现多环境配置方案:
命名空间 (namesapce)区分:一个命名空间对应一个环境
配置组 (group)区分:命名空间默认public即可,一个组对应一种环境
配置集ID (Data ID)区分:命名空间和组用默认的,通过文件名来区分
1 2 3 4 5 多环境多项目管理: 问题1: 实际开发中,一个系统通常会有dev,test,prod环境;需要保证指定环境启动时能正确读取到Nacos上相应的配置文件 问题2: 一个大型分布式微服务会有很多微服务子项目,每个子项目又会有很多相应的环境
配置管理:
public 命名空间是兜底的,不能删(想删也删不掉)
最外层 namespace 是用于区分部署环境的,Group 和 DataID逻辑上区分目标对象
1 2 3 4 5 默认情况: Namespace = public Group = DEFAULT_GROUP 默认Cluster(集群)是DEFAULT Instance(实例)
1> DataID配置
2> Group 分组 DataID相同,组不同
bootstrap中设置group,设哪个就找哪个
3> Namespace
在不同的 namespace 下建配置就好
使用:在不同 namespace 下创建配置就好
层级结构:namespace > group > data id
5. Nacos 集群和持久化(重要 ) 部署生产集群模式,要在 Linux 环境下部署;使用 MySQL(高可用数据库)
光配到Nacos上还不够,容易出现问题,还要放到数据库中
最终的数据源一致(都汇聚到mysql中)
Nacos 自带一个内嵌的数据库–derby ,若启动多个Nacos,数据会存在一致性问题,或配置丢失问题
所以 Nacos 采用集中式存储的方式来支持集群化部署,目前只支持 mysql
Nacos 支持三种部署方式
单机模式 - 用于测试和单机试用
集群模式- 用于生产环境,确保高可用
多集群模式 - 用于多数据中心场景
单机模式下,把 derby 替换成 mysql,具体参考官网(部署手册 ——– 单机模式支持mysql):https://nacos.io/zh-cn/docs/deployment.html
集群模式:
Linux版的下载安装:
github上下载Linux版本,到虚拟机中,拷贝到 /opt 下解压
1 tar -zxvf nacos-server-1.4.4.tar.gz
解压后,会有一个nacos文件夹;拷贝到新创建的 mynacos 文件夹中
步骤:
Linux 上配置mysql
application.properties 配置修改———把nacos默认的数据库改为指向mysql
Linux 服务器上 nacos 的集群配置 cluster.conf———–梳理出同一集群的端口号,比方三台机器就有三个不同的端口号(IP地址要设置成Linux的地址)
编辑Nacos的启动脚本 start.sh,使它可以接受使用不同端口
Nginx配置,作为负载均衡器
二十、 SpringCloud Alibaba Sentinel 实现熔断和限流 官网:https://sentinelguard.io/zh-cn/docs/introduction.html
下载:Releases · alibaba/Sentinel (github.com)
cmd中运行jar包, 访问localhost:8080,默认用户名和密码 sentinel
1. 演示工程 new Moudle: cloudAlibaba-sentinel-service8401
pom:使用 nacos+sentinel 的话,基本前3个依赖都加
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 <dependencies > <dependency > <groupId > com.alibaba.cloud</groupId > <artifactId > spring-cloud-starter-alibaba-nacos-discovery</artifactId > </dependency > <dependency > <groupId > com.alibaba.csp</groupId > <artifactId > sentinel-datasource-nacos</artifactId > </dependency > <dependency > <groupId > com.alibaba.cloud</groupId > <artifactId > spring-cloud-starter-alibaba-sentinel</artifactId > </dependency > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-openfeign</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 > <version > 2.7.3</version > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-devtools</artifactId > <scope > runtime</scope > <optional > true</optional > </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 > </dependency > </dependencies >
application:
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: 8401 spring: application: name: cloudalibaba-sentinel-service cloud: nacos: discovery: server-addr: localhost:8848 sentinel: transport: dashboard: localhost:8080 port: 8719 management: endpoints: web: exposure: include: '*'
主启动:
@EnableDiscoveryClient ——– 让注册中心能够发现
@SpringBootApplication
controller
1 2 3 4 5 6 7 8 9 10 11 12 13 @RestController public class FlowLimitController { @GetMapping("/testA") public String testA () { return "-----------testA" ; } @GetMapping("/testB") public String testB () { return "-----------testB" ; } }
测试:启动 Nacos、Sentinel,启动微服务
因为Sentinel是懒加载机制,需要调用接口后才能看到信息
2. 流控规则 流量限制控制规则
资源名:唯一名称,默认请求路径
针对来源:Sentinel 可以针对调用者进行限流,填写微服务名,默认default(不区分来源)
阈值类型/单机阈值:
QPS(每秒请求数量):当调用该 api 的QPS达到阈值时,进行限流
线程数:当调用该 api 线程数达到阈值时,进行限流
是否集群
流控模式:
直接:api达到限流条件时,直接限流
关联:当关联的资源达到阈值时,就限流自己
链路:只记录指定链路上的流量(指定资源从入口资源进来的流量,如果达到阈值,就进行限流) 【api级别的限流】
流控效果:
快速失败:直接失败,抛异常
Warm Up:根据codeFactor(冷加载因子,默认3)的值,从阈值➗codeFactor,经过预热时长,才达到QPS阈值
排队等待:匀速排队,让请求匀速通过,阈值类型必须设置为QPS,否则无效
2.1 流控模式
直接: 1s请求1次可以,多了直接报错
访问量1s超过1次:默认报错
思考:当访问失败时,我们需要能够自己处理问题,也就是允许用户自定义
1 2 QPS:每秒允许访问的请求量 线程数:高并发的请求过来(QPS很高),线程数为1,相当于我前台工作人员只有1个,只能接待一个服务,当前服务未结束,后面再来的就要报错
关联: 当关联的资源达到阈值,就限流自己(连坐效应,比方支付模块到阈值,就限流下单)
当与A关联的B达到阈值时,限流A——–B惹事,A挂了
解释:这边压力大,那边就别进来了
模拟:使用 postman 让B不停的请求;访问地址添加进多线程集合组
Run,大批量线程高并发访问B,导致A失败
20个线程结束后,调用A回复正常
3. 流控效果 直接 ==》 快速失败(默认的流控处理)
预热: 冷启动的方式;当系统长期无人问津,突然拉高访问量,容易把系统压垮。通过冷启动,让通过的流量缓慢增加,在一段时间内逐渐达到阈值上限。
默认 coldFactor 为3,即请求QPS从(coldFactor /3)经多少预热时间才逐渐升至设定的QPS阈值
例子:阈值为10,预热时间5s
系统初始化阈值为10/3,约为3,即阈值刚开始为3;经过5s后阈值慢慢升高到10
排队等待: 严格控制请求通过的间隔时间,就是让请求以均匀速度通过;对应漏桶算法
匀速排队,让请求以均匀速度通过,阈值类型必须是QPS,否则无效
设置含义:/testB每秒请求1次,超过就排队等待,等待超时时间为20000ms
测试:postman高并发多线程测试即可(一次一堆请求过来)
4. 熔断规则
慢调用比例
指耗时大于阈值RT的请求称为慢调用,阈值RT由用户设置
请求数大于最小请求数并且慢调用的比率大于比例阈值则发生熔断,熔断时长为用户自定义设置。
异常比例(秒级)
QPS >= 5 且异常比例(秒级统计)超过阈值时,触发降级;时间窗口结束后,关闭降级
异常数(分钟级)
异常数(分钟统计)超过阈值时, 触发降级,时间窗口结束后,关闭降级
5. Sentinel 热点key
热点即经常访问的数据。很多时候我们希望统计某个热点数据中访问频次最高的 Top K数据
代码设置:设置热点规则后的兜底函数
1 2 3 4 5 6 7 8 9 @GetMapping("testHotKey") @SentinelResource(value = "testHotKey", blockHandler = "deal_testHotKey") public String testHotKey (@RequestParam(value = "p1",required = false) String p1, @RequestParam(value = "p2",required = false) String p2) { return "-------testHotKey" ; } public String deal_testHotKey (String p1, String p2, BlockException exception) { return "-------deal_testHotKey,sorry T_T" ; }
方法testHotKey里面第一个参数只要QPS超过每秒1次,马上降级处理
本来应该是报我自定义的返回信息,这块可能是jar包冲突了所以直接成页面报错信息了 SOS
注: 参数中只要有 p1 就会报问题;如果只携带 p2 是不会触发降级的
参数例外项
只要当 p1=5 时,阈值可变为200
1 @SentinelResource 只管配置出错;若是程序出错,改报异常就是异常
6. @SentinelResource 从 HystrixCommand 到 @SentinelResource
8401 项目,pom 导入自定义的 ja r包
1 2 3 4 5 <dependency > <groupId > com.atguigu.springcloud</groupId > <artifactId > cloud-api-commons</artifactId > <version > ${project.version}</version > </dependency >
controller
两种方法都可以
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @RestController public class RateLimitController { @GetMapping("/byResource") @SentinelResource(value = "byResource", blockHandler = "handleException") public CommonResult byResource () { return new CommonResult(200 ,"按资源名称限流测试ok" ,new Payment(2020L ,"serial001" )); } public CommonResult handleException (BlockException exception) { return new CommonResult(444 ,exception.getClass().getCanonicalName()+"\t 服务不可用" ); } @GetMapping("/rateLimit/byUrl") @SentinelResource(value = "byUrl") public CommonResult byUrl () { return new CommonResult(200 ,"按url限流测试ok" ,new Payment(2020L ,"serial002" )); } }
启动 Nacos、Sentinel、8401
blockHandler:设置了就返回自定义的报错信息;没有就返回系统默认的‘
限流是临时的,停掉微服务后规则会直接消失
把降级处理的信息改为统一类,降低 程序的耦合度
新建一个类用于统一返回降级信息:
1 2 3 4 5 6 7 8 public class CustomerBlockHandler { public static CommonResult handlerException (BlockException exception) { return new CommonResult(4444 ,"按客户自定义,global handlerException---------1" ); } public static CommonResult handlerException2 (BlockException exception) { return new CommonResult(4444 ,"按客户自定义,global handlerException---------2" ); } }
controller:测试的接口
1 2 3 4 5 6 7 8 9 10 @GetMapping("/rateLimit/customerBlockHandler") @SentinelResource(value = "customerBlockHandler", blockHandlerClass = CustomerBlockHandler.class, blockHandler = "handlerException2") public CommonResult customerBlockHandler () { return new CommonResult(200 ,"按客户自定义" ,new Payment(2020L ,"serial003" )); }
7. Sentinel 服务熔断 Sentinel 整合 ribbon + openFeign + fallback
服务提供者:cloudAlibaba-provider-payment9003/9004 负载均衡
服务消费者:cloudAlibaba-consumer-nacos-order84
Ribbon系列
1> 创建提供者 9003和9004相同,改下端口号就行
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 <dependencies > <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 > <version > 2.7.3</version > </dependency > <dependency > <groupId > com.atguigu.springcloud</groupId > <artifactId > cloud-api-commons</artifactId > <version > ${project.version}</version > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-devtools</artifactId > <scope > runtime</scope > <optional > true</optional > </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 > </dependency > </dependencies >
yml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 server: port: 9003 spring: application: name: nacos-payment-provider cloud: nacos: discovery: server-addr: localhost:8848 management: endpoints: web: exposure: include: '*'
主启动:
1 2 3 4 5 6 7 @SpringBootApplication @EnableDiscoveryClient public class PaymentMain9003 { public static void main (String[] args) { SpringApplication.run(PaymentMain9003.class, args); } }
controller: 偷懒,用哈希表做数据库
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 @RestController public class PaymentController { @Value("${server.port}") private String serverPort; public static HashMap<Long, Payment> hashMap = new HashMap<>(); static { hashMap.put(1L ,new Payment(1L ,"28a82uj2h8d92hnv" )); hashMap.put(2L ,new Payment(2L ,"bb2jdu9283nd9ch1" )); hashMap.put(3L ,new Payment(3L ,"cv87ekd0293ncf82" )); } @GetMapping("/paymentSQL/{id}") public CommonResult<Payment> paymentSQL (@PathVariable("id") Long id) { Payment payment = hashMap.get(id); CommonResult<Payment> result = new CommonResult(200 ,"from mysql,serverPort: " + serverPort, payment); return result; } }
测试:启动 nacos、sentinel、9003、9004
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 <dependencies > <dependency > <groupId > com.alibaba.cloud</groupId > <artifactId > spring-cloud-starter-alibaba-nacos-discovery</artifactId > </dependency > <dependency > <groupId > com.alibaba.csp</groupId > <artifactId > sentinel-datasource-nacos</artifactId > </dependency > <dependency > <groupId > com.alibaba.cloud</groupId > <artifactId > spring-cloud-starter-alibaba-sentinel</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 > <version > 2.7.3</version > </dependency > <dependency > <groupId > com.atguigu.springcloud</groupId > <artifactId > cloud-api-commons</artifactId > <version > ${project.version}</version > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-devtools</artifactId > <scope > runtime</scope > <optional > true</optional > </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 > </dependency > </dependencies >
yml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 server: port: 84 spring: application: name: nacos-order-consumer cloud: nacos: discovery: server-addr: localhost:8848 sentinel: transport: dashboard: localhost:8080 port: 8719 service-url: nacos-user-service: http://nacos-payment-provider
主启动
1 2 3 4 5 6 7 @SpringBootApplication @EnableDiscoveryClient public class OrderNacosMain84 { public static void main (String[] args) { SpringApplication.run(OrderNacosMain84.class, args); } }
配置类:负载均衡
1 2 3 4 5 6 7 8 @Configuration public class ApplicationContextConfig { @Bean @LoadBalanced public RestTemplate getRestTemplate () { return new RestTemplate(); } }
controller
fallback 管运行异常(主业务逻辑的问题)
blockHandler管配置违规问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @Slf4j @RestController public class CircleBreakerController { public static final String SERVICE_URL = "http://nacos-payment-provider" ; @Resource private RestTemplate restTemplate; @RequestMapping("/consumer/fallback/{id}") @SentinelResource(value = "fallback") public CommonResult<Payment> fallback (@PathVariable Long id) throws IllegalAccessException { CommonResult<Payment> result = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/" + id, CommonResult.class, id); if (id == 4 ) { throw new IllegalAccessException("IllegalAccessException,非法参数异常。。。" ); } else if (result.getData() == null ) { throw new NullPointerException("NullPointerException,该ID没有对应记录,空指针异常" ); } return result; } }
@SentinelResource的配置
1 2 @SentinelResource(value = "fallback", fallback = "handleFallback") @SentinelResource(value = "fallback", blockHandler = "blockHandler")
两个结合使用(报错时各找各妈)
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 @Slf4j @RestController public class CircleBreakerController { public static final String SERVICE_URL = "http://nacos-payment-provider" ; @Resource private RestTemplate restTemplate; @RequestMapping("/consumer/fallback/{id}") @SentinelResource(value = "fallback",fallback = "handleFallback", blockHandler = "blockHandler") public CommonResult<Payment> fallback (@PathVariable Long id) throws IllegalAccessException { CommonResult<Payment> result = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/" + id, CommonResult.class, id); if (id == 4 ) { throw new IllegalAccessException("IllegalAccessException,非法参数异常。。。" ); } else if (result.getData() == null ) { throw new NullPointerException("NullPointerException,该ID没有对应记录,空指针异常" ); } return result; } public CommonResult handleFallback (@PathVariable Long id, Throwable e) { Payment payment = new Payment(id,"null" ); return new CommonResult<>(444 ,"兜底异常handleFallback,exception内容 " + e.getMessage(),payment); } public CommonResult blockHandler (@PathVariable Long id, BlockException blockException) { Payment payment = new Payment(id,"null" ); return new CommonResult<>(445 ,"blockException-sentinel限流,无此流水:blockException " + blockException.getMessage(),payment); } }
在Sentinel中设置熔断规则
问题:如果异常同时触发 fallback 和 blockHandler;应该找谁??
测试:调用 id 为4的接口,本为Java异常问题,但同时触发熔断规则
所以,若 fallback 和 blockHandler 都进行了配置,则被限流降级而抛出 BlockException 时只会进入 blockException 处理逻辑
Feign系列
3> 使用Feign调用微服务 Feign组件一般是消费侧
添加pom
1 2 3 4 5 <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-openfeign</artifactId > </dependency >
yml: 激活 feign
1 2 3 4 feign: hystrix: enabled: true
主启动:添加一个注解
service:
1 2 3 4 5 6 7 8 9 @FeignClient(value = "nacos-payment-provider",fallback = PaymentFallbackService.class) public interface PaymentService { @GetMapping("/paymentSQL/{id}") public CommonResult<Payment> paymentSQL (@PathVariable("id") Long id) ; }
1 2 3 4 5 6 7 8 @Component public class PaymentFallbackService implements PaymentService { @Override public CommonResult<Payment> paymentSQL (Long id) { return new CommonResult<>(444444 ,"服务降级返回,------PaymentFallbackService" ,new Payment(id,"ErrorSerial" )); } }
controller:
1 2 3 4 5 6 7 8 @Resource private PaymentService paymentService;@GetMapping("/consumer/paymentSQL/{id}") public CommonResult<Payment> paymentSQL (@PathVariable("id") Long id) { return paymentService.paymentSQL(id); }
测试:
关闭9003、9004;服务降级,不会拖垮服务器
4> 熔断框架比较
Sentinel
Hystrix
隔离策略
信号量隔离(并发线程数限流)
线程池隔离/信号量隔离
熔断降级策略
基于响应时间、异常比率、异常数
基于异常比率
实时统计实现
滑动窗口(LeapArray)
滑动窗口(基于RxJava)
动态规则配置
支持多种数据源
支持多种数据源
扩展性
多个扩展点
插件的形式
基于注解的支持
支持
支持
限流
基于QPS,支持基于调用关系的限流
有限的支持
流量整形
支持预热模式、匀速器模式、预热排队模式
不支持
系统自适应保护
支持
不支持
控制台
提供开箱即用的控制台,可配置规则、查看秒级监控、机器发现等
简单的监控查看
8. 规则持久化 存在问题:重启微服务后,Sentinel 规则会消失,生产环境需要将配置规则持久化
解决:规则配置注册进 Nacos 中
修改 cloudAlibaba-sentinel-service8401
pom:添加
1 2 3 4 5 <dependency > <groupId > com.alibaba.csp</groupId > <artifactId > sentinel-datasource-nacos</artifactId > </dependency >
yml
1 2 3 4 5 6 7 8 datasource: ds1: nacos: server-addr: localhost:8848 dataId: cloudalibaba-sentinel-service groupId: DEFAULT_GROUP data-type: json rule-type: flow
Nacos新增配置
1 2 3 4 5 6 7 8 9 10 11 [ { "resource" : "/rateLimit/byUrl" , "limitApp" : "default" , "grade" : 1 , "count" : 1 , "strategy" : 0 , "controlBehavior" : 0 , "cluserMode" : false } ]
resource:资源名称
limitApp:来源应用
grade:阈值类型,0表示线程数,1表示QPS
count:单机阈值
strategy:流控模式,0表示直接,1表示关联,2表示链路
controlBehavior:流控效果,0表示快速失败,1表示Warm Up,2表示排队等待
cluserMode:是否集群
测试:启动8401,访问接口 http://localhost:8401/rateLimit/byUrl
快速点击:规则生效
重启 8401 后,再次调用接口,查看 Sentinel,规则重新出现
二十一、SpringCloud Alibaba Seata 处理分布式事务 分布式之后,单体应用都被拆分成微服务应用,原来的三个模块被拆分成三个独立的应用,分别使用三个独立的数据源,业务操作也需要调用三个服务来完成。此时每个服务内部的数据一致性由本地事务来保证,但是全局的数据一致性就无法保证。
我们需要解决的问题就是:如何保证全局数据的一致性
1. Seata 简介 Seata是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事件
官网:https://seata.io/zh-cn/
一个典型的分布式事务过程:分布式事务处理过程的 一ID+三组件模型
XID:全局唯一的事务ID
3组件概念
TC——-事务协调者:维护全局和分支事务的状态,驱动全局事务提交或回滚
TM——-事务管理器:定义全局事务的范围:开始全局事务、提交或回滚全局事务
RM——-资源管理器:管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务的提交或回滚
处理过程:
TM 向 TC 申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的 XID
(各个班的班主任TM向授课老师TC申请一个班级号)
XID 在微服务调用链路的上下文中传播
(班级号在学生们之间传播,让他们加入班级)
RM 向 TC 注册分支事务,将其纳入 XID 对应全局事务的管辖
(学生们加入授课老师的课堂,授课老师可以统一看到所有学生)
TM 向 TC 发起针对 XID 的全局提交或回滚协议
(班主任发起签到,然后告诉老师可以上课了;老师可以屏幕共享到所有学生)
TC 调度 XID 下管辖的全部分支事务完成提交或回滚请求
(下课休息,告知全部学生)