本文仅用于记录自己学习Spring Cloud CircuitBreaker来实现服务熔断及服务的限流和隔离的学习过程,若介绍有误,还请手下留情,敬请批评指正,遵循共同学习进步的原则。
一、服务熔断
状态: 三个普通的状态,关闭(CLOSED)、开启(OPEN)、半开(HALF_OPEN),其他状态包括有禁用(DISABLED)、强制开启(FORCED_OPEN)
怎么做
1.该pom
<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.该yml
spring: cloud: openfeign: circuitbreaker: enabled: true #是否开启 group: enabled: true #是否开启分组
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
代码上
@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ㄒ)/~~";
}
}
二.隔离
对下游并发的最大并发量进行限制
Resilience4j限制并发执行的数量
(1)SemaphoreBulkhead信号量舱壁
pom引入
<!--resilience4j-bulkhead-->
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-bulkhead</artifactId>
</dependency>
yml配置
resilience4j: bulkhead: configs: default: maxConcurrentCalls: 2 # 隔离允许并发线程执行的最大数量 maxWaitDuration: 1s # 当达到并发调用数量时,新的线程的阻塞时间,我只愿意等待1秒,过时不候进舱壁兜底fallback instances: cloud-payment-service: baseConfig: default timelimiter: configs: default: timeout-duration: 20s
构造方法
@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();
}
暴露接口
@GetMapping(value = "/pay/bulkhead/{id}")
public String myBulkhead(@PathVariable("id") Integer id);
设置错误提示
@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(Integer id,Throwable t){
return "myBulkheadFallback,隔板超出最大数量限制,系统繁忙,请稍后再试-----/(ㄒoㄒ)/~~";
}
(2)固定线程池仓壁
引入pom
<!--resilience4j-bulkhead-->
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-bulkhead</artifactId>
</dependency>
yml配置
server: port: 82 spring: application: name: cloud-consumer-openfeign-order cloud: consul: host: localhost port: 8500 discovery: service-name: ${spring.application.name} prefer-ip-address: true openfeign: client: config: default: connect-timeout: 3000 read-timeout: 3000 cloud-payment-service: connect-timeout: 20000 read-timeout: 20000 httpclient: hc5: enabled: true compression: request: enabled: true min-request-size: 2048 #最小触发压缩的大小 mime-types: text/xml,application/xml,application/json #触发压缩数据类型 response: enabled: true circuitbreaker: enabled: true #是否开启 # group: # enabled: true #是否开启分组 logging: level: com: #包名 lllmark: cloud: apis: PayFeignApi: debug ####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
加注解限制
@GetMapping(value = "/feign/pay/bulkheadT/{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ㄒ)/~~");
}
三.限流
被限流的接口
//=========Resilience4j ratelimit 的例子
@GetMapping(value = "/pay/ratelimit/{id}")
public String myRatelimit(@PathVariable("id") Integer id)
{
return "Hello, myRatelimit欢迎到来 inputId: "+id+" \t " + IdUtil.simpleUUID();
}
暴露该接口
/**
* Resilience4j Ratelimit 的例子
* @param id
* @return
*/
@GetMapping(value = "/pay/ratelimit/{id}")
public String myRatelimit(@PathVariable("id") Integer id);
pom引入依赖
<!--resilience4j-ratelimiter-->
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-ratelimiter</artifactId>
</dependency>
yml配置
resilience4j:
ratelimiter:
configs:
default:
limitForPeriod: 2 #在一次刷新周期内,允许执行的最大请求数
limitRefreshPeriod: 1s # 限流器每隔limitRefreshPeriod刷新一次,将允许处理的最大请求数量重置为limitForPeriod
timeout-duration: 1 # 线程等待权限的默认等待时间
instances:
cloud-payment-service:
baseConfig: default
限流配置代码
@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ㄒ)/~~";
}