目录
九、CircuitBreaker断路器
1.前言(Hystrix)
2.服务雪崩
3.Circuit Breaker
4. Resilience4j
5.案例实战
(1)熔断(服务熔断 + 服务降级)
Ⅰ. 按照 COUNT_BASED(计数的滑动窗口)
Ⅱ . 按照 TIME_BASED(时间的滑动窗口)
(2)隔离(舱壁)
Ⅰ. 实现SemaphoreBulkhead(信号量舱壁)
Ⅱ. 实现FixedThreadPoolBulkhead(固定线程池舱壁)
(3)限流(RateLimiter)
Ⅰ. 计数器算法 / 滚动时间窗(tumbling time window)
Ⅱ . 滑动时间窗口(sliding time window)
Ⅲ . 漏斗算法(Leaky Bucket)
Ⅳ . 令牌桶算法(Token Bucket)[ Resilience4j 默认使用 ]
(4)疑惑点
十、Sleuth(Micrometer) + ZipKin分布式链路追踪
1.前言
(1)Sleuth
(2)分布式链路追踪
(3)新一代Spring Cloud Sleuth:Micrometer
(4)zipkin
2.分布式链路追踪原理
3.zipkin
(1)概述
(2)下载安装
4. Micrometer + ZipKin 搭建链路监控案例步骤
(1)总体父工程依赖包导入
(2)服务提供者8001
(3)通用 Api 接口
(4)服务调用者80
(5)测试
九、CircuitBreaker断路器
1.前言(Hystrix)
Hystrix 是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖不可避免的会调用失败,比如超时、异常等。
Hystrix 能够保证在一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性。
但 Hystrix 目前已经进入了维护模式,我们不再使用。
Hystrix 新的替代方案是 Resilience4j,接下来将进行介绍。
2.服务雪崩
复杂分布式体系结构中的应用程序有数十个依赖关系,每个依赖关系在某些时候将不可避免地失败。
多个微服务之间调用的时候,假设微服务 A 调用微服务 B 和微服务 C,微服务 B 和微服务 C 又调用其它的微服务,这就是所谓的 “扇出”。
如果扇出的链路上某个微服务的调用响应时间过长或者不可用,对微服务 A 的调用就会占用越来越多的系统资源,进而引起系统崩溃,所谓的“雪崩效应”。
对于高流量的应用来说,单一的后端依赖可能会导致所有服务器上的所有资源都在几秒钟内饱和。比失败更糟糕的是,这些应用程序还可能导致服务之间的延迟增加,备份队列,线程和其他系统资源紧张,导致整个系统发生更多的级联故障。这些都表示需要对故障和延迟进行隔离和管理,以便单个依赖关系的失败,不能取消整个应用程序或系统。
所以,通常当你发现一个模块下的某个实例失败后,这时候这个模块依然还会接收流量,然后这个有问题的模块还调用了其他的模块,这样就会发生级联故障,或者叫雪崩。
例如:语雀崩了(2023.10.23)、阿里系大部分产品(2023.11.12)
Question:如何解决服务雪崩?
有问题的节点 --> 快速熔断(快速返回失败处理或者返回默认兜底数据【服务降级】)。
”断路器” 本身是一种开关装置,当某个服务单元发生故障之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个符合预期的、可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方无法处理的异常。
这样就保证了服务调用方的线程不会被长时间、不必要地占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩。
简单理解:出故障了 “保险丝” 跳闸,别把整个家给烧了
Tips:服务雪崩还有很多解决办法,后续会一 一讲解
3.Circuit Breaker
CircuitBreaker 的目的是保护分布式系统免受故障和异常,提高系统的可用性和健壮性。
当一个组件或服务出现故障时,CircuitBreaker 会迅速切换到开放 OPEN 状态(保险丝跳闸断电),阻止请求发送到该组件或服务从而避免更多的请求发送到该组件或服务。
这可以减少对该组件或服务的负载,防止该组件或服务进一步崩溃,并使整个系统能够继续正常运行。同时,CircuitBreaker 还可以提高系统的可用性和健壮性,因为它可以在分布式系统的各个组件之间自动切换,从而避免单点故障的问题。
Circuit Breaker 只是一套规范和接口,落地实现者是 Resilience4J。
4. Resilience4j
Resilience4j 是一个专为函数式编程设计的轻量级容错库。
Resiience4j 提供高阶函数(装饰器),包括通过断路器、限流器、重试或隔离任何功能接口、lambda 表达式或方法引用,并且这些装饰器可以进行叠加。
优点是您可以选择您需要的装饰器,而没有其他选择。此外,Resilience4j2 需要 Java 17。
这里主要介绍前三个,后三个我们基本实际开发中使用其他框架或技术来实现。
5.案例实战
(1)熔断(服务熔断 + 服务降级)
常用配置参数如下:
failure-rate-threshold | 以百分比配置失败率峰值 |
sliding-window-type | 断路器的滑动窗口期类型 |
sliding-window-size | 若COUNT_BASED,则10次调用中有50%失败(即5次)打开熔断断路器; 若为TIME_BASED则,此时还有额外的两个设置属性,含义为:在N秒内(sliding-window-size)100%(slow-call-rate-threshold)的请求超过N秒(slow-call-duration-threshold)打开断路器。 |
slowCallRateThreshold | 以百分比的方式配置,断路器把调用时间大于slowCallDurationThreshold的调用视为慢调用,当慢调用比例大于等于峰值时,断路器开启,并进入服务降级。 |
slowCallDurationThreshold | 配置调用时间的峰值,高于该峰值的视为慢调用。 |
permitted-number-of-calls-in-half-open-state | 运行断路器在HALF_OPEN状态下时进行N次调用,如果故障或慢速调用仍然高于阈值,断路器再次进入打开状态。 |
minimum-number-of-calls | 在每个滑动窗口期样本数,配置断路器计算错误率或者慢调用率的最小调用数。比如设置为5意味着,在计算故障率之前,必须至少调用5次。如果只记录了4次,即使4次都失败了,断路器也不会进入到打开状态。 |
wait-duration-in-open-state | 从OPEN到HALF_OPEN状态需要等待的时间 |
想要详细了解,可以看官网介绍:
https://github.com/lmhmhl/Resilience4j-Guides-Chinese/blob/main/core-modules/CircuitBreaker.md
Question:服务熔断加在客户端还是服务端?为什么?
快速失败:客户端熔断可以在服务调用失败的情况下快速返回错误结果,而不需要等待服务端超时。
保护后端服务:当后端服务出现性能问题或高并发时,如果没有熔断机制,客户端会继续大量请求,导致后端服务雪崩
提升系统稳定性和用户体验:客户端感知到下游服务异常时,可以及时熔断或降级处理,向用户提供替代服务或兜底方案,从而提升系统的整体稳定性和用户体验。
减轻服务器负载:熔断逻辑由客户端承担,可以减少服务端的压力,因为服务端不需要检测客户端的状态。
网络因素:在分布式系统中,网络可能不稳定。如果客户端能够检测到网络不佳,及时熔断,可以减少因网络问题导致的错误请求,优化服务间的通信体验。
与重试机制配合:客户端的熔断机制通常与重试机制结合使用。当检测到下游服务恢复正常后,客户端会自动解除熔断,恢复调用。
服务端准备:
① 在 8001 中新建一个 PayCircuitController,手动制造异常:
说明:
id 为-4时,模拟服务提供方接口报错情况(模拟调用失败)
id 为9999时,模拟接口阻塞情况(模拟慢调用)
id 为其他值时模拟正常情况。
@RestController
public class PayCircuitController
{
//=========Resilience4j CircuitBreaker 的例子
@GetMapping(value = "/pay/circuit/{id}")
public String myCircuit(@PathVariable("id") Integer id)
{
if(id == -4) throw new RuntimeException("----circuit id 不能负数");
if(id == 9999){
try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); }
}
return "Hello, circuit! inputId: "+id+" \t " + IdUtil.simpleUUID();
}
}
② 修改 PayFeignApi 接口
//支付微服务的feign接口
@FeignClient(value = "cloud-payment-service")
public interface PayFeignApi
{
//新增一条支付相关流水记录
@PostMapping("/pay/add")
public ResultData addPay(@RequestBody PayDTO payDTO);
//按照主键记录查询支付流水信息
@GetMapping("/pay/get/{id}")
public ResultData getPayInfo(@PathVariable("id") Integer id);
// openfeign天然支持负载均衡演示
@GetMapping(value = "/pay/get/info")
public String getConsulInfo();
// Resilience4j CircuitBreaker 的例子
@GetMapping(value = "/pay/circuit/{id}")
public String myCircuit(@PathVariable("id") Integer id);
}
Ⅰ. 按照 COUNT_BASED(计数的滑动窗口)
步骤(在客户端 Feign80 修改):
① 在 pom 文件中导入依赖:
<!--resilience4j-circuitbreaker-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId>
</dependency>
<!-- 由于断路保护等需要AOP实现,所以必须导入AOP包 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
注意:resilience4j 对于请求的控制是基于 AOP 切面的:在进入目标接口前,通过 AOP 切面进行前置用用拦截,允许通过的访问接口,不允许通过的直接拦截。
(扩展:理论上,我觉得 aop 能做到,springMvc 的拦截器也能实现服务熔断。但使用AOP的原因应该是因为它更灵活、更精细,同时如果使用拦截器可能需要程序员来做更多的逻辑判断,增加工作量!)
② 改写 application.yml:
server: port: 80 spring: application: name: cloud-consumer-openfeign-order cloud: consul: host: localhost port: 8500 discovery: prefer-ip-address: true #优先使用服务ip进行注册 service-name: ${spring.application.name} openfeign: client: config: cloud-payment-service: #连接超时时间,为避免演示出错,本次内容后设置为20秒 connectTimeout: 20000 readTimeout: 20000 httpclient: hc5: enabled: true # Apache HttpClient5 配置开启 compression: request: enabled: false min-request-size: 2048 #最小触发压缩的大小 mime-types: text/xml,application/xml,application/json #触发压缩数据类型 response: enabled: false # 开启circuitbreaker和分组激活 spring.cloud.openfeign.circuitbreaker.enabled circuitbreaker: enabled: true # 启用服务熔断器功能 group: enabled: true #没开分组永远不用分组的配置。精确优先、分组次之(开了分组)、默认最后 # Resilience4j CircuitBreaker 按照次数:COUNT_BASED 的例子 # 6次访问中当执行方法的失败率达到50%时CircuitBreaker将进入开启OPEN状态(保险丝跳闸断电)拒绝所有请求。 # 等待5秒后,CircuitBreaker 将自动从开启OPEN状态过渡到半开HALF_OPEN状态,允许一些请求通过以测试服务是否恢复正常。 # 如还是异常CircuitBreaker 将重新进入开启OPEN状态;如正常将进入关闭CLOSE闭合状态恢复正常处理请求。 resilience4j: circuitbreaker: configs: default: failureRateThreshold: 50 #设置50%的调用失败时打开断路器,超过失败请求百分⽐CircuitBreaker变为OPEN状态。 slidingWindowType: COUNT_BASED # 滑动窗口的类型 slidingWindowSize: 6 #滑动窗⼝的⼤⼩配置COUNT_BASED表示6个请求,配置TIME_BASED表示6秒 minimumNumberOfCalls: 6 #断路器计算失败率或慢调用率之前所需的最小样本(每个滑动窗口周期)。如果minimumNumberOfCalls为10,则必须最少记录10个样本,然后才能计算失败率。如果只记录了9次调用,即使所有9次调用都失败,断路器也不会开启。 automaticTransitionFromOpenToHalfOpenEnabled: true # 是否启用自动从开启状态过渡到半开状态,默认值为true。如果启用,CircuitBreaker将自动从开启状态过渡到半开状态,并允许一些请求通过以测试服务是否恢复正常 waitDurationInOpenState: 5s #从OPEN到HALF_OPEN状态需要等待的时间 permittedNumberOfCallsInHalfOpenState: 2 #半开状态允许的最大请求数,默认值为10。在半开状态下,CircuitBreaker将允许最多permittedNumberOfCallsInHalfOpenState个请求通过,如果其中有任何一个请求失败,CircuitBreaker将重新进入开启状态。 recordExceptions: - java.lang.Exception instances: cloud-payment-service: baseConfig: default
③ 新建 OrderCircuitController
//Resilience4j CircuitBreaker 的例子
@RestController
public class OrderCircuitController
{
@Resource
private PayFeignApi payFeignApi;
@GetMapping(value = "/feign/pay/circuit/{id}")
@CircuitBreaker(name = "cloud-payment-service", fallbackMethod = "myCircuitFallback")
public String myCircuitBreaker(@PathVariable("id") Integer id)
{
return payFeignApi.myCircuit(id);
}
//myCircuitFallback就是服务降级后的兜底处理方法
public String myCircuitFallback(Integer id,Throwable t) {
// 这里是容错处理逻辑,返回备用结果
return "myCircuitFallback,系统繁忙,请稍后再试-----/(ㄒoㄒ)/~~";
}
}
注意:
④ 测试,启动 8001 和 feign80,正确和错误的 url 交替各试 3 次
正确:http://localhost/feign/pay/circuit/11
错误:http://localhost/feign/pay/circuit/-4
在第 6 次访问错误 url 时,会达到 50% 的错误比例,触发服务熔断!
所以,第 7 次访问正确 url 时,就会直接拒绝访问,触发服务降级调用兜底方法。
此时,过了 5s,之后就会进入半开状态,后续两次请求都成功就会回到关闭状态。如果有任何一次失败,就进入开启状态。
Ⅱ . 按照 TIME_BASED(时间的滑动窗口)
步骤(在客户端 Feign80 修改):
依赖导入和 controller 上面都完成了,这里只介绍不同点
① 修改 application.yml:
server:
port: 80
spring:
application:
name: cloud-consumer-openfeign-order
####Spring Cloud Consul for Service Discovery
cloud:
consul:
host: localhost
port: 8500
discovery:
prefer-ip-address: true # 优先使用服务 ip 进行注册
service-name: ${ spring.application.name}
openfeign:
client:
config:
default:
#cloud-payment-service:
# 连接超时时间,为避免演示出错,讲解完本次内容后设置为 20 秒
connectTimeout: 20000
# 读取超时时间,为避免演示出错,讲解完本次内容后设置为 20 秒
readTimeout: 20000
# 开启 httpclient5
httpclient:
hc5:
enabled: true
# 开启压缩特性
compression:
request:
enabled: true
min-request-size: 2048
mime-types: text/xml,application/xml,application/json
response:
enabled: true
# 开启 circuitbreaker 和分组激活
circuitbreaker:
enabled: true
group:
enabled: true # 没开分组永远不用分组的配置。精确优先、分组次之 ( 开了分组 ) 、默认最后
# Resilience4j CircuitBreaker 按照时间: TIME_BASED 的例子
resilience4j:
timelimiter:
configs:
default:
timeout-duration: 10s #神坑的位置,timelimiter 默认限制远程1s,超于1s就超时异常,配置了降级,就走降级逻辑
circuitbreaker:
configs:
default:
failureRateThreshold: 50 #设置50%的调用失败时打开断路器,超过失败请求百分⽐CircuitBreaker变为OPEN状态。
slowCallDurationThreshold: 2s #慢调用时间阈值,高于这个阈值的视为慢调用并增加慢调用比例。
slowCallRateThreshold: 30 #慢调用百分比峰值,断路器把调用时间⼤于slowCallDurationThreshold,视为慢调用,当慢调用比例高于阈值,断路器打开,并开启服务降级
slidingWindowType: TIME_BASED # 滑动窗口的类型
slidingWindowSize: 2 #滑动窗口的大小配置,配置TIME_BASED表示2秒
minimumNumberOfCalls: 2 #断路器计算失败率或慢调用率之前所需的最小样本(每个滑动窗口周期)。
permittedNumberOfCallsInHalfOpenState: 2 #半开状态允许的最大请求数,默认值为10。
waitDurationInOpenState: 5s #从OPEN到HALF_OPEN状态需要等待的时间
recordExceptions:
- java.lang.Exception
instances:
cloud-payment-service:
baseConfig: default
② 测试,启动 8001 和 feign80,4 次慢调用,1 次正常访问,同时进行
正确:http://localhost/feign/pay/circuit/1
慢调用:http://localhost/feign/pay/circuit/9999
正常情况:
当四个窗口的慢调用同时进行,触发服务熔断:
(2)隔离(舱壁)
bulkhead(船的)舱壁/(飞机的)隔板
隔板来自造船行业,床仓内部一般会分成很多小隔舱,一旦一个隔舱漏水因为隔板的存在而不至于影响其它隔舱和整体船。
作用:
依赖隔离 & 负载保护:用来限制对于下游服务的最大并发数量
Resilience4j 提供了两种隔离的实现方式,可以限制并发执行的数量,即限制对于下游服务的最大并发数量。
- SemaphoreBulkhead:使用了信号量
- FixedThreadPoolBulkhead:使用了有界队列和固定大小线程池
SemaphoreBulkhead 可以在各种线程和 I/O 模型上正常工作。与 Hystrix 不同,它不提供基于shadow 的 thread 选项。由客户端来确保正确的线程池大小与隔离配置一致。
服务端准备:
① 在 8001 中修改 PayCircuitController,手动制造异常:
@RestController
public class PayCircuitController
{
...
//=========Resilience4j bulkhead 的例子
@GetMapping(value = "/pay/bulkhead/{id}")
public String myBulkhead(@PathVariable("id") Integer id)
{
if(id == -4) throw new RuntimeException("----bulkhead id 不能-4");
if(id == 9999)
{
try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); }
}
return "Hello, bulkhead! inputId: "+id+" \t " + IdUtil.simpleUUID();
}
}
② 修改 PayFeignApi 接口
//支付微服务的feign接口
@FeignClient(value = "cloud-payment-service")
public interface PayFeignApi
{
...
//Resilience4j Bulkhead 的例子
@GetMapping(value = "/pay/bulkhead/{id}")
public String myBulkhead(@PathVariable("id") Integer id);
}
Ⅰ. 实现SemaphoreBulkhead(信号量舱壁)
信号量舱壁(SemaphoreBulkhead)原理:
1. 当信号量有空闲时,进入系统的请求会直接获取信号量并开始业务处理。
2. 当信号量全被占用时,接下来的请求将会进入阻塞状态,SemaphoreBulkhead 提供了一个阻塞计时器,
如果阻塞状态的请求在阻塞计时内无法获取到信号量则系统会拒绝这些请求。
若请求在阻塞计时内获取到了信号量,那将直接获取信号量并执行相应的业务处理。
基本上就是 JUC 信号灯内容的同样思想:
步骤(在客户端 Feign80 修改):
① 在 pom 文件中导入依赖:
<!--resilience4j-bulkhead-->
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-bulkhead</artifactId>
</dependency>
② application.yml:
server:
port: 80
spring:
application:
name: cloud-consumer-openfeign-order
cloud:
consul:
host: localhost
port: 8500
discovery:
prefer-ip-address: true #优先使用服务ip进行注册
service-name: ${spring.application.name}
openfeign:
client:
config:
cloud-payment-service:
#连接超时时间,为避免演示出错,讲解完本次内容后设置为20秒
connectTimeout: 20000
readTimeout: 20000
httpclient:
hc5:
enabled: true # Apache HttpClient5 配置开启
compression:
request:
enabled: false
min-request-size: 2048 #最小触发压缩的大小
mime-types: text/xml,application/xml,application/json #触发压缩数据类型
response:
enabled: false
# 开启circuitbreaker和分组激活 spring.cloud.openfeign.circuitbreaker.enabled
circuitbreaker:
enabled: true
group:
enabled: true #没开分组永远不用分组的配置。精确优先、分组次之(开了分组)、默认最后
####resilience4j bulkhead 的例子
resilience4j:
bulkhead:
configs:
default:
maxConcurrentCalls: 2 # 隔离允许并发线程执行的最大数量
maxWaitDuration: 1s # 当达到并发调用数量时,新的线程的阻塞时间,我只愿意等待1秒,过时不候进舱壁兜底fallback
instances:
cloud-payment-service:
baseConfig: default
timelimiter:
configs:
default:
timeout-duration: 20s
注意:
i. 需要将之前服务熔断的配置注释掉,防止冲突。
ii. 默认配置如下,这里为了更好的展示效果,覆盖了默认值
③ 修改 OrderCircuitController
@RestController
public class OrderCircuitController
{
@Resource
private PayFeignApi payFeignApi;
...
// (船的)舱壁,隔离
@GetMapping(value = "/feign/pay/bulkhead/{id}")
@Bulkhead(name = "cloud-payment-service",fallbackMethod = "myBulkheadFallback",type = Bulkhead.Type.SEMAPHORE)
public String myBulkhead(@PathVariable("id") Integer id)
{
return payFeignApi.myBulkhead(id);
}
public String myBulkheadFallback(Throwable t)
{
return "myBulkheadFallback,隔板超出最大数量限制,系统繁忙,请稍后再试-----/(ㄒoㄒ)/~~";
}
}
细节:
@Bulkhead 中的 type属性,指的是实现舱壁用的哪一种方式,当前使用 SEMAPHORE 信号灯
④ 测试:
浏览器新打开 2 个窗口,各点一次,分别点击 http://localhost/feign/pay/bulkhead/9999
每个请求调用需要耗时 5 秒,2 个线程瞬间达到配置过的最大并发数 2
此时第 3 个请求正常的请求访问,http://localhost/feign/pay/bulkhead/1
直接被舱壁限制隔离了,碰不到 8001,调用兜底方法。
等其中一个窗口停止了,再去正常访问,并发数小于 2 了,可以OK
(如果这里没有触发隔离,第 3 次仍能正常访问,可能是因为 url 中的 id 相同,都是 9999,所以认为是同一个请求了。不妨把最大并发数设为 1,此时就会出现隔离,我就是这样)
Ⅱ. 实现FixedThreadPoolBulkhead(固定线程池舱壁)
固定线程池舱壁(FixedThreadPoolBulkhead)原理:
FixedThreadPoolBulkhead 的功能与 SemaphoreBulkhead 一样也是用于限制并发执行的次数的,但是二者的实现原理存在差别而且表现效果也存在细微的差别。
FixedThreadPoolBulkhead 使用一个固定线程池(包含核心线程和临时线程)和一个等待队列(阻塞队列)来实现舱壁。
1. 当线程池(核心线程)中存在空闲时,则此时进入系统的请求将直接进入线程池开启新线程或使用空闲线程来处理请求。
2. 当线程池(核心线程)中无空闲时时,接下来的请求将进入等待队列,
2.1 在队列中的请求等待线程池(核心线程)出现空闲时,将进入线程池进行业务处理,
2.2 若等待队列仍然无剩余空间时,接下来的请求将直接进入线程池(临时线程)进行业务处理
2.3 若等待队列和临时线程都无剩余空间,接下来的请求将会被拒绝。
另外:ThreadPoolBulkhead 只对 CompletableFuture 方法有效,所以我们必创建返回CompletableFuture 类型的方法
基本上就是 JUC-线程池内容的同样思想:
步骤(在客户端 Feign80 修改):
依赖导入和 controller 上面都完成了,这里只介绍不同点
① 修改 application.yml:
server:
port: 80
spring:
application:
name: cloud-consumer-openfeign-order
####Spring Cloud Consul for Service Discovery
cloud:
consul:
host: localhost
port: 8500
discovery:
prefer-ip-address: true #优先使用服务ip进行注册
service-name: ${spring.application.name}
openfeign:
client:
config:
default:
#cloud-payment-service:
#连接超时时间,为避免演示出错,讲解完本次内容后设置为20秒
connectTimeout: 20000
#读取超时时间,为避免演示出错,讲解完本次内容后设置为20秒
readTimeout: 20000
#开启httpclient5
httpclient:
hc5:
enabled: true
#开启压缩特性
compression:
request:
enabled: true
min-request-size: 2048
mime-types: text/xml,application/xml,application/json
response:
enabled: true
#开启circuitbreaker和分组激活
circuitbreaker:
enabled: true
# group:
# enabled: false # 演示Bulkhead.Type.THREADPOOL时spring.cloud.openfeign.circuitbreaker.group.enabled设为 false(或者注释掉)新启线程和原来主线程脱离了。
####resilience4j bulkhead -THREADPOOL 的例子
resilience4j:
timelimiter:
configs:
default:
timeout-duration: 10s #timelimiter 默认限制远程 1s ,超过报错不好演示效果所以加上 10 秒
thread-pool-bulkhead:
configs:
default:
core-thread-pool-size: 1
max-thread-pool-size: 1
queue-capacity: 1
instances:
cloud-payment-service:
baseConfig: default# spring.cloud.openfeign.circuitbreaker.group.enabled 请设置为false 新启线程和原来主线程脱离
注意:
i . 默认配置如下,这里为了更好的展示效果,覆盖了默认值
ii . 当前配置分析:
② 修改 OrderCircuitController
//Resilience4j CircuitBreaker 的例子
@RestController
public class OrderCircuitController
{
@Resource
private PayFeignApi payFeignApi;
...
@GetMapping(value = "/feign/pay/bulkhead/{id}")
@Bulkhead(name = "cloud-payment-service",fallbackMethod = "myBulkheadPoolFallback",type = Bulkhead.Type.THREADPOOL)
public CompletableFuture<String> myBulkheadTHREADPOOL(@PathVariable("id") Integer id)
{
System.out.println(Thread.currentThread().getName()+"\t"+"enter the method!!!");
try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println(Thread.currentThread().getName()+"\t"+"exist the method!!!");
return CompletableFuture.supplyAsync(() -> payFeignApi.myBulkhead(id) + "\t" + " Bulkhead.Type.THREADPOOL");
}
public CompletableFuture<String> myBulkheadPoolFallback(Integer id,Throwable t)
{
return CompletableFuture.supplyAsync(() -> "Bulkhead.Type.THREADPOOL,系统繁忙,请稍后再试-----/(ㄒoㄒ)/~~");
}
}
细节:
i . 将刚才信号量舱壁的请求方法注释掉,避免请求路径冲突
ii . @Bulkhead 中的 type属性,当前使用 THREADPOOL 固定线程池
iii . ThreadPoolBulkhead 只对 CompletableFuture 方法有效,所以我们必创建返回CompletableFuture 类型的方法
③ 同时测试三个请求:
http://localhost/feign/pay/bulkhead/1
http://localhost/feign/pay/bulkhead/2
http://localhost/feign/pay/bulkhead/3
(3)限流(RateLimiter)
限流:是指对某个用户在一定时间内对某个 API 的访问次数或某个用户的下载速度进行限制,以保护系统不被过载或滥用。例如,某个 API 每秒只能被访问 100 次,或者某个用户每秒只能下载 1MB 的数据。
简单来说,限流就是限制最大访问流量。系统能提供的最大并发是有限的,同时来的请求又太多,就需要限流。
比如商城秒杀业务,瞬时大量请求涌入,服务器忙不过就只好排队限流了,和去景点排队买票和去医院办理业务排队等号道理相同。
所谓限流,就是通过对并发访问 / 请求进行限速,或者对一个时间窗口内的请求进行限速,以保护自身系统和下游系统不被高并发流量冲垮,导致系统雪崩等问题。
一旦达到限制速率则可以拒绝服务、排队或等待、降级等处理。
常见限流算法:
Ⅰ. 计数器算法 / 滚动时间窗(tumbling time window)
计数器(Counter)算法是在一段时间间隔内(时间窗 / 时间区间),处理请求的最大数量固定,超过部分不做处理。计数器算法是限流算法里最简单也是最容易实现的一种算法。
举个例子,比如我们规定对于 A 接口,我们 1 分钟的访问次数不能超过 100 个。可以这么做:
① 在一开始的时候,可以设置一个计数器 counter,每当一个请求过来的时候,counter 就加 1,如果 counter 的值大于 100 并且该请求与第一个请求的间隔时间还在 1 分钟之内,那么说明请求数过多,拒绝访问;
② 如果该请求与第一个请求的间隔时间大于 1 分钟,且 counter 的值还在限流范围内,那么就重置 counter,就是这么简单粗暴。
缺点:由于计数器算法存在时间临界点缺陷,因此在时间临界点左右的极短时间段内容易遭到攻击。
Ⅱ . 滑动时间窗口(sliding time window)
把固定窗口划分为多个小窗口,这些小窗口构成一个环,每个小窗口分别计数。而这些小窗口计数的总和不能超过限制阀值从而保证限流。划分小窗口数量越多,限流则越准确。
例如:对于 A 接口,我们规定 1 秒钟的访问次数不能超过 100 个,以 0.1s 为一个独立区间,整个窗口大小为 1s。
假设前 0.9s 访问量都是 0,所以前 0.9s 滑动窗口的 count 都为 0。
第 1.0s 的访问量是 100,此时整个滑动窗口的 count = 0 + 100 = 100。
当 1.1s 时,又来了 100 个访问量,我们的滑动窗口为 0.2s -1.1s,count 仍是 100,因为 0.1s 时的访问量为 0,count = 100 - 0 = 100。
所以,面对 1.1s 的 100个请求,我们会全部拒绝。
当 2.0s 时,滑动窗口为 1.1s - 2.0s,count 会减去第 1.0s 的 100 个访问量,此时 count 才重新变为 0 < 100,才能接收请求。
优点:可以解决上述临界点问题
缺点:
① 滑动窗口会给服务器内存造成更大的压力,因为滑动窗口的具体实现是依赖内存块进行一块一块存储的,块越多,服务器负担越大。
② 假设在整个时间窗口内进行计数和限流,但如果处理速度足够快,某些请求的处理可能会在窗口结束前完成。此时,虽然计算中的 count
值没有及时更新,仍然保持在最大阈值,但实际上系统已经有能力处理更多请求了。这种“延迟释放”可能导致性能和资源的浪费。
Ⅲ . 漏斗算法(Leaky Bucket)
漏桶(Leak Bucket)算法限流的基本原理为:
水(对应请求)从进水口进入到漏桶里,漏桶以一定的速度出水(请求放行),当水流入速度过大,桶内的总水量大于桶容量会直接溢出,请求被拒绝。
大致的漏桶限流规则如下:
1. 进水口(对应客户端请求)以任意速率流入进入漏桶。
2. 漏桶的容量是固定的,出水(放行)速率也是固定的。
3. 漏桶容量是不变的,如果处理速度太慢,桶内水量会超出了桶的容量,则后面流入的水滴会溢出表示请求拒绝。
缺点: 漏斗这样严格完美的流量限制策略,使得他完全放弃了应对突发流量的能力(突然来了100个请求,但是处理速度还是一个一个的)。
Ⅳ . 令牌桶算法(Token Bucket)[ Resilience4j 默认使用 ]
令牌桶(Token Bucket)算法原理:
以一个设定的速率产生令牌(Token)并放入令牌桶,每次用户请求都得申请令牌,如果令牌不足,则拒绝请求。
令牌桶算法中新请求到来时会从桶里拿走一个令牌,如果桶内没有令牌可拿,就拒绝服务。当然,令牌的数量也是有上限的。令牌的数量与时间和发放速率强相关,时间流逝的时间越长,会不断往桶里加入越多的令牌,如果令牌发放的速度比申请速度快,令牌桶会放满令牌,直到令牌占满整个令牌桶。令牌桶限流大致的规则如下:
1. 进水口按照某个速度,向桶中放入令牌。
2. 令牌的容量是固定的,但是放行的速度不是固定的,只要桶中还有剩余令牌,一旦请求过来就能申请成功,然后放行。
3. 如果令牌的发放速度,慢于请求到来速度,桶内就无令牌可领,请求就会被拒绝。
总之,令牌的发送速率可以设置,从而可以对突发的出口流量进行有效的应对,
优点:可以应对突发流量的情况(突然来了 100 个请求,只要桶里令牌足够,我都可以获取到,都能进行处理)
服务端准备:
① 在 8001 修改 PayCircuitController 新增 myRatelimit 方法
@RestController
public class PayCircuitController
{
...
//=========Resilience4j ratelimit 的例子
@GetMapping(value = "/pay/ratelimit/{id}")
public String myRatelimit(@PathVariable("id") Integer id)
{
return "Hello, myRatelimit欢迎到来 inputId: "+id+" \t " + IdUtil.simpleUUID();
}
}
② PayFeignApi 接口新增限流 api 方法
@FeignClient(value = "cloud-payment-service")
public interface PayFeignApi
{
...
//Resilience4j Ratelimit 的例子
@GetMapping(value = "/pay/ratelimit/{id}")
public String myRatelimit(@PathVariable("id") Integer id);
}
步骤(在客户端 Feign80 修改):
① 在 pom 文件中导入依赖:
<!--resilience4j-ratelimiter-->
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-ratelimiter</artifactId>
</dependency>
② 修改 application.yml:
####resilience4j ratelimiter 限流的例子
resilience4j:
ratelimiter:
configs:
default:
limitForPeriod: 2 #在一次刷新周期内,允许执行的最大请求数(每次生成的令牌数)
limitRefreshPeriod: 1s # 限流器的刷新时间(每隔多少秒生成新的令牌)
timeout-duration: 1 # 线程等待权限的默认等待时间(请求等待令牌的最大时间)
instances:
cloud-payment-service:
baseConfig: default
注意:
i . timeout-duration 默认的单位是 ms,值为 1 代表 1 ms,值为 1s 代表 1s。
ii. 默认桶大小就是 limitForPeriod,也就是每个周期允许请求的最大数量
iii . 需要将上述隔离的配置注释掉,防止冲突
③ 修改 OrderCircuitController
@RestController
public class OrderCircuitController
{
@Resource
private PayFeignApi payFeignApi;
...
@GetMapping(value = "/feign/pay/ratelimit/{id}")
@RateLimiter(name = "cloud-payment-service",fallbackMethod = "myRatelimitFallback")
public String myBulkhead(@PathVariable("id") Integer id)
{
return payFeignApi.myRatelimit(id);
}
public String myRatelimitFallback(Integer id,Throwable t)
{
return "你被限流了,禁止访问/(ㄒoㄒ)/~~";
}
}
④ 测试:
刷新地址:http://localhost/feign/pay/ratelimit/11
正常后 F5 按钮狂刷一会儿,停止刷新看到被限流的效果(1s 刷 3 次就能看到效果)
(4)疑惑点
以下问题均是 gpt4 给出的回答, 我只是挑出重要的部分。如果有不对的地方,欢迎评论和私信
Question1:熔断、隔离、限流分别有什么作用?可以共存吗?
熔断器(Circuit Breaker):用于监控和控制服务的健康状态。如果服务出现连续的失败(例如,超过阈值的错误率),熔断器会将服务置于“打开”状态,拒绝后续请求,避免给已经失败的服务增加额外的负载。
- 作用范围:它作用于服务调用的健康性,决定是否继续向服务发送请求。
限流器(Rate Limiter):用于控制请求的速率。它通过限制请求的数量(例如,每秒只能处理 10 个请求),防止系统过载,确保系统不会因为请求过多而崩溃。
- 作用范围:它控制请求的流量,通过令牌桶等机制管理系统接收请求的速率。
隔离(Thread Isolation / Semaphore Isolation):隔离用于将请求从主线程池中分离出来,避免请求之间的相互干扰,确保请求处理不会影响到其他业务逻辑。
- 作用范围:对于高延迟或外部调用,可以使用线程隔离或信号量隔离,防止过多的请求占用系统资源。
Resilience4j 的 熔断器、限流器 和 隔离 是独立的机制,它们分别处理不同类型的问题。它们可以配合使用,提升系统的稳定性和可用性。但需要根据业务场景合理配置,避免因过度保护而导致不必要的开销或过度拒绝请求。
通常,限流 会先拒绝过多的请求,熔断器 会在服务不健康时拒绝请求,隔离 则保证资源密集型请求的独立执行。合理的配置和调优可以确保它们不会相互干扰,并能够有效提升系统的弹性和稳定性。
Question2:隔离和限流有什么区别?不都是限制请求过多吗?
限流:是控制请求的速率,避免系统因为过多的请求而超载。其核心思想是对进入系统的请求数进行限制,从而确保系统不会被过多的请求淹没,进而保持高可用性和稳定性。
举例:
- 在一个高并发的电商网站上,限流可以防止在大促期间,用户频繁刷新页面或重复请求导致服务崩溃。
隔离:是指通过将不同的服务或操作分开,防止某个服务或操作的失败或高负载影响到其他服务。隔离的关键目标是避免故障扩散,尤其是在微服务架构中,一个服务的失败不应该影响到其他服务。
举例:
- 在一个在线支付系统中,支付服务可能会遇到高并发请求,通过隔离机制可以将支付服务与其他服务(如订单查询、用户信息)进行分离,使得支付服务的高并发请求不会影响到其他业务操作。
需要注意的是,隔离既可以用在单个微服务内部,也可以用于微服务之间。
单个微服务内部的隔离:通常涉及资源池、线程池等内部资源的隔离,防止服务内部的不同功能模块相互影响,保证服务内部的稳定性。
多个微服务之间的隔离:通常通过消息队列、熔断器、服务限流等机制实现,确保一个服务的故障或负载过大不会影响其他服务的正常运行。
我们上述隔离的案例更倾向于单个微服务内部,在为支付服务实施隔离,通过限制支付服务的并发请求数,避免支付服务过载,而这和限流的效果确实相似。
所以,很多人会疑惑隔离和限流为什么这么像?
示例:线程池隔离
假设一个微服务有两个主要的功能:
- 支付请求:支付处理通常是高并发且资源密集型的。
- 订单查询:订单查询请求是较为轻量的,处理并发的压力相对较小。
在这种情况下,可以为这两个操作配置不同的线程池:
- 为支付请求配置一个线程池,并限制该线程池的最大并发数,例如设置为2个。
- 为订单查询配置另一个线程池,可能不需要限制并发数,或者可以设置更高的并发数限制。
当支付请求的并发数达到限制时,支付线程池会拒绝处理更多请求,避免支付请求占用过多资源影响订单查询等其他操作。
十、Sleuth(Micrometer) + ZipKin分布式链路追踪
1.前言
(1)Sleuth
Sleuth 目前已经进入了维护模式,它的核心功能已转移至 Micrometer Tracing!
(2)分布式链路追踪
在微服务架构中,一个由客户端发起的请求在后端系统中会经过多个不同的的服务节点调用来协同产生最后的请求结果,每一个前段请求都会形成一条复杂的分布式服务调用链路,链路中的任何一环出现高延时或错误都会引起整个请求最后的失败。
在分布式与微服务场景下,面对大规模分布式与微服务集群,我们需要解决如下问题:
- 如何实时观测系统的整体调用链路情况。
- 如何快速发现并定位到问题。
- 如何尽可能精确的判断故障对系统的影响范围与影响程度。
- 如何尽可能精确的梳理出服务之间的依赖关系,并判断出服务之间的依赖关系是否合理。
- 如何尽可能精确的分析整个系统调用链路的性能与瓶颈点。
- 如何尽可能精确的分析系统的存储瓶颈与容量规划。
上述问题就是我们的落地议题答案:
分布式链路追踪技术要解决的问题,分布式链路追踪(Distributed Tracing),就是将一次分布式请求还原成调用链路,进行日志记录,性能监控并将一次分布式请求的调用情况集中展示。比如各个服务节点上的耗时、请求具体到达哪台机器上、每个服务节点的请求状态等等。
(3)新一代Spring Cloud Sleuth:Micrometer
官网:https://micrometer.io/docs/tracing
Question:老项目还能用 Sleuth 开发吗?
可以,但 Sleuth 只支持 springboot 2.x,已经不支持现在的 springboot 3 了!
(4)zipkin
micrometer 提供了一套完整的分布式链路追踪(Distributed Tracing)解决方案且兼容支持了zipkin 展现。
简单来说:
micrometer 负责将一次分布式请求还原成调用链路,进行日志记录和性能监控。
zipkin 负责将一次分布式请求的调用情况收集起来,变成 web 网页集中展示。
扩展:行业内比较成熟的其它分布式链路追踪技术解决方案
2.分布式链路追踪原理
假定3个微服务调用的链路:Service1调用Service2,Service2调用Service3和Service4
那么一条链路追踪会在每个服务调用的时候加上Trace ID 和 Span ID
链路通过 TraceId 唯一标识,Span 标识发起的请求信息(Span:表示调用链路来源,通俗的理解 span 就是一次请求信息)
我们将上图简化:
一条链路通过 Trace Id 唯一标识,Span 标识发起的请求信息,各 span 通过 parent id 关联起来
1 | 第一个节点:Span ID = A,Parent ID = null,Service 1 接收到请求。 |
2 | 第二个节点:Span ID = B,Parent ID= A,Service 1 发送请求到 Service 2 返回响应给Service 1 的过程。 |
3 | 第三个节点:Span ID = C,Parent ID= B,Service 2 的 中间解决过程。 |
4 | 第四个节点:Span ID = D,Parent ID= C,Service 2 发送请求到 Service 3 返回响应给Service 2 的过程。 |
5 | 第五个节点:Span ID = E,Parent ID= D,Service 3 的中间解决过程。 |
6 | 第六个节点:Span ID = F,Parent ID= C,Service 3 发送请求到 Service 4 返回响应给 Service 3 的过程。 |
7 | 第七个节点:Span ID = G,Parent ID= F,Service 4 的中间解决过程。 |
8 | 通过 Parent ID 就可找到父节点,整个链路即可以进行跟踪追溯了。 |
3.zipkin
(1)概述
Zipkin 是一种分布式链路跟踪系统图形化的工具,Zipkin 是 Twitter 开源的分布式跟踪系统,能够收集微服务运行过程中的实时调用链路信息,并能够将这些调用链路信息展示到 Web 图形化界面上供开发人员分析,开发人员能够从 ZipKin 中分析出调用链路中的性能瓶颈,识别出存在问题的应用程序,进而定位问题和解决问题。
官网:https://zipkin.io/
Question:单有Sleuth(Micrometer)行不行?我不展示行不行?
说明:
当没有配置 Sleuth 链路追踪的时候,INFO 信息里面是 [passjava-question,,,],后面跟着三个空字符串。
当配置了 Sleuth 链路追踪的时候,追踪到的信息是 [passjava-question,504a5360ca906016,e55ff064b3941956,false] ,第一个是 Trace ID,第二个是 Span ID。
只有日志没有图,行!
但是观看不方便,不美观,所以引入图形化 Zipkin 链路监控是十分必要的。
(2)下载安装
下载网址:Quickstart · OpenZipkin
安装目录下打开 cmd 控制台:
输入:java -jar 你的jar包名
运行成功,浏览器打开:http://localhost:9411/zipkin/
4. Micrometer + ZipKin 搭建链路监控案例步骤
(1)总体父工程依赖包导入
由于 Micrometer Tracing 是一个门面工具自身并没有实现完整的链路追踪系统,具体的链路追踪另外需要引入的是第三方链路追踪系统的依赖:
依赖 | 作用 |
---|---|
micrometer-tracing-bom | 导入链路追踪版本中心,体系化说明 |
micrometer-tracing | 指标追踪 |
micrometer-tracing-bridge-brave | 一个Micrometer模块,用于与分布式跟踪工具 Brave 集成,以收集应用程序的分布式跟踪数据。 Brave是一个开源的分布式跟踪工具,它可以帮助用户在分布式系统中跟踪请求的流转,它使用一种称为"跟踪上下文"的机制,将请求的跟踪信息存储在请求的头部,然后将请求传递给下一个服务。 在整个请求链中,Brave会将每个服务处理请求的时间和其他信息存储到跟踪数据中,以便用户可以了解整个请求的路径和性能。 |
micrometer-observation | 一个基于度量库 Micrometer的观测模块,用于收集应用程序的度量数据。 |
feign-micrometer | 一个Feign HTTP客户端的Micrometer模块,用于收集客户端请求的度量数据。 |
zipkin-reporter-brave | 一个用于将 Brave 跟踪数据报告到Zipkin 跟踪系统的库。 |
补充包:spring-boot-starter-actuator:SpringBoot 框架的一个模块用于监视和管理应用程序
总体父工程(cloud2024)的 pom 文件
<?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.mihoyo.cloud</groupId>
<artifactId>cloud2024</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<modules>
<module>mybatis_generator2024</module>
<module>cloud-provider-payment8001</module>
<module>cloud-consumer-order80</module>
<module>cloud-api-commons</module>
<module>cloud-provider-payment8002</module>
<module>cloud-consumer-feign-order80</module>
</modules>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<hutool.version>5.8.22</hutool.version>
<druid.version>1.1.20</druid.version>
<mybatis.springboot.version>3.0.3</mybatis.springboot.version>
<mysql.version>8.0.11</mysql.version>
<swagger3.version>2.2.0</swagger3.version>
<mapper.version>4.2.3</mapper.version>
<fastjson2.version>2.0.40</fastjson2.version>
<persistence-api.version>1.0.2</persistence-api.version>
<spring.boot.test.version>3.1.5</spring.boot.test.version>
<spring.boot.version>3.2.0</spring.boot.version>
<spring.cloud.version>2023.0.0</spring.cloud.version>
<spring.cloud.alibaba.version>2023.0.0.0-RC1</spring.cloud.alibaba.version>
<micrometer-tracing.version>1.2.0</micrometer-tracing.version>
<micrometer-observation.version>1.12.0</micrometer-observation.version>
<feign-micrometer.version>12.5</feign-micrometer.version>
<zipkin-reporter-brave.version>2.17.0</zipkin-reporter-brave.version>
</properties>
<dependencyManagement>
<dependencies>
<!--springboot 3.2.0-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>${spring.boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--springcloud 2023.0.0-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring.cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--springcloud alibaba 2023.0.0.0-RC1-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring.cloud.alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--SpringBoot集成mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis.springboot.version}</version>
</dependency>
<!--Mysql数据库驱动8 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<!--SpringBoot集成druid连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>${druid.version}</version>
</dependency>
<!--通用Mapper4之tk.mybatis-->
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper</artifactId>
<version>${mapper.version}</version>
</dependency>
<!--persistence-->
<dependency>
<groupId>javax.persistence</groupId>
<artifactId>persistence-api</artifactId>
<version>${persistence-api.version}</version>
</dependency>
<!-- fastjson2 -->
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>${fastjson2.version}</version>
</dependency>
<!-- swagger3 调用方式 http://你的主机IP地址:5555/swagger-ui/index.html -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>${swagger3.version}</version>
</dependency>
<!--hutool-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>${hutool.version}</version>
</dependency>
<!-- spring-boot-starter-test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>${spring.boot.test.version}</version>
<scope>test</scope>
</dependency>
<!--micrometer-tracing-bom导入链路追踪版本中心 1-->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-bom</artifactId>
<version>${micrometer-tracing.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--micrometer-tracing指标追踪 2-->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing</artifactId>
<version>${micrometer-tracing.version}</version>
</dependency>
<!--micrometer-tracing-bridge-brave适配zipkin的桥接包 3-->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-bridge-brave</artifactId>
<version>${micrometer-tracing.version}</version>
</dependency>
<!--micrometer-observation 4-->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-observation</artifactId>
<version>${micrometer-observation.version}</version>
</dependency>
<!--feign-micrometer 5-->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-micrometer</artifactId>
<version>${feign-micrometer.version}</version>
</dependency>
<!--zipkin-reporter-brave 6-->
<dependency>
<groupId>io.zipkin.reporter2</groupId>
<artifactId>zipkin-reporter-brave</artifactId>
<version>${zipkin-reporter-brave.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>
(2)服务提供者8001
步骤:
① pom 导入依赖
<!--micrometer-tracing指标追踪 1-->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing</artifactId>
</dependency>
<!--micrometer-tracing-bridge-brave适配zipkin的桥接包 2-->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-bridge-brave</artifactId>
</dependency>
<!--micrometer-observation 3-->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-observation</artifactId>
</dependency>
<!--feign-micrometer 4-->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-micrometer</artifactId>
</dependency>
<!--zipkin-reporter-brave 5-->
<dependency>
<groupId>io.zipkin.reporter2</groupId>
<artifactId>zipkin-reporter-brave</artifactId>
</dependency>
② 修改 application.yml
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/db2024?characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true
username: root
password: 123456# ========================mybatis===================
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.mihoyo.cloud.entities
configuration:
map-underscore-to-camel-case: trueserver:
port: 8001
# ========================zipkin===================
management:
zipkin:
tracing:
endpoint: http://localhost:9411/api/v2/spans
tracing:
sampling:
probability: 1.0 #采样率默认为0.1(0.1就是10次只能有一次被记录下来),值越大收集越及时。
③ 新建业务类 PayMicrometerController
@RestController
public class PayMicrometerController
{
/**
* Micrometer(Sleuth)进行链路监控的例子
* @param id
* @return
*/
@GetMapping(value = "/pay/micrometer/{id}")
public String myMicrometer(@PathVariable("id") Integer id)
{
return "Hello, 欢迎到来myMicrometer inputId: "+id+" \t 服务返回:" + IdUtil.simpleUUID();
}
}
(3)通用 Api 接口
修改 PayFeignApi:
@FeignClient(value = "cloud-payment-service")
public interface PayFeignApi
{
...
//Micrometer(Sleuth)进行链路监控的例子
@GetMapping(value = "/pay/micrometer/{id}")
public String myMicrometer(@PathVariable("id") Integer id);
}
(4)服务调用者80
步骤:
① pom 导入依赖
<!--micrometer-tracing指标追踪 1-->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing</artifactId>
</dependency>
<!--micrometer-tracing-bridge-brave适配zipkin的桥接包 2-->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-bridge-brave</artifactId>
</dependency>
<!--micrometer-observation 3-->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-observation</artifactId>
</dependency>
<!--feign-micrometer 4-->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-micrometer</artifactId>
</dependency>
<!--zipkin-reporter-brave 5-->
<dependency>
<groupId>io.zipkin.reporter2</groupId>
<artifactId>zipkin-reporter-brave</artifactId>
</dependency>
② 修改 application.yml
server:
port: 80
spring:
application:
name: cloud-consumer-openfeign-order
cloud:
consul:
host: localhost
port: 8500
discovery:
prefer-ip-address: true #优先使用服务ip进行注册
service-name: ${spring.application.name}
openfeign:
client:
config:
cloud-payment-service:
#连接超时时间,为避免演示出错,讲解完本次内容后设置为20秒
connectTimeout: 20000
readTimeout: 20000
httpclient:
hc5:
enabled: true # Apache HttpClient5 配置开启
compression:
request:
enabled: false
min-request-size: 2048 #最小触发压缩的大小
mime-types: text/xml,application/xml,application/json #触发压缩数据类型
response:
enabled: false
# zipkin图形展现地址和采样率设置
management:
zipkin:
tracing:
endpoint: http://localhost:9411/api/v2/spans
tracing:
sampling:
probability: 1.0 #采样率默认为0.1(0.1就是10次只能有一次被记录下来),值越大收集越及时。
③ 新建业务类 OrderMicrometerController
@RestController
@Slf4j
public class OrderMicrometerController
{
@Resource
private PayFeignApi payFeignApi;
@GetMapping(value = "/feign/micrometer/{id}")
public String myMicrometer(@PathVariable("id") Integer id)
{
return payFeignApi.myMicrometer(id);
}
}
(5)测试
浏览器访问:http://localhost/feign/micrometer/1
打开 zipkin
查看依赖关系: