前言一、微服务容错简介二、 Resilience4j
前言
这篇文章主要是用来记录自己学习spring-cloud的内容
一、微服务容错简介
在高并发访问之下,服务之间的相互调用频率会突然增加,引发系统负载过高,使得服务的稳定性降低,同时还有其他不确定因素引起雪崩(网络连接中断,服务宕机)。微服务容错组件提供了限流、隔离、降级、熔断等⼿段,可以有效保护微服务系统。
- 隔离:微服务A调用B,B调用C,如果C出现故障,那么就会出现调用B的线程阻塞导致B的线程数持续增加知道CPU耗尽100%(微服务A也会因为B的故障而出现一样的问题),整个微服务不可用时,就需要对不可用的服务进行隔离。
隔离的分类:
- 线程池隔离:通过Java的线程池进⾏隔离,B服务调⽤C服务给予固定的线程数量比如12个线程,如果此时C服务宕机了就算⼤量的请求过来,调⽤C服务的接⼝只会占⽤12个线程不会占⽤其他⼯作线程资源,因此B服务就不会出现故障。
信号量隔离:隔离信号量隔离是使⽤Semaphore来实现的,当拿不到信号量的时候直接拒接因此不会出现超时占⽤其他⼯作线程的情况。(因为当服务C宕机时,信号量会一直被其中一个线程拿着不会释放。)线程池隔离和信号量隔离的区别:
信号量隔离每次都需要去拿到信号量,所以不支持异步
熔断器模型的状态:
1. Closed:关闭状态,所有请求正常访问。 2. Open:断路器打开,所有请求降级。熔断器开始对请求情况计数,当失败请求百分比达到阈值(在一定时间内),就会触发熔断,六安路器完全打开。 3. Half Open:半开状态,不是永久的,断路器打开后进入休眠时间,随后进入半开状态。释放部分请求通过,如果请求是正常的,断路器重回关闭状态,否则保持打开,再次进行休眠计数。
- 降级:降级是指当⾃身服务压⼒增⼤时,系统将某些不重要的业务或接⼝的功能降低,可以只提供部分功能,也可以完全停⽌所有不重要的功能。限流:限流,就是限制最⼤流量。系统能提供的最⼤并发有限,同时来的请求⼜太多,就需要限流。(进程排队执行)
Resilience4j提供了提供了⼀组⾼阶函数(装饰器),包括断路器,限流器,重试机制,隔离机制。
Resilience4j的核⼼模块:
- resilience4j-circuitbreaker: 熔断resilience4j-ratelimiter: 限流resilience4j-bulkhead: 隔离resilience4j-retry: ⾃动重试resilience4j-cache: 结果缓存resilience4j-timelimiter: 超时处理
resilience4j-circuitbreaker: 熔断的实践:
添加相应的依赖
org.springframework.cloud >spring-cloud-starter-circuitbreakerresilience4j
在订单微服务中配置断路器:
这里配置了两个断路器:backendA和backendB,backendA是基于default的配置,backendB配置了慢调用比例熔断(slowCallRateThreshold: 30 )而backendA配置了异常比例熔断
resilience4j:
circuitbreaker:
configs:
default:
failureRateThreshold: 30 #失败请求百分⽐,超过这个⽐例,CircuitBreaker变为OPEN状态
slidingWindowSize: 10 #滑动窗⼝的⼤⼩,配置COUNT_baseD,表示10个请求,配置TIME_baseD表示10秒
minimumNumberOfCalls: 5 #最⼩请求个数,只有在滑动窗⼝内,请求个数达到这个个数,才会触发CircuitBreader对于断路器的判断
slidingWindowType: TIME_baseD #滑动窗⼝的类型
permittedNumberOfCallsInHalfOpenState: 3 #当CircuitBreaker处于HALF_OPEN状态的时候,允许通过的请求个数
automaticTransitionFromOpenToHalfOpenEnabled: true #设置true,表示⾃动从OPEN变成HALF_OPEN,即使没有请求过来
waitDurationInOpenState: 2s #从OPEN到HALF_OPEN状态需要等待的时间
recordExceptions: #异常名单
- java.lang.Exception
instances:
backendA:
baseConfig: default #熔断器backendA,继承默认配置default
backendB:
failureRateThreshold: 50
slowCallDurationThreshold: 2s #慢调⽤时间阈值,⾼于这个阈值的呼叫视为慢调⽤, 并增加慢调⽤⽐例。
slowCallRateThreshold: 30 #慢调⽤百分⽐阈值,断路器把调⽤时间⼤于slowCallDurationThreshold,视为慢调⽤,当慢调⽤⽐例⼤于阈值,断路器打开,并进⾏服务降级
slidingWindowSize: 10
slidingWindowType: TIME_baseD
minimumNumberOfCalls: 2
permittedNumberOfCallsInHalfOpenState: 2
waitDurationInOpenState: 2s #从OPEN到HALF_OPEN状态需要等待的时间
OrderController代码:
@GetMapping("/payment/{id}")
@CircuitBreaker(name = "backendA", fallbackMethod = "fallback")
public ResponseEntity getPaymentById(@PathVariable("id") Integer id) throws Exception{
log.info("进入订单服务方法...");
Thread.sleep(10000); //用于测试慢调用比例熔断
String url = "http://cloud-payment-service/payment/" + id;
Payment payment = restTemplate.getForObject(url, Payment.class);
log.info("离开订单服务方法...");
return ResponseEntity.ok(payment);
}
//熔断后执行的方法
public ResponseEntity fallback(Integer id, Throwable e) {
e.printStackTrace();
Payment payment = new Payment();
payment.setId(id);
payment.setMessage("fallback...");
return new ResponseEntity<>(payment, HttpStatus.BAD_REQUEST);
}
当关闭支付微服务,订单微服务无法调用,当一次并发发送20次请求,如果超过失败请求比(30%: failureRateThreshold: 30),断路器将打开,两秒后进入半开状态(waitDurationInOpenState: 2s),再次发送请求但仅允许三次请求通过(permittedNumberOfCallsInHalfOpenState: 3),如果请求是正常的则关闭否则继续进入打开状态。
backendB慢比例调用熔断:当一次并发发送20次请求,Thread.sleep(10000);这条语句将触发慢比例熔断,因为配置是不超过2秒(slowCallDurationThreshold: 2s)。其余和backendA一样。
隔离的实践:
- 信号量隔离(SemaphoreBulkhead)线程池隔离(FixedThreadPoolBulkhead)
添加依赖
io.github.resilience4j resilience4j-bulkhead 1.7.0
订单微服务配置信号量隔离
bulkhead:
configs:
default:
maxConcurrentCalls: 5 # 隔离允许并发线程执⾏的最⼤数量
maxWaitDuration: 20ms # 当达到并发调⽤数量时,新的线程的阻塞时间
instances:
backendA:
baseConfig: default
backendB:
maxWaitDuration: 10ms
maxConcurrentCalls: 20
Controller修改:
@GetMapping("/payment/{id}")
@Bulkhead(name = "backendA", fallbackMethod = "fallback", type = Bulkhead.Type.SEMAPHORE)
public ResponseEntity getPaymentById(@PathVariable("id") Integer id) throws Exception{
log.info("进入订单服务方法...");
//Thread.sleep(10000); //用于测试慢调用比例熔断
String url = "http://cloud-payment-service/payment/" + id;
Payment payment = restTemplate.getForObject(url, Payment.class);
log.info("离开订单服务方法...");
return ResponseEntity.ok(payment);
}
线程池配置:
thread-pool-bulkhead:
configs:
default:
maxThreadPoolSize: 4 # 最⼤线程池⼤⼩
coreThreadPoolSize: 2 # 核⼼线程池⼤⼩
queueCapacity: 2 # 队列容量
instances:
backendA:
baseConfig: default
backendB:
maxThreadPoolSize: 1
coreThreadPoolSize: 1
queueCapacity: 1
增加OrderService,因为FixedThreadPoolBulkhead只对CompletableFuture⽅法有效。所以必须创建返回CompletableFuture类型的⽅法。
@Service
@Slf4j
public class OrderService {
@Bulkhead(name = "backendA", type = Bulkhead.Type.THREADPOOL)
public CompletableFuture getPaymentById(Integer id) throws Exception{
log.info("进入订单服务方法...");
Thread.sleep(10000);
log.info("离开订单服务方法...");
return CompletableFuture.supplyAsync(() -> new Payment(id, "线程池隔离信息"));
}
}
修改OrderController:
@Autowired
private OrderService orderService;
@GetMapping("/payment/thread/{id}")
public ResponseEntity getPaymentByThread(@PathVariable("id") Integer id) throws Exception {
return ResponseEntity.ok(orderService.getPaymentById(id).get());
}
限流的实践:
依赖:
io.github.resilience4j resilience4j-ratelimiter 1.7.0
配置文件:
ratelimiter:
configs:
default:
timeoutDuration: 5 # 线程等待权限的默认等待时间
limitRefreshPeriod: 1s # 限流器每隔1s刷新⼀次,将允许处理的最⼤请求重置为2
limitForPeriod: 2 #在⼀个刷新周期内,允许执⾏的最⼤请求数
instances:
backendA:
baseConfig: default
backendB:
timeoutDuration: 5
limitRefreshPeriod: 1s
limitForPeriod: 5
修改OrderController:
@GetMapping("/payment/{id}")
@RateLimiter(name = "backendA", fallbackMethod = "fallback")
public ResponseEntity getPaymentById(@PathVariable("id") Integer id) throws Exception{
log.info("进入订单服务方法...");
//Thread.sleep(10000);
String url = "http://cloud-payment-service/payment/" + id;
Payment payment = restTemplate.getForObject(url, Payment.class);
log.info("离开订单服务方法...");
return ResponseEntity.ok(payment);
}



