Spring Cloud简述 Spring Cloud微服务是由多功能模块组成的,每个模块实现耦合性降低。
注意微服务是多个模块进行组合,我们对版本的控制必须严谨。
通过url:
https://start.spring.io/actuator/info 获取json数据,查看版本对应关系。
或 https://spring.io/projects/spring-cloud#learn 自行查看版本对应关系。
Spring Cloud模块分布如下:
注册中心:Nacos、Eureka、Zookeeper、Consul
服务调用:LoadBalancer、Ribbon、OpenFeign(Feign)
服务熔断:Sentinel、Resilience4j、Hystrix
服务网关:Gateway、Zuul
服务配置:Nacos、Config
服务总线:Nacos、Bus
Spring Cloud Alibaba官网: https://github.com/alibaba/spring-cloud-alibaba/blob/master/README-zh.md
Spring Cloud Alibaba组件版本关系: https://github.com/alibaba/spring-cloud-alibaba/wiki/%E7%89%88%E6%9C%AC%E8%AF%B4%E6%98%8E
基础回顾 先开启一个干净的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 <?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.tang</groupId > <artifactId > SpringCloud</artifactId > <version > 1.0-SNAPSHOT</version > <packaging > pom</packaging > <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.18.20</lombok.version > <mysql.version > 5.1.47</mysql.version > <druid.version > 1.1.17</druid.version > <mybatis.spring.boot.version > 2.1.4</mybatis.spring.boot.version > </properties > <dependencyManagement > <dependencies > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-dependencies</artifactId > <version > 2.3.2.RELEASE</version > <type > pom</type > <scope > import</scope > </dependency > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-dependencies</artifactId > <version > Hoxton.SR9</version > <type > pom</type > <scope > import</scope > </dependency > <dependency > <groupId > com.alibaba.cloud</groupId > <artifactId > spring-cloud-alibaba-dependencies</artifactId > <version > 2.2.6.RELEASE</version > <type > pom</type > <scope > import</scope > </dependency > <dependency > <groupId > mysql</groupId > <artifactId > mysql-connector-java</artifactId > <version > ${mysql.version}</version > </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 > <dependency > <groupId > org.projectlombok</groupId > <artifactId > lombok</artifactId > <version > ${lombok.version}</version > <optional > true</optional > </dependency > </dependencies > </dependencyManagement > <build > <plugins > <plugin > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-maven-plugin</artifactId > <configuration > <fork > true</fork > <addResources > true</addResources > </configuration > </plugin > </plugins > </build > </project >
模块配置 + 依赖 + 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 <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.mybatis.spring.boot</groupId > <artifactId > mybatis-spring-boot-starter</artifactId > </dependency > <dependency > <groupId > com.alibaba</groupId > <artifactId > druid-spring-boot-starter</artifactId > <version > 1.1.10</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 >
application.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.jdbc.Driver url: jdbc:mysql://localhost:3306/spring_cloud?useSSL=false&useUnicode=true&characterEncoding=UTF-8 username: root password: 111111 mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.tang.springcloud.entity
微服务支付服务端 entity-dao-service-controller 我们按照前后端分离的规范,每次执行操作后,向前端返回状态码信息,所以还要写一个状态码的封装类
CommonResult
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 ); } }
PaymentMapper
1 2 3 4 5 6 @Mapper public interface PaymentMapper { public int create (Payment payment) ; public Payment getPaymentById (@Param("id") Long id) ; }
PaymentMapper.xml
为了规范一般都会自行封装一个resultmap作为返回类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <?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.tang.springcloud.dao.PaymentMapper" > <insert id ="create" parameterType ="Payment" useGeneratedKeys ="true" keyProperty ="id" > insert into payment(serial) values(#{serial}); </insert > <resultMap id ="BaseResultMap" type ="com.tang.springcloud.entity.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 >
PaymentServiceImpl
接口省略
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Service public class PaymentServiceImpl implements PaymentService { @Resource private PaymentMapper paymentMapper; @Override public int create (Payment payment) { return paymentMapper.create(payment); } @Override public Payment getPaymentById (Long id) { return paymentMapper.getPaymentById(id); } }
PaymentController
对应url的http方法:post增、delete删、put改、get查
注意对象参数要使用@RequestBody,接收客户端发送的数据
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 @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(404 , "插入失败" , 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(404 , "查询失败,失败id是" + id, null ); } } }
测试功能 由于浏览器限制,我们可以使用Postman测试post、get等请求
localhost:8001/payment/get/1
localhost:8001/payment/create?serial=wewewew
1 2 3 4 5 6 7 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-devtools</artifactId > <scope > runtime</scope > <optional > true</optional > </dependency >
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 > <configuration > <fork > true</fork > <addResources > true</addResources > </configuration > </plugin > </plugins > </build >
在setting–Build–Compiler下勾选,以下四个开头的条件:
Auto···、Display···、Build···、Compile···。
组合键:ctrl + alt + shift + 左斜杠,选中1、Registry···,勾选:
compiler.automake.allow.when.app.running
actionSystem.assertFocusAccessFromEdt
微服务客户端 entity-controller 客户肯定是不能操作业务的,所以我们肯定没有service进行调用,使用RestTemplate 。
RestTemplate是HTTP请求工具,提供了常见的请求方案模板。
这里客户端使用http默认端口80,不用主动指明端口号,因为客户是不用关心端口的,只用输入对应的url即可。
客户端:http://localhost/consumer/payment/get/1
无需端口号,对服务器进行访问。
工程重构 我们发现客户端与服务端实体类重复,希望进行复用操作。单独分一个模块方实体类、工具类等。
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.1.0</version > </dependency > </dependencies >
然后把实体类和工具类都写到这个模块,在maven工具上进行clean-install操作打包,接下来我们去之前的模块把实体类删除,在pom中引用改模块的依赖即可导入。
1 2 3 4 5 6 <dependency > <groupId > com.tang</groupId > <artifactId > cloud-api-common</artifactId > <version > ${project.version}</version > </dependency >
注册中心 Eureka 服务的中转站,发现并维护服务,让后提供服务给客户,比如长时间未使用的服务则自行删除,不再提供给客户。
Eureka分为服务端与客户端,我们需要在注册中心的启动器入口上进行服务端配置,其余组件进行客户端配置。
Eureka Server
1 2 3 4 <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-netflix-eureka-server</artifactId > </dependency >
Eureka Client
1 2 3 4 <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-netflix-eureka-client</artifactId > </dependency >
基础配置(服务端) 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.tang</groupId > <artifactId > cloud-api-common</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 > </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 > </dependency > </dependencies >
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: defalutZone: http://${eureka.instance.hostname}:${server.port}/eureka/
1 2 3 4 5 6 7 @SpringBootApplication @EnableEurekaServer public class EurekaMain7001 { public static void main (String[] args) { SpringApplication.run(EurekaMain7001.class, args); } }
基础配置(客户端) pom导入Eureka-client的依赖。
1 2 3 4 5 6 7 eureka: client: register-with-eureka: true fetch-registry: true service-url: defaultZone: http://localhost:7001/eureka
在启动入口上添加注解:@EnableEurekaServer。
然后在7001端口的eureka可以看到注册进来的8001组件。
Eureka集群 Eureka包括多台服务器,每台服务器都会注册除自己以外的所有服务器,保证能获取其他服务器的信息。以便切换。
修改host文件,加入自己eureka的多机ip配置
1 2 127.0.0.1 eureka7001.com 127.0.0.1 eureka7002.com
服务器修改yml,每个服务器配置其他所有服务器,逗号分隔。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 server: port: 7001 eureka: instance: hostname: eureka7001.com client: register-with-eureka: false fetch-registry: false service-url: defaultZone: http://eureka7002.com:7002/eureka
客户端修改yml
1 2 3 4 5 6 7 eureka: client: register-with-eureka: true fetch-registry: true service-url: defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
然后我们发现集群的eureka都注册了实例,且集群之间服务器都相互加载。
支付服务端集群 拷贝一个8002支付工程,和8001一模一样,注意改端口号。在Controller中添加以下内容,并在后续操作中返回端口号。
1 2 3 4 @Value("${server.port}") private String serverPort;return new CommonResult(200 , "查询成功,serverPort:" + serverPort, payment);
此时客户端不能写死端口号,转变成微服务名,且要开启负载均衡,因为集群服务名相同,没有配置负载均衡则无法识别。
1 2 3 4 5 6 7 8 9 10 private static final String PAYMENT_URL = "http://cloud-payment-service" ;@Bean @LoadBalanced public RestTemplate getRestTemplate () { return new RestTemplate(); }
配置好后发现集群服务器会交替使用,负载均衡。
完善处理 在eureka界面发现各个服务的默认名是带有主机的,很冗杂,可以自定义名称,并且可以开启服务的正确ip跳转。
1 2 3 4 eureka: instance: instance-id: payment8001 prefer-ip-address: true
服务发现Discovery 配置支付服务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Resource private DiscoveryClient discoveryClient;@GetMapping(value = "/payment/discovery") public Object discovery () { List<String> services = discoveryClient.getServices(); for (String s : services){ log.info("服务:" + s); } 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 需要加到服务启动类上开启服务发现
Eureka自我保护机制 当一个微服务不能使用时,eureka不会马上进行清理,而是对该服务信息进行保存。
在分布式架构CAP理论中属于AP分支。
Zookeeper apache官方下载zookeeper,在linux完成解压
https://dlcdn.apache.org/zookeeper/zookeeper-3.6.3/apache-zookeeper-3.6.3-bin.tar.gz
解压后进入bin目录
1 2 ./zkServer.sh start ./zkCli.sh
支付业务注册到zookeeper 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.cloud</groupId > <artifactId > spring-cloud-starter-zookeeper-discovery</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 >
1 2 3 4 5 6 7 8 9 server: port: 8004 spring: application: name: cloud-provider-payment cloud: zookeeper: connect-string: 192.168 .158 .137 :2181
1 2 3 4 5 6 7 @SpringBootApplication @EnableDiscoveryClient public class PaymentMain8004 { public static void main (String[] args) { SpringApplication.run(PaymentMain8004.class, args); } }
完成基础配置,启动服务,在linux查看zookeeper注册情况是否成功
1 2 3 4 [zk: localhost:2181(CONNECTED) 4] ls / [services, zookeeper] [zk: localhost:2181(CONNECTED) 5] ls /services [cloud-provider-payment]
业务测试
1 2 3 4 5 6 7 8 9 10 11 @RestController @Slf4j public class PaymentController { @Value("${server.port}") private String serverPort; @RequestMapping(value = "/payment/zk") public String paymentzk () { return "springcloud with zookeeper:" + serverPort + "\t" + UUID.randomUUID().toString(); } }
走http://localhost:8004/payment/zk,发现uuid返回成功
节点类型 可对比Eureka自我保护,zookeeper是临时节点,当服务宕机后,过了一个心跳时间不会保存服务。重启服务后则是新开的一个服务。
客户端调用服务 pom服务端一致
1 2 3 4 5 6 7 8 9 server: port: 80 spring: application: name: cloud-consumer-order cloud: zookeeper: connect-string: 192.168 .158 .138 :2181
controller调服务,template需要先注册,和之前一样
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @RestController @Slf4j public class OrderZKController { public static final String INVOKE_URL = "http://cloud-provider-payment" ; @Resource private RestTemplate restTemplate; @GetMapping(value = "/consumer/payment/zk") public String paymentInfo () { String result = restTemplate.getForObject(INVOKE_URL + "/payment/zk" , String.class); return result; } }
最后发现客户端可以使用,且linux上的zookeeper也能发现两个服务。
Consul 官方下载windows版本:https://www.consul.io/downloads
解压后进入文件目录命令行执行consul agent -dev 开启consul可视化界面
http://localhost:8500/ui/dc1/services 访问可视化界面
服务端注册 相关配置:
1 2 3 4 <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-consul-discovery</artifactId > </dependency >
1 2 3 4 5 6 7 8 9 10 11 12 server: port: 8006 spring: application: name: consul-provider-payment cloud: consul: host: localhost port: 8500 discovery: service-name: ${spring.application.name}
业务和zookeeper一样,进行简单端口返回测试,查看consul是否注册成功。主启动类使用@EnableDiscoveryClient。
客户端注册调用服务 pom同服务端。
1 2 3 4 5 6 7 8 9 10 11 12 server: port: 80 spring: application: name: consul-consumer-order cloud: consul: host: localhost port: 8500 discovery: service-name: ${spring.application.name}
config注册template组件,然后controller使用网络连接调用服务端。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @RestController @Slf4j public class OrderZKController { public static final String INVOKE_URL = "http://consul-provider-payment" ; @Resource private RestTemplate restTemplate; @GetMapping(value = "/consumer/payment/consul") public String paymentInfo () { String result = restTemplate.getForObject(INVOKE_URL + "/payment/consul" , String.class); return result; } }
最后url测试,服务调用成功。
注册中心的选择 注册中心作用都一样,但每一个在分布式系统中的定位不同,也就是CAP实现不同。
C:Consistency(强一致性),所有节点在集群中具有相同数据
A:Avaliability(可用性),保证请求无论成功失败都有响应,但可能接收到的数据节点是过时或错的。
P:Partition(分区容错性),对于分布式分区容错是必备的,也就是集群中部分机器宕机,不会影响整个系统的运作。
一般来说分布式系统必须实现P,但全部特性又不能同时满足,所以一般是CP、AP两种方式。
CA:单点集群,满足一致性,一般不利于扩展。、
CP(Zookeeper、Consul): 满足一致性、分区容错,但性能不高。
AP(Eureka): 满足可用性、分区容错,对一致性要求低。
服务调用(负载均衡) Ribbon 我们在eureka集群环境下测试该技术
Ribbon主要实现负载均衡(Load Balance)
集中式LB(Nginx):在服务消费者与提供方之间独立的负载均衡设施,如Nginx反向代理,由该设备将请求通过策略转发给服务的提供方。
进程内LB(Ribbon):将负载均衡逻辑集成到消费者,由消费者从注册中心选取合适的服务器进行调用。
对应依赖(不适用):
1 2 3 4 5 <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-netflix-ribbon</artifactId > <version > 2.2.6.RELEASE</version > </dependency >
RestTemplate 我们在注册中心的客户端就使用过@LoadBalance + RestTemplate完成服务调用及负载均衡。这里要说说RestTemplate。
RestTemplate对应get、post都有两种请求方法,这里以get举例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @GetMapping("/consumer/payment/get/{id}") public CommonResult<Payment> getPayemntById (@PathVariable("id") Long id) { return restTemplate.getForObject(PAYMENT_URL + "/payment/get/" + id, CommonResult.class); } @GetMapping("/consumer/payment/getForEntity/{id}") public CommonResult<Payment> getPayemntById2 (@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<>(404 , "操作失败" ); } }
getForObject:获取响应数据,类似一个json
getForEntity:获取整个响应,信息更多。
分情况使用,post和get一样,只是换了前缀。
负载均衡算法替换 Ribbon依赖中有一个接口IRule,一般实现该接口的类就对应了一种算法,抽象类除外。有轮询、随机等。
我们不想使用默认的轮询算法。可以自行构造一个新算法规则,但官方规定这个规则不应该放在@ComponentScan能扫描的包中,这样做不到特殊化,也就是说我们要跳出主启动类所在包,新建一个规则包来实现规则。
注意坑,现在版本eureka-client没有集成ribbon,所以不能直接使用IRule自定义策略,需要去导入Ribbon包,但我执行导入Ribbon运行都会冲突报错,没找到原因,只能说老技术没人维护是这样的,赶紧往后学Nacos配合Feign,负载均衡策略都可以在yml改,不用自定义类过于麻烦。
解决办法:https://blog.csdn.net/Curtain_show01/article/details/116838815
或 https://www.cnblogs.com/minejava/p/14851495.html
我们使用Spring Cloud LoadBalancer顶替Ribbon,LoadBalancer被Eureka所集成
1 2 3 4 5 6 7 8 9 10 public class MyLoadBalance { @Bean ReactorLoadBalancer<ServiceInstance> randomLoadBalancer (Environment environment, LoadBalancerClientFactory loadBalancerClientFactory) { String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME); return new RandomLoadBalancer(loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name); } }
1 2 3 4 5 6 7 8 9 10 @Configuration @LoadBalancerClient(name = "cloud-payment-service", configuration = MyLoadBalance.class) public class ApplicationContextConfig { @Bean @LoadBalanced public RestTemplate getRestTemplate () { return new RestTemplate(); } }
然后我们再使用改客户端即可完成随机算法的负载均衡。
1 2 @LoadBalancerClients(defaultConfiguration = MyLoadBalance.class)
OpenFeign 传统Ribbon + RestTemplate对http请求封装,形成模板化调用方法,而Feign在该基础上进一步封装,通过接口 + 注解进行配置,进一步简化操作。(类比Dao层配合Mapper注解)
服务调用 基础配置:
1 2 3 4 <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-openfeign</artifactId > </dependency >
1 2 3 4 5 6 7 8 server: port: 80 eureka: client: register-with-eureka: false service-url: defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/
service对应你要调用的服务端服务,我们原本业务是使用RestTemplate + 逻辑自行实现Controller,而OpenFeign是先定义服务的接口,指明服务与业务。
1 2 3 4 5 6 @Component @FeignClient(value = "cloud-payment-service") public interface PaymentFeignService { @GetMapping(value = "/payment/get/{id}") CommonResult<Payment> getPaymentById (@PathVariable("id") Long id) ; }
Controller直接获取业务接口的实例,通过它调用服务端的业务,不用注入RestTemplate。
1 2 3 4 5 6 7 8 9 10 11 @RestController @Slf4j public class OrderFeignController { @Resource private PaymentFeignService paymentFeignService; @GetMapping(value = "/consumer/payment/get/{id}") public CommonResult<Payment> getPaymetById (@PathVariable("id") Long id) { return paymentFeignService.getPaymentById(id); } }
主启动类使用@EnableFeignClients,开启OpenFeign
1 2 3 4 5 6 7 @SpringBootApplication @EnableFeignClients public class OrderFeignMain80 { public static void main (String[] args) { SpringApplication.run(OrderFeignMain80.class, args); } }
测试客户端链接,发现能调用业务,且开启了轮询负载均衡。
超时控制 OpenFeign默认等待服务1s,我们模拟业务等待3s,客户端调用后会报错
1 2 3 4 5 6 7 8 9 10 @GetMapping(value = "/payment/feign/timeout") public String paymentFeignTimeOut () { try { TimeUnit.SECONDS.sleep(10 ); } catch (InterruptedException e) { e.printStackTrace(); } return serverPort; }
1 2 3 4 5 6 7 8 @GetMapping(value = "/payment/feign/timeout") String paymentFeignTimeOut () ;@GetMapping(value = "/consumer/payment/feign/timeout") public String paymentFeignTimeOut () { return paymentFeignService.paymentFeignTimeOut(); }
1 2 3 ribbon: ReadTimeout: 1000 ConnectTimeout: 1000
配置yml可延长超时时间。
ps:不知道是什么问题,我客户端一直在等待,没有默认1s的判断???不管了反正只是一个小点
OpenFeign自带日志输出
NONE:默认不显示日志
BASIC:记录请求方法、URL、响应状态码、执行时间
HEADERS:除BASIC定义的信息外,还有请求和响应的头信息
FULL:除HEADERS定义的信息外,还有请求与响应的正文、元数据
1 2 3 4 5 6 7 8 @Configuration public class FeignConfig { @Bean Logger.Level feignLogLevel () { return Logger.Level.FULL; } }
1 2 3 4 logging: level: com.tang.springcloud.service.PaymentFeignService: debug
Hystrix 当微服务配置多了,一个微服务会调用多个微服务,也就是扇出(广播),而其中某一个微服务出现问题,会影响其他所有微服务,好比雪崩。简单就是说高可用被破坏。
Hystrix在某一个服务出现问题时,不会导致整个服务全部失败,避免级联故障,可以提高分布式系统的弹性。
基础服务构建
1 2 3 4 5 <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-netflix-hystrix</artifactId > <version > 2.2.9.RELEASE</version > </dependency >
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: fetch-registry: true register-with-eureka: true service-url: defaultZone: http://eureka7001.com/7001/eureka
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Service public class PaymentService { public String paymentInfo_OK (Integer id) { return "线程池:" + Thread.currentThread().getName() + "paymentInfo_OK, id:" + id + "\t" ; } public String paymentInfo_TimeOut (Integer id) { int time = 3 ; try { TimeUnit.SECONDS.sleep(time); } catch (InterruptedException e) { e.printStackTrace(); } return "线程池:" + Thread.currentThread().getName() + "paymentInfo_TimeOut, id:" + id + "\t" + "耗时" + time + "s" ; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @RestController public class PaymentController { @Resource private PaymentService paymentService; @GetMapping(value = "/payment/hystrix/ok/{id}") public String paymentInfo_OK (@PathVariable("id") Integer id) { String result = paymentService.paymentInfo_OK(id); return result; } @GetMapping(value = "/payment/hystrix/timeout/{id}") public String paymentInfo_TimeOut (@PathVariable("id") Integer id) { String result = paymentService.paymentInfo_TimeOut(id); return result; } }
1 2 3 4 5 6 7 8 9 <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 > <version > 2.2.9.RELEASE</version > </dependency >
1 2 3 4 5 6 7 8 server: port: 80 eureka: client: register-with-eureka: false service-url: defaultZone: http://eureka7001.com:7001/eureka
1 2 3 4 5 6 7 8 9 10 @Component @FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT") public interface PaymentHystrixService { @GetMapping(value = "/consumer/payment/hystrix/ok/{id}") String paymentInfo_OK (@PathVariable("id") Integer id) ; @GetMapping(value = "/consumer/payment/hystrix/timeout/{id}") String paymentInfo_TimeOut (@PathVariable("id") Integer id) ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @RestController public class OrderHystrixController { @Resource private PaymentHystrixService paymentHystrixService; @GetMapping(value = "/payment/hystrix/ok/{id}") String paymentInfo_OK (@PathVariable("id") Integer id) { String result = paymentHystrixService.paymentInfo_OK(id); return result; } @GetMapping(value = "/payment/hystrix/timeout/{id}") String paymentInfo_TimeOut (@PathVariable("id") Integer id) { String result = paymentHystrixService.paymentInfo_TimeOut(id); return result; } }
如果我们现在对服务端施加压力测试,多线程执行服务,那么客户端和服务端执行操作时都会变慢,加载时间变长,此时需要相应措施。
服务降级(fallback) 服务器压力剧增,可对不重要的服务进行延迟或暂停处理,以便释放服务器资源,保证核心服务正常运行。
服务端设置服务时间阈值,若超时则执行兜底方法处理,也就是进行降级操作。且以下情况都会发生服务降级:程序运行异常 + 超时 。
注意,以下配置超时时间,不配置则使用默认值1s。
服务端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandle", commandProperties = { @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value="3000") }) public String paymentInfo_TimeOut (Integer id) { int time = 5 ; try { TimeUnit.SECONDS.sleep(time); } catch (InterruptedException e) { e.printStackTrace(); } return "线程池:" + Thread.currentThread().getName() + "paymentInfo_TimeOut, id:" + id + "\t" + "耗时" + time + "s" ; } public String paymentInfo_TimeOutHandle (Integer id) { return "线程池:" + Thread.currentThread().getName() + "paymentInfo_TimeOutHandle, id:" + id + "\t" ; }
客户端 可以发现两端对应时间配置可以不一样的,这里服务响应是3s,刚刚服务端配置5s可以响应,现在客户端只配置1s则不能响应。
1 2 3 4 5 6 7 8 9 10 11 12 @HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandle", commandProperties = { @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value="1000") }) @GetMapping(value = "/consumer/payment/hystrix/timeout/{id}") String paymentInfo_TimeOut (@PathVariable("id") Integer id) { String result = paymentHystrixService.paymentInfo_TimeOut(id); return result; } public String paymentInfo_TimeOutHandle (Integer id) { return "线程池:" + Thread.currentThread().getName() + "paymentInfo_TimeOutHandle, id:" + id + "\t" ; }
技术重构–全局fallback配置 对于兜底方法可以进一步简化,设置一个全局兜底,没有自行配置则走整个方法,不用每次服务降级都要自行配置兜底方法。以下为改变后的客户端,使用 @DefaultProperties 全局兜底
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 @RestController @DefaultProperties(defaultFallback = "paymentGlobalFallbackMethod", commandProperties = { @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "5000") }) public class OrderHystrixController { @Resource private PaymentHystrixService paymentHystrixService; @GetMapping(value = "/consumer/payment/hystrix/ok/{id}") String paymentInfo_OK (@PathVariable("id") Integer id) { String result = paymentHystrixService.paymentInfo_OK(id); return result; } @HystrixCommand @GetMapping(value = "/consumer/payment/hystrix/timeout/{id}") String paymentInfo_TimeOut (@PathVariable("id") Integer id) { String result = paymentHystrixService.paymentInfo_TimeOut(id); return result; } public String paymentGlobalFallbackMethod () { return "全局异常,稍后重试" ; } }
全局fallback,可应对服务端宕机 1 2 3 4 feign: circuitbreaker: enabled: true
1 2 3 4 5 6 7 8 9 10 11 12 13 @Component public class PaymentFallbackService implements PaymentHystrixService { @Override public String paymentInfo_OK (Integer id) { return "paymentInfo_OK,NO" ; } @Override public String paymentInfo_TimeOut (Integer id) { return "paymentInfo_TimeOut,NO" ; } }
1 2 @FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT", fallback = PaymentFallbackService.class)
经过实验,使用该方法开启全局fallback,可应对服务端宕机,宕机后不会显示服务404,而是走全局fallback进行服务提醒。而且注释前面的 @HystrixCommand + @DefaultProperties,我们服务超时和运行异常也会走这个全局fallback,可以说这个实现接口的全局fallback是最佳方案,其他几种可用来配置特殊情况。
服务熔断 可类比保险丝,当某个服务不可用或响应超时时,急时停止该服务,防止系统雪崩现象出现。而当检测到某个微服务响应正常后,恢复调用链路。
HystrixCommandProperties ,这个抽象类包含了我们可以配置的参数信息。
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"), //请求时间ms @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "60") //失败率,达到就熔断 }) public String paymentCircuitBreaker (@PathVariable("id") Integer id) { if (id < 0 ){ throw new RuntimeException("------负数-------" ); } String serialNumber = IdUtil.simpleUUID(); return Thread.currentThread().getName()+"\t" +"调用成功,流水号: " + serialNumber; } public String paymentCircuitBreaker_fallback (@PathVariable("id") Integer id) { return "id 不能是负数,请稍后再试,id: " +id; }
1 2 3 4 5 6 @GetMapping(value = "/payment/circuit/{id}") public String paymentCircuitBreaker (@PathVariable("id") Integer id) { String result = paymentService.paymentCircuitBreaker(id); return result; }
解释以下配置的参数,开启断路器,若10s内10次请求失败率达到60%,则熔断。而后续正确回升则会恢复链路。我们测试时先输入正数会成功调用返回uuid,输入负数则抛异常走兜底方法处理。但10s内多次失败后,我们再去输入整数,返回的是兜底方法,因为失败率达到60%被熔断了,而我们后续多次调用正数,失败率降低,又可以走正常方法返回uuid。
熔断后不再走正常逻辑,而是执行服务降级的兜底方法,但熔断可以自行恢复链路重新执行正常逻辑。
工作流程
图形化Dashboard 1 2 3 4 5 <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-netflix-hystrix-dashboard</artifactId > <version > 2.2.9.RELEASE</version > </dependency >
1 2 3 4 5 6 7 @SpringBootApplication @EnableHystrixDashboard public class HystrixDashboardMain9001 { public static void main (String[] args) { SpringApplication.run(HystrixDashboardMain9001.class, args); } }
1 2 3 4 5 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-actuator</artifactId > </dependency >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @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; }
访问仪表盘界面:http://localhost:9001/hystrix,输入 http://localhost:8001/hystrix.stream 对服务进行图形化监控,然后我们就可以监控服务熔断demo的流程。
Gateway网关 工作流程
路由(Route):匹配规则,由一系列断言和过滤器组成,若断言匹配成功则选择该路由。
断言(Predicate):可以匹配HTTP请求的全部内容,断言判断为true则继续进行路由,即选择路由的路径。
过滤(Filter):Spring的GatewayFilter实例,使用过滤器可在请求被路由前后对请求进行修改。
客户端经过网关的路由、Predicate、以及过滤器的筛选后达到服务端。这个过程是双向的,其中过滤器分为pre、post两类,pre是过滤客户端到服务端,post过滤服务端到客户端。
网关搭建 1 2 3 4 5 <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-gateway</artifactId > </dependency >
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: gateway: routes: - id: payment_route uri: http://localhost:8001 predicates: - Path=/payment/get/** - id: payment_route2 uri: http://localhost:8001 predicates: - Path=/payment/feign/** eureka: client: register-with-eureka: true fetch-registry: true service-url: defaultZone: http://eureka7001.com:7001/eureka
然后运行8001服务与9527网关,分别访问;
http://localhost:8001/payment/feign/timeout 和 http://localhost:9527/payment/feign/timeout
可以通过9527访问8001的服务,返回的端口号依旧是8001,通过网关进行中转,我们无需暴露真实服务端口。
动态路由配置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 spring: application: name: cloud-gateway cloud: gateway: discovery: locator: enabled: true routes: - id: payment_route uri: lb://cloud-payment-service predicates: - Path=/payment/get/** - id: payment_route2 uri: lb://cloud-payment-service predicates: - Path=/payment/feign/**
先开启动态路由配置,然后对网关配置中写死的uri 进行替换,从注册中心获取动态路由,格式是lb://微服务名,断言路径不变。
常用的preticates 官方配置:https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.1.RELEASE/reference/html/#gateway-request-predicates-factories
1 2 3 4 5 6 7 - id: payment_route2 uri: lb://cloud-payment-service predicates: - Path=/payment/feign/** - After=2021-10-17T10:40:00.781+08:00[Asia/Shanghai]
示例:我们将上面的路由设置After时区,规定只有在这段时间之后服务网关才会生效,类似的还有before、between
1 2 predicates: - Cookie=username,tang
1 2 3 curl http://localhost:9527/payment/feign/timeout curl http://localhost:9527/payment/feign/timeout --cookie "username=tang"
设置cookie的kv键值对,只有对应键值对的请求可以正常访问。
1 2 predicates: - Header=X-Request-Id, \d+
请求头要有X-Request-Id这个属性,且值为整数(正则表达式)
1 2 curl http://localhost:9527/payment/feign/timeout -H "X-Request-Id:123"
Filter 可在请求被路由前后对请求进行处理。
官方配置:https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.1.RELEASE/reference/html/#gatewayfilter-factories
1 2 3 4 filters: - AddRequestHeader=X-Request-Id,123
推荐使用全局Filter,功能更丰富,可自行配置日志输出。主要是实现两个接口,GlobalFilter和Ordered
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @Component @Slf4j public class LogGatewayFilter implements GlobalFilter , Ordered { @Override public Mono<Void> filter (ServerWebExchange exchange, GatewayFilterChain chain) { log.info("FilterLog" + new Date()); String name = exchange.getRequest().getQueryParams().getFirst("name" ); if (name == null ){ log.info("用户名为null???" ); exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE); return exchange.getResponse().setComplete(); } return chain.filter(exchange); } @Override public int getOrder () { return 0 ; } }
http://localhost:9527/payment/get/1?name=tang 符合过滤器的规则可以正常访问。
http://localhost:9527/payment/get/1?username=xxx 只要不符合直接404,无连接,后台也会打印日志。
服务配置 Config 服务端配置 每个微服务都有一个配置文件,但服务多了以后一个个修改配置效率太低并任意出错,所以使用配置中心避免重复配置。为不同的微服务环境提供中心化的外部配置。这个外部配置指的是把配置文件上传到github/gitee上,然后通过yml读取仓库的配置。
1 2 3 4 5 <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-config-server</artifactId > <version > 3.0.4</version > </dependency >
1 2 3 4 5 6 7 @SpringBootApplication @EnableConfigServer public class ConfigMain3344 { public static void main (String[] args) { SpringApplication.run(ConfigMain3344.class, args); } }
http://config-3344.com:3344/master/config-dev.yml
http://config-3344.com:3344/master/config-prod.yml
http://config-3344.com:3344/master/config-test.yml
通过以上格式,/分支/文件名读取配置
客户端配置 application.yml 是用户级资源配置,bootstrap.yml 是系统级资源配置,优先级更高。我们为了引入外部的配置文件,需要使用bootstrap.yml,其先于application加载,我们拉取外部配置后,再加载application。
springcloud默认关闭bootstrap,需要我们手动加入依赖开启。
1 2 3 4 5 <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-bootstrap</artifactId > <version > 3.0.4</version > </dependency >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 server: port: 3355 spring: application: name: config-client cloud: config: label: master name: config profile: dev uri: http://config-3344.com:3344 eureka: client: service-url: defaultZone: http://eureka7001.com:7001/eureka
1 2 3 4 5 6 7 8 9 10 11 12 @RestController @RefreshScope public class ConfigClientController { @Value("${config.info}") private String configInfo; @GetMapping("/configInfo") public String getConfigInfo () { return configInfo; } }
访问 http://localhost:3355/configInfo,和之前config-dev内容一致。
客户端手动动态刷新 我们实时修改仓库内的配置,3344可以实时更新,而3355服务读取配置却没有实时更新,需要我们重启服务才能更新配置。所以我们需要开启动态刷新解决该问题。
1 2 3 4 5 6 management: endpoints: web: exposure: include: "*"
但是直接网页刷新是不会变的。我们必须发送POST请求让服务刷新配置,但不用重启服务。
1 2 curl -X POST "http://localhost:3355/actuator/refresh"
Bus(jar包问题)上面Config并没有实现实时的更新,只是从重启服务变为了发送请求后更改,使用Bus消息总线完成自动动态刷新配置。Bus消息代理支持RabbitMQ和Kafka。
先再linux开启mq,访问mq图形化界面 http://192.168.158.138:15672。
自动动态刷新的两种实现:
Bus触发一个客户端的刷新,进而传播所有客户端都刷新。
Bus触发服务端ConfigServer的刷新,然后由服务端广播所有客户端进行刷新。
我们选择由服务端进行广播的模式吗,这种肯定是更优的。
服务端广播动态刷新 新建一个客户端3366一起测试,业务输出端口号
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @RestController @RefreshScope public class ConfigClientController { @Value("${config.info}") private String configInfo; @Value("${server.port}") private String serverPort; @GetMapping("/configInfo") public String getConfigInfo () { return serverPort + "," + configInfo; } }
在3344服务端和3355、3366客户端加入依赖
1 2 3 4 <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-bus-amqp</artifactId > </dependency >
配置文件也要引入mq相关,bus-refresh是官方指定的刷新端点。客户端也要配置mq的相关属性
1 2 3 4 5 6 7 8 9 10 11 12 13 spring: rabbitmq: host: 192.168 .158 .138 port: 5672 username: admin password: 111 management: endpoints: web: exposure: include: "bus-refresh"
1 curl -X POST "http://localhost:3344/actuator/bus-refresh"
按理来说我们启动服务端和多个客户端,更新依赖后,往服务端端点bus-refresh发送POST请求即可广播全部的客户端。但我在使用bus-amqp这个mq连接依赖时遇到了问题,说缺失类(当前依赖缺失需要的类,使用其他依赖版本),自己删除jar包导入低版本仍无法解决,所以Bus的广播模拟失败,版本才是唯一神,更新换代太快了,网上也搜索不到解决办法就很难受,还得看我Nacos。
Stream(jar包问题)MQ有多种款式,可使用Stream进行统一。stream会忽略底层使用的MQ差异,适配不同版本的MQ。
通过Stream的Binder对象进行中间件的交互。定义绑定器Binder作为中间层,实现了中间件的版本隔离。
1 2 3 4 <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-stream-rabbit</artifactId > </dependency >
常用注解
注解
说明
@Input
注解标识输入通道,通过该输乎通道接收到的消息进入应用程序
@Output
注解标识输出通道,发布的消息将通过该通道离开应用程序
@StreamListener
监听队列,用于消费者的队列的消息接收
@EnableBinding
将信道channel和exchange绑定在一起
现在这个绑定注解依旧过时了,可参考帖子下面的官方文档设置https://www.5axxw.com/questions/content/0b9xic,虽然注解过时了但还是可以用的。
消息生产者(output) 依赖及配置
1 2 3 4 <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-stream-rabbit</artifactId > </dependency >
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: 192.168 .158 .139 port: 5672 username: admin password: 111 bindings: output: destination: studyExchange content-type: application/json binder: defaultRabbit eureka: client: service-url: defaultZone: http://localhost:7001/eureka instance: lease-renewal-interval-in-seconds: 2 lease-expiration-duration-in-seconds: 5 instance-id: send-8801.com prefer-ip-address: true
接口 + 实现类 + 业务Controller,启动eureka7001,访问http://localhost:8801/sendMsg,发现后台有随机数,且MQ内创建的channel产生了消息。
1 2 3 public interface MessageProvider { public String send () ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 @EnableBinding(Source.class) public class MessageProviderImpl implements MessageProvider { @Resource private MessageChannel output; @Override public String send () { String serial = UUID.randomUUID().toString(); output.send(MessageBuilder.withPayload(serial).build()); System.out.println(serial); return null ; } }
1 2 3 4 5 6 7 8 9 10 @RestController public class SendMessageController { @Resource private MessageProvider messageProvider; @GetMapping("/sendMsg") public String sendMessage () { return messageProvider.send(); } }
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: 8802 spring: application: name: cloud-stream-consumer cloud: stream: binders: defaultRabbit: type: rabbit environment: spring: rabbitmq: host: 192.168 .158 .139 port: 5672 username: admin password: 111 bindings: input: destination: studyExchange content-type: application/json binder: defaultRabbit eureka: client: service-url: defaultZone: http://localhost:7001/eureka instance: lease-renewal-interval-in-seconds: 2 lease-expiration-duration-in-seconds: 5 instance-id: receive-8802.com prefer-ip-address: true
1 2 3 4 5 6 7 8 9 10 11 @Component @EnableBinding(Sink.class) public class ReceiverMessageController { @Value("${server.port}") private String serverPort; @StreamListener(Sink.INPUT) public void input (Message<String> msg) { System.out.println("消费者1,消息:" + msg.getPayload() + "\t port: " + serverPort); } }
生产者发送随机序列号,通过withPayload发送,消费者通过getPayload获取序列号进行消费。开启8802消费8801信息,然后又遇到Bus的jar包问题,还好这个消息队列的处理不关键,我们记录一个大概即可,实在是不知道jar包问题该如何处理······
Sleuth链路跟踪 zipkin安装及启动 https://zipkin.io/
1 2 3 4 5 6 7 git clone https://github.com/openzipkin/zipkin cd zipkin./mvnw -DskipTests --also-make -pl zipkin-server clean install java -jar ./zipkin-server/target/zipkin-server-*exec.jar
访问 http://localhost:9411/ zipkin界面。
在服务8001和消费者80添加依赖和新的配置:
1 2 3 4 5 <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-zipkin</artifactId > <version > 2.2.8.RELEASE</version > </dependency >
1 2 3 4 5 6 7 spring: zipkin: base-url: http://localhost:9411 sleuth: sampler: probability: 1
1 2 3 4 5 @GetMapping("/payment/zipkin") public String paymentZipkin () { return "Zipkin" ; }
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; }
使用消费者调用服务,9411端口会出现记录。注意使用单机环境要先把集群的配置和负载均衡关闭,不然会报错。
Nacos 下载及启动 Nacos充当服务配置中心,Nacos = Eureka + Config + Bus
在官方进行下载:https://github.com/alibaba/nacos/releases/tag/1.4.2
在命令行进入bin/startup.cmd进行启动,微盟单机模式需要修改.cmd的配置。
1 2 3 4 set MODE="cluster" set MODE="standalone"
启动后访问:http://localhost:8848/nacos,初始账号密码均为nacos。
服务提供者 官方2.2.6版本相关配置:https://spring-cloud-alibaba-group.github.io/github-pages/hoxton/en-us/index.html#_introduction
父类进行pom管理,子类配置依赖无需版本号,在yml中将9001端口配置到nacos。
1 2 3 4 5 6 7 8 9 <dependencymanagement > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-dependencies</artifactId > <version > Hoxton.SR9</version > <type > pom</type > <scope > import</scope > </dependency > </dependencymanagement >
1 2 3 4 <dependency > <groupId > com.alibaba.cloud</groupId > <artifactId > spring-cloud-starter-alibaba-nacos-discovery</artifactId > </dependency >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 server: port: 9001 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 8 9 10 11 @RestController public class PaymentController { @Value("${server.port}") private String serverPort; @GetMapping(value = "/payment/nacos/{id}") public String getPayment (@PathVariable("id") Integer id) { return "nacos registry, serverPort: " + serverPort+"\t id" +id; } }
1 2 3 4 5 6 7 8 @SpringBootApplication @EnableDiscoveryClient public class PaymentMain9001 { public static void main (String[] args) { SpringApplication.run(PaymentMain9001.class, args); } }
然后我们在8848nacos上的服务列表中,可以找到注册的服务。
建立两个一样的服务端9001、9002,方便客户端测试Nacos负载均衡功能。
客户端消费者 nacos依赖集成ribbon,实现负载均衡
1 2 3 4 <dependency > <groupId > com.alibaba.cloud</groupId > <artifactId > spring-cloud-starter-alibaba-nacos-discovery</artifactId > </dependency >
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
1 2 3 4 5 6 7 @SpringBootApplication @EnableDiscoveryClient public class OrderMain83 { public static void main (String[] args) { SpringApplication.run(OrderMain83.class, args); } }
1 2 3 4 5 6 7 8 @Configuration public class ApplicationContextConfig { @Bean @LoadBalanced public RestTemplate getRestTemplate () { return new RestTemplate(); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @RestController @Slf4j public class OrderController { @Resource private RestTemplate restTemplate; @Value("${service-url.nacos-user-service}") private String serverURL; @GetMapping(value = "/consumer/payment/nacos/{id}") public String paymentInfo (@PathVariable("id") Long id) { return restTemplate.getForObject(serverURL + "/payment/nacos/" + id, String.class); } }
多次访问请求localhost:83/consumer/payment/nacos/1,发现服务端轮询调用,实现了负载均衡。
注册中心对比 Eureka:AP
Zookeeper:CP
Consul:CP
Nacos:AP & CP(可切换)
服务配置中心 1 2 3 4 5 6 7 8 9 <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 >
和config一样,bootstrap优先级高于application,而使用nacos后,bootstrap读配置可以从nacos上读取,不用去git读取。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 server: port: 3377 spring: application: name: nacos-config-client cloud: nacos: discovery: server-addr: localhost:8848 config: server-addr: localhost:8848 file-extension: yml
1 2 3 4 5 6 spring: profiles: active: dev
注意配置注解@RefreshScope,动态更新
1 2 3 4 5 6 7 8 9 10 11 @RestController @RefreshScope public class ConfigClientController { @Value("${config.info}") private String configInfo; @GetMapping("/config/info") public String getConfigInfo () { return configInfo; } }
官方:https://nacos.io/zh-cn/docs/quick-start-spring-cloud.html
在 Nacos Spring Cloud 中,dataId
的完整格式如下:
1 ${prefix}-${spring.profiles.active}.${file-extension}
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
类型。
1 2 3 4 5 ${spring.application.name} -${spring.profile.active} .${spring.cloud.nacos.config.file-extension} nacos-config-client-dev.yml nacos-config-client-test.yml
在nacos上data id进行对应声明,内容则是yml/properties格式配置。
最后走请求 localhost:3377/config/info 可读取配置文件的config:info: 内容,且修改版本后会动态刷新,相比于config + bus,nacos又快又好。
Nacos配置详解 Nacos配置由NameSpace + Group + Data Id构成,也就是逐层分类。
NameSpace命名空间,实现环境隔离,如生产、测试、开发三个环境,它们之间是相互隔离的。
Group,默认为DEFAULT_GROUP,Group可将不同微服务划分到同一个分组中。
Data Id也就是Service服务,一个服务可包含多个集群。
Data Id 刚刚分析了Data Id的官方配置:${prefix}-${spring.profiles.active}.${file-extension}
我们配置是对应Data Id分为bootstrap 和 application两个文件,其中bootstrap用于读取nacos配置信息,后续不修改,applition记录配置类型,即生产、测试等版本。也就是要修改的单独放。之后只用改application就能换版本,无需改变bootstrap。
1 2 3 4 spring: profiles: active: dev
nacos-config-client-dev.yml、 nacos-config-client-test.yml,改配置文件spring.profiles.active属性即可指明要使用的环境。
Group
Data Id
GROUP
nacos-config-client-info.yml
DEV_GROUP
nacos-config-client-info.yml
TEST_GROUP
配置服务名相同,组分配的不同的两个服务。通过config下group切换。
1 2 3 4 5 spring: cloud: nacos: config: group: TEST_GROUP
NameSpace 在Nacos命名空间处声明新的命名空间,默认为public保留空间不可删除,在config下namespace生成的将命名空间id进行配置。
1 2 3 4 5 spring: cloud: nacos: config: namespace: d52c7eba-1ada-4ed1-8934-69f388dbdbe9
Nacos持久化 官网配置信息:https://nacos.io/zh-cn/docs/deployment.html
Nacos原本使用内置数据库derby进行持久化,我们可以对持久化存储的数据库进行配置。将脚本表在本地mysql中构建,然后配置nacos的application。
1 2 3 4 5 6 spring.datasource.platform =mysql db.num =1 db.url.0 =jdbc:mysql://127.0.0.1:3306/nacos_config?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC db.user.0 =root db.password.0 =111111
Nacos集群 切记linux开启nacos集群,在start.sh中设置jvm大小限制以免越界,虚拟机容量建议分配多一点。
nginx + nacos集群模拟,将集群ip映射到nginx上进行反向代理,关于nginx和nacos的相关配置就不赘述了,资料很多。
Sentinel(熔断 + 降级 + 限流) 下载、安装 官方文档:https://sentinelguard.io/zh-cn/docs/introduction.html
然后我们按照官方文档的版本对应,cloud2.2.6对应sentinel1.8.1:https://github.com/alibaba/Sentinel/releases/tag/1.8.1
在jar包目录下执行
1 java -jar sentinel-dashboard-1.8.1.jar
然后访问本机8080便是图形化界面,账号密码都是sentinel。
监控初始化 xml新引入sentinel相关依赖,和nacos搭配
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <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 >
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: 8401 spring: application: name: sentinel-service cloud: nacos: discovery: server-addr: localhost:8848 sentinel: transport: dashboard: localhost:8080 port: 8719 management: endpoints: web: exposure: include: '*' feign: sentinel: enabled: true
1 2 3 4 5 6 7 @SpringBootApplication @EnableDiscoveryClient public class SentinelMain8401 { public static void main (String[] args) { SpringApplication.run(SentinelMain8401.class, args); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @RestController @Slf4j public class FlowLimitController { @GetMapping("/testA") public String testA () { return "------testA" ; } @GetMapping("/testB") public String testB () { log.info(Thread.currentThread().getName() + "\t" + "...testB" ); return "------testB" ; } }
Sentinel采用懒加载机制,我们开启服务后不会主动监测,只有第一次服务被调用后才会被监控。
我们在簇点链路可以查看服务链接,并对它进行流控、降级、热点、授权等操作。
流控规则(单机)
QPS:每秒请求数,可设置阈值
流控模式
直接:api接口达到限流条件时,进行限流
关联:A关联B资源,B达到阈值时,A自行限流
链路:层级链路,设置上层入口监控,若对应的下层资源达到阈值,则对入口进行限流。
流控效果
快速失败:直接报错,Blocked by Sentinel (flow limiting)
Warm Up:冷加载、预热,官方默认coldFactor为3,初次QPS阈值为 阈值/coldFactor,然后随着时间的预热,慢慢将阈值恢复到设定的值。让流量逐步增加,而不是瞬间反应。
排队等待:排队匀速通过,超时则排队,会设置超时等待时间。
一开始使用直接失败的配置,当前点击超过1次/s,sentinel会自行限流控制(Blocked by Sentinel (flow limiting))
使用关联模式,我们可以用Postman设置多次请求B,此时手动访问A发现被限流。
1 2 3 4 5 6 7 8 9 @GetMapping("/testA") public String testA () { try { TimeUnit.MILLISECONDS.sleep(10000 ); } catch (InterruptedException e) { e.printStackTrace(); } return "------testA" ; }
我们设置阈值为1,让第一个线程进来后睡10s,然后不断发起请求,前面的线程由于睡眠并没有处理完业务,然后当前申请的线程数超过阈值,则会进行限流。
线程数只有流控模式,没有流控效果选择(默认为直接失败)。
降级规则 https://sentinelguard.io/zh-cn/docs/circuit-breaking.html
慢调用比例
设置RT最大响应时间,当前响应时间大于该值,则记为慢调用,同时规定时长、请求数、阈值比例。若在规定时长内,达到请求数且慢调用比例超过阈值,则发生熔断。此后经过一段熔断时长进入half-open半开状态,并放行一次请求做测试,若当前响应超过RT继续熔断,反之结束熔断。
异常比例
规定时间内,请求达到最小请求数,且异常发生的比例超过阈值,则发生熔断。经过一段熔断时长后进入half-open,通过放行一次请求来判断是否结束熔断。比例范围0% ~ 100%。
异常数
规定时间内,请求达到最小请求数,且异常发生次数超过阈值。经过熔断时长后进入half-open,放行一次请求判断是否结束熔断。
热点规则 热点搜索访问量大,对热点数据进行限制。
controller新增兜底方法,使用**@SentinelResource对应Hystrix的 @HystrixCommand**。
@SentinelResource 只能处理违反控制台配置的情况,并使用兜底方法处理。如果是代码本身抛出运行时异常,是不会用兜底方法处理的。
1 2 3 4 5 6 7 8 9 10 11 12 13 @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,o(╥﹏╥)o" ; }
我们设置热点规则,资源名即@SentinelResource设置的value,设置的参数索引对应我们服务的参数,从0开始对应,也就是0对应第一个参数。只要当我们请求中有这个参数时就会走热点监控。超过设置的阈值就会走兜底方法,若我们没有兜底方法则直接报错。
参数例外项:当配置参数时某个特殊值时,阈值会不一样,可自行确认值的类型和值。
系统规则 https://github.com/alibaba/Sentinel/wiki/%E7%B3%BB%E7%BB%9F%E8%87%AA%E9%80%82%E5%BA%94%E9%99%90%E6%B5%81
系统保护规则是从应用级别的入口流量进行控制,从单台机器的 load、CPU 使用率、平均 RT、入口 QPS 和并发线程数等几个维度监控应用指标,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。
相当于一个全局入口配置。
@SentinelResource blockHandler 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @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" )); } }
我们对资源进行限流处理时,可以对@SentinelResource声明的value处理,限流会走自定义方法,没有配置则走默认。或是直接走@GetMapping的url,限流只会走自定义的方法。
而兜底方法每次都要配置,我们需要进行解耦操作。设置自定义限流处理类:CustomerBlockHandler
1 2 3 4 5 6 7 8 9 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" ); } }
1 2 3 4 5 6 7 8 @GetMapping("/rateLimit/customerBlockHandler") @SentinelResource(value = "customerBlockHandler", blockHandlerClass = CustomerBlockHandler.class, blockHandler = "handlerException2") public CommonResult customerBlockHandler () { return new CommonResult(200 , "按客戶自定义" , new Payment(2020L , "serial003" )); }
fallback 配饰配置服务9003/9004和消费者84,消费者配置sentinel。
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 , "28a8c1e3bc2742d8848569891fb42181" )); hashMap.put(2L , new Payment(2L , "bba8c1e3bc2742d8848569891ac32182" )); hashMap.put(3L , new Payment(3L , "6ua8c1e3bc2742d8848569891xt92183" )); } @GetMapping(value = "/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; } }
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 @Slf4j public class CircleBreakerController { public static final String SERVICE_URL = "http://nacos-payment-provider" ; @Resource private RestTemplate restTemplate; @RequestMapping("/consumer/fallback/{id}") @SentinelResource(value = "fallback", blockHandler = "blockHandler", fallback = "handlerFallback", exceptionsToIgnore = IllegalArgumentException.class) public CommonResult<Payment> fallback (@PathVariable Long id) { CommonResult<Payment> result = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/" + id, CommonResult.class, id); if (id == 4 ) { throw new IllegalArgumentException("IllegalArgumentException,非法参数异常...." ); } else if (result.getData() == null ) { throw new NullPointerException("NullPointerException,该ID没有对应记录,空指针异常" ); } return result; } public CommonResult handlerFallback (@PathVariable Long id, Throwable e) { Payment payment = new Payment(id, "null" ); return new CommonResult<>(444 , "兜底异常handlerFallback,exception内容 " + e.getMessage(), payment); } public CommonResult blockHandler (@PathVariable Long id, BlockException blockException) { Payment payment = new Payment(id,"null" ); return new CommonResult<>(445 ,"blockHandler-sentinel限流,无此流水: blockException " +blockException.getMessage(),payment); } }
启动服务,发现ribbon生效,默认轮询调用两个服务端口,且@SentinelResource可配置fallback 和 blockHandler。二者同时配置时,因控制台限流、降级抛出的异常优先由blockHandler处理。
整合OpenFeign 修改84消费者的配置,注意OpenFeign会与devtools依赖冲突,需去掉devtools
1 2 3 4 <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-openfeign</artifactId > </dependency >
1 2 3 feign: sentinel: enabled: true
主启动类加上@EnableFeignClients注解
1 2 3 4 5 @FeignClient(value = "nacos-payment-provider", fallback = PaymentFallbackService.class) public interface PaymentService { @GetMapping("/paymentSQL/{id}") CommonResult<Payment> paymentSQL (@PathVariable("id") Long id) ; }
1 2 3 4 5 6 7 @Component public class PaymentFallbackService implements PaymentService { @Override public CommonResult<Payment> paymentSQL (Long id) { return new CommonResult<>(444 , "服务降级,PaymentFallbackService" , new Payment(id, "errorSerial" )); } }
1 2 3 4 5 6 7 8 @Resource private PaymentService paymentService;@GetMapping(value = "/consumer/paymentSQL/{id}") public CommonResult<Payment> paymentSQL (@PathVariable("id") Long id) { return paymentService.paymentSQL(id); }
使用OpenFeign配置熔断的兜底方法,接口声明服务,实现接口作为fallback为服务熔断后的处理具体实现。然后在controller中无需使用restTemplate,直接使用接口的组件调用服务。
Sentinel持久化 每次重启应用,规则配置都会重置,所以我们需要进行持久化操作。这里使用8401服务模拟。
1 2 3 4 <dependency > <groupId > com.alibaba.csp</groupId > <artifactId > sentinel-datasource-nacos</artifactId > </dependency >
1 2 3 4 5 6 7 8 9 10 11 12 spring: cloud: sentinel: datasource: ds1: nacos: server-addr: localhost:8848 dataId: sentinel-service groupId: DEFAULT_GROUP data-type: json rule-type: flow
完成依赖及配置后,进入Nacosxi新增配置,配置名即我们的服务名sentinel-service,选择json数据类型进行以下配置。然后在sentinel中我们就会加载这个限流的配置。
1 2 3 4 5 6 7 8 9 [{ "resource" : "/rateLimit/byUrl" , "IimitApp" : "default" , "grade" : 1 , "count" : 1 , "strategy" : 0 , "controlBehavior" : 0 , "clusterMode" : false }]
resource:资源名称
limitApp:来源应用
grade:阈值类型,0表示线程数, 1表示QPS
count:单机阈值
strategy:流控模式,0表示直接,1表示关联,2表示链路
controlBehavior:流控效果,0表示快速失败,1表示Warm Up,2表示排队等待
clusterMode:是否集群
完成持久化配置后,我们每次调用服务后,sentinel会监听到服务,并加载Nacos中对应服务的流控配置。
Seata(分布式事务) 官网:http://seata.io/zh-cn/docs/overview/what-is-seata.html
分布式事务要保证全局数据一致性。Seata由1 + 3组成,即一个全局唯一的事务ID + TC + TM + RM构成。
TC (Transaction Coordinator) - 事务协调者
维护全局和分支事务的状态,驱动全局事务提交或回滚。
TM (Transaction Manager) - 事务管理器
定义全局事务的范围:开始全局事务、提交或回滚全局事务。
RM (Resource Manager) - 资源管理器
管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。