一 前言
1.1 断路器介绍
断路器是一种开关装置,当某个服务单元发生故障后,通过断路器向调用方返回一个符合预期,可处理的备选响应。保证服务不会被长时间,不必要的占用,从而避免在分布式系统故障的蔓延、乃至雪崩。
断路器使用的判断策略有:基于访问数量的滑动窗口和基于时间的滑动窗口。
基于访问数量的滑动窗口统计最近N次调用的返回结果。
基于时间的滑动窗口统计N秒调用的返回结果。
1.2 断路器的工作流程
1.当满足一定的峰值和失败率达到一定条件后,断路器将会进入open状态,服务熔断。
当open的时候,所有请求都不会调用主业务逻辑方法,而是直接走fallbackmethod兜底的方法,服务降级,一段时间后,断路器会从open状态变为half_open半开状态,会放几个请求过去:
如果成功,断路器会关闭close,如果失败;继续开启,重复上述动作。
1.3 断路器与resilience4j的关系
circuit break 是一种规范和接口,resilience4j是具体实现
resilience4j 等于 resilience for java
resilience 弹性,张力
1.4 Resilience4j
resilience4j是一个轻量级的容错框架,能够实现断路器,限流器,重试等功能。
二 基于统计次数实现的熔断
2.1 消费端
满足50%错误后,触发熔断并给出服务降级,告知调用者服务不可用,此时就算是输入正确的访问地址也无法调用服务。它还在断路中(open状态),一会过度到半开并继续正确地址访问,慢慢切换到close状态,可以正常访问链路回复。
2.1.1 pom文件
1.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>
2.1.2.application配置
内容
# feign日志以什么级别监控哪个接口
#logging:
# level:
# com:
# jurf:
# ms:
# api:
# sb:
# PayFeginApi: debug
# 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:
ms-provider:
baseConfig: default
2.1.3.controller配置
@RestController
public class OrderCircuitController {
@Resource
private PayCircuitFegin payCircuitFegin;
@GetMapping(value = "/feign/pay/circuit/{id}")
@CircuitBreaker(name = "cloud-payment-service", fallbackMethod = "myCircuitFallback")
public String myCircuitBreaker(@PathVariable("id") Integer id)
{
return payCircuitFegin.myCircuit(id);
}
//myCircuitFallback就是服务降级后的兜底处理方法
public String myCircuitFallback(Integer id,Throwable t) {
// 这里是容错处理逻辑,返回备用结果
return "myCircuitFallback,系统繁忙,请稍后再试-----/(ㄒoㄒ)/~~";
}
}
2.1.4.启动类
@SpringBootApplication
@EnableDiscoveryClient
//@EnableFeignClients
@EnableFeignClients(basePackages="com.jurf.ms")//启用feign客户端,定义服务+绑定接口,以声明式的方法优雅而简单的实现服务调用
public class FeginApp8081
{
public static void main( String[] args )
{
SpringApplication.run(FeginApp8081.class,args);
System.out.println( "Hello World!" );
}
}
2.2 fegin工具api
2.3 服务端
2.3.1 结构
2.3.2 业务类的编写
@RestController
public class PayCircuitController {
//=========Resilience4j CircuitBreaker 的例子
@GetMapping(value = "/pay/circuit/{id}")
public String myCircuit(@PathVariable("id") Integer id)
{
System.out.println("调到服务端: 参数为:"+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();
}
}
2.3.3 启动类
@SpringBootApplication
@MapperScan("com.jurf.ms.provider9091.mapper") //import tk.mybatis.spring.annotation.MapperScan;
@EnableDiscoveryClient
@RefreshScope // 动态刷新
public class App9091
{
public static void main( String[] args )
{
SpringApplication.run(App9091.class,args);
System.out.println( "Hello World!" );
}
}
2.4 测试验证
2.4.1 基础服务启动
1.确保注册中心consul服务启动起来;
2.确保zipkin服务启动起来:
D:\>java -jar zipkin-server-2.23.4-exec.jar
2.4.2 应用服务启动
1.启动服务
2.访问测试
1.异常地址:http://localhost:8081/feign/pay/circuit/-4
2.正常地址:http://localhost:8081/feign/pay/circuit/123
当触发失败率达到50%时候,正常的访问的地址也报,服务降级提示
三 基于时间的熔断
3.1 逻辑思想
3.2 application配置
配置内容
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:
ms-provider:
baseConfig: default
3.3 测试
3.3.1 基础服务启动
1.确保注册中心consul服务启动起来;
2.确保zipkin服务启动起来:
D:\>java -jar zipkin-server-2.23.4-exec.jar
3.3.2 应用服务启动
1.服务启动
2.测试
1.开启异常4个窗口:http://localhost:8081/feign/pay/circuit/9999
2.开启一个正常接口: http://localhost:8081/feign/pay/circuit/123
3.出现4个异常都慢调用,触发30%,则进行服务降级,影响到正常访问也无法访问
四 隔离
4.1 隔离
Resilence4j 提供了两种隔离的方式,可以限制并发执行的数量:
1.实现SemaphoneBulkhead 信号量舱壁
2.实现FixedThreadPoolBulkhead(固定线程池舱壁)
作用:用来限制对下游服务的最大并发数量的限制。
4.2 基于信号量的实操
4.2.1 消费端的配置
1.pom文件
<!--resilience4j-bulkhead-->
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-bulkhead</artifactId>
</dependency>
2.配置文件
####resilience4j bulkhead 的例子
resilience4j:
bulkhead:
configs:
default:
maxConcurrentCalls: 1 # 隔离允许并发线程执行的最大数量
maxWaitDuration: 200ms # 当达到并发调用数量时,新的线程的阻塞时间,我只愿意等待1秒,过时不候进舱壁兜底fallback
instances:
ms-provider:
baseConfig: default
timelimiter:
configs:
default:
timeout-duration: 20s
3.业务类
/**
*(船的)舱壁,隔离
* @param id
* @return
*/
@GetMapping(value = "/feign/pay/bulkhead999/{id}")
@Bulkhead(name = "ms-provider",fallbackMethod = "myBulkheadFallback",type = Bulkhead.Type.SEMAPHORE)
public String myBulkhead(@PathVariable("id") Integer id)
{
return payCircuitFegin.myBulkhead(id);
}
public String myBulkheadFallback(Throwable t)
{
return "myBulkheadFallback,隔板超出最大数量限制,系统繁忙,请稍后再试-----/(ㄒoㄒ)/~~";
}
4.2.2 openfign的公用接口
1.接口代码
/**
* Resilience4j Bulkhead 的例子
* @param id
* @return
*/
@GetMapping(value = "/pay/bulkhead/{id}")
public String myBulkhead(@PathVariable("id") Integer id);
2.截图
4.2.3 消费提供端
1.工程结构
2.业务类
//=========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)
{
System.out.println("99999");
try { TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return "Hello, bulkhead! inputId: "+id+" \t " + IdUtil.simpleUUID();
}
4.2.4 测试验证
4.2.4.1 基础服务启动
1.确保注册中心consul服务启动起来;
2.确保zipkin服务启动起来:
D:\>java -jar zipkin-server-2.23.4-exec.jar
4.2.4.2 应用服务启动
1.默认配置概述
2.测试方式
访问地址:
http://localhost:8081/feign/pay/bulkhead999/9999
多打开几个请求这个地址的窗口,这里打开5个
其中第6个,最后一个访问地址:http://localhost:8081/feign/pay/bulkhead999/44
可以看到出现了,服务降级提示。
4.3 基于线程池实现隔离
4.3.1 消费端
1.pom文件
<!--resilience4j-bulkhead-->
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-bulkhead</artifactId>
</dependency>
2.配置文件
####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:
ms-test666:
baseConfig: default
3.业务controller类:注意@Bulkhead注解的name为要调用提供服务的名称
@GetMapping(value = "/feign/pay/bulkhead/{id}")
@Bulkhead(name = "ms-test666",fallbackMethod = "myBulkheadPoolFallback",type = Bulkhead.Type.THREADPOOL)
public CompletableFuture<String> myBulkheadTHREADPOOL(@PathVariable("id") Integer id)
{
System.out.println(Thread.currentThread().getName()+"\t"+"---开始进入");
try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println(Thread.currentThread().getName()+"\t"+"---准备离开");
return CompletableFuture.supplyAsync(() -> payTest.myBulkhead(id)+"\t"+"Bulkhead.Type.THREADPOOL");
}
public CompletableFuture<String> myBulkheadPoolFallback(Integer id,Throwable t)
{
return CompletableFuture.supplyAsync(() -> "Bulkhead.Type.THREADPOOL,系统繁忙,请稍后再试-----/(ㄒoㄒ)/~~");
}
}
4.3.2 服务提供端
4.3.3 测试验证
4.3.3.1 基础服务启动
1.确保注册中心consul服务启动起来;
2.确保zipkin服务启动起来:
D:\>java -jar zipkin-server-2.23.4-exec.jar
4.3.3.2 应用服务启动
访问地址:
http://localhost:8081/feign/pay/bulkhead/1
http://localhost:8081/feign/pay/bulkhead/2
http://localhost:8081/feign/pay/bulkhead/3
http://localhost:8081/feign/pay/bulkhead/4
依次进行访问:
1.
2.
3.
4.第4个开始出现,服务降级
五 限流
5.1 限流常用策略
客户端常用的限流: 漏斗,漏桶,计数器,滑动窗口
服务端限流:主动丢弃部分不重要内容或者延迟处理的方式。
1.漏桶算法
5.2 实操案例
5.2.1 工程结构
5.2.2 消费端的配置
1.pom文件
<!--resilience4j-ratelimiter-->
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-ratelimiter</artifactId>
</dependency>
2.配置文件
####resilience4j ratelimiter 限流的例子
resilience4j:
ratelimiter:
configs:
default:
limitForPeriod: 2 #在一次刷新周期内,允许执行的最大请求数
limitRefreshPeriod: 1s # 限流器每隔limitRefreshPeriod刷新一次,将允许处理的最大请求数量重置为limitForPeriod
timeout-duration: 1 # 线程等待权限的默认等待时间
instances:
ms-provider:
baseConfig: default
3.业务类
@GetMapping(value = "/feign/pay/ratelimit/{id}")
@RateLimiter(name = "cloud-payment-service",fallbackMethod = "myRatelimitFallback")
public String myBulkheadCircuitFegin(@PathVariable("id") Integer id)
{
return payCircuitFegin.myRatelimit(id);
}
public String myRatelimitFallback(Integer id,Throwable t)
{
return "你被限流了,禁止访问/(ㄒoㄒ)/~~";
}
5.2.3 openfegin的API配置
5.2.4 服务提供端的配置
1.业务类配置
5.2.5 测试验证
5.2.5.1 基础服务启动
1.确保注册中心consul服务启动起来;
2.确保zipkin服务启动起来:
D:\>java -jar zipkin-server-2.23.4-exec.jar
5.2.5.2 应用服务启动
1.启动服务
2.访问页面
多刷新几次: