1.系列文章
2. 前言
在你必须懂也可以懂的微服务系列三:服务调用一文中介绍了服务调用实现原理,在你必须懂也可以懂的微服务系列四:服务发现与负载均衡一文中介绍了服务发现以及负载均衡实现原理。
事实上远程服务调用过程中往往会出现一些我们无法预料的异常,针对这些异常如果不采取相关的处理措施,往往会带来无法想象的灾难。举个示例:服务提供者某个接口
响应慢,会造成服务调用者调用线程阻塞,当请求达到一定量,服务调用者资源被消耗完,也会出现无法对外提供服务的现象。
为了避免出现由于下游服务故障从而引起自身服务不可用的问题,作为服务调用方必须采取某种措施来进行自我保护,微服务中这种保护技术被称为熔断
,也是本文将要介绍的内容
3.熔断
3.1 熔断器介绍
3.1.1 熔断器配置
在使用熔断器
前需要进行相关参数配置,比如设置滑动窗口大小
、最小请求数
等,更多相关配置可参考Create and configure a CircuitBreaker
CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom()
.minimumNumberOfCalls(3)
.slidingWindowSize(10)
.build();
复制代码
3.1.2 熔断器注册表
熔断器注册表
存储熔断器
相关配置,因此可以指定熔断器配置
来创建熔断器注册表
CircuitBreakerRegistry circuitBreakerRegistry = CircuitBreakerRegistry.of(circuitBreakerConfig);
复制代码
3.1.3 创建熔断器
熔断器注册表
存储熔断器
相关配置,因此可以用来创建熔断器实例
CircuitBreaker circuitBreaker = circuitBreakerRegistry.circuitBreaker("hello");
复制代码
3.1.4 使用熔断器执行目标逻辑
要想实现熔断功能,就需要使用熔断器
包裹目标逻辑
CheckedFunction0<String> decoratedSupplier = CircuitBreaker.decorateCheckedSupplier(
circuitBreaker, () -> { throw new NullPointerException(); });
for (int i = 0; i < 10; i++) {
TimeUnit.SECONDS.sleep(1);
Try.of(decoratedSupplier).map(value -> value + " world'");
}
复制代码
3.2 熔断器实现
static <T> CheckedFunction0<T> decorateCheckedSupplier(CircuitBreaker circuitBreaker,
CheckedFunction0<T> supplier) {
return () -> {
// 1.获取许可,得到许可后执行业务逻辑
circuitBreaker.acquirePermission();
final long start = circuitBreaker.getCurrentTimestamp();
try {
T result = supplier.apply();
long duration = circuitBreaker.getCurrentTimestamp() - start;
// 2.执行成功
circuitBreaker.onResult(duration, circuitBreaker.getTimestampUnit(), result);
return result;
} catch (Exception exception) {
// Do not handle java.lang.Error
long duration = circuitBreaker.getCurrentTimestamp() - start;
// 3.执行失败
circuitBreaker.onError(duration, circuitBreaker.getTimestampUnit(), exception);
throw exception;
}
};
}
复制代码
3.2.1 熔断器执行失败逻辑处理
void record(long duration, TimeUnit durationUnit, Outcome outcome) {
++this.numberOfCalls;
this.totalDurationInMillis += durationUnit.toMillis(duration);
switch(outcome) {
case SLOW_SUCCESS:
++this.numberOfSlowCalls;
break;
case SLOW_ERROR:
++this.numberOfSlowCalls;
++this.numberOfFailedCalls;
++this.numberOfSlowFailedCalls;
break;
case ERROR:
++this.numberOfFailedCalls;
}
}
复制代码
每次执行失败都会对失败计数器
numberOfFailedCalls
、总请求计数器numberOfCalls
进行 + 1操作,调用该方法的上层方法使用了synchronized
关键字,因此是线程安全的
3.2.2 计算失败率
private float getFailureRate(Snapshot snapshot) {
int bufferedCalls = snapshot.getTotalNumberOfCalls();
if (bufferedCalls == 0 || bufferedCalls < minimumNumberOfCalls) {
return -1.0f;
}
return snapshot.getFailureRate();
}
复制代码
public float getFailureRate() {
return this.totalNumberOfCalls == 0 ? 0.0F : (float)this.totalNumberOfFailedCalls * 100.0F / (float)this.totalNumberOfCalls;
}
复制代码
通过总失败数 / 总请求数就可以得到请求失败率,不过此处有一点需要特别注意:
bufferedCalls < minimumNumberOfCalls
条件,也就是当请求数达到最小请求数minimumNumberOfCalls
才会计算失败率,否则不会进行计算。官网Create and configure a CircuitBreaker中也有对minimumNumberOfCalls
配置进行详细说明
3.2.3 检查是否超限
private Result checkIfThresholdsExceeded(Snapshot snapshot) {
float failureRateInPercentage = getFailureRate(snapshot);
float slowCallsInPercentage = getSlowCallRate(snapshot);
if (failureRateInPercentage == -1 || slowCallsInPercentage == -1) {
return Result.BELOW_MINIMUM_CALLS_THRESHOLD;
}
if (failureRateInPercentage >= failureRateThreshold
&& slowCallsInPercentage >= slowCallRateThreshold) {
return Result.ABOVE_THRESHOLDS;
}
// 当请求失败率超过配置的阀值(默认值50%),直接拒绝请求
if (failureRateInPercentage >= failureRateThreshold) {
return Result.FAILURE_RATE_ABOVE_THRESHOLDS;
}
if (slowCallsInPercentage >= slowCallRateThreshold) {
return Result.SLOW_CALL_RATE_ABOVE_THRESHOLDS;
}
return Result.BELOW_THRESHOLDS;
}
复制代码
当请求失败率超过配置的
failureRateThreshold
阀值后,会对后续的请求进行熔断,直接拒绝
3.3 熔断器总结
- 通过
CircuitBreakerConfig
进行熔断器配置 - 根据
CircuitBreakerConfig
配置创建CircuitBreakerRegistry
注册表 - 根据
CircuitBreakerRegistry
注册表创建CircuitBreaker
- 使用
CircuitBreaker
包裹目标业务逻辑 - 每次请求失败对请求失败数、总请求数进行 + 1操作
- 当请求数据超过最小请求数
minimumNumberOfCalls
后,计算失败率 - 当请求失败率阀值超过配置阀值
failureRateThreshold
后,拒绝后续请求
3.4 融合openfeign
openfeign
融合熔断器
功能,当调用目标方式时会回调org.springframework.cloud.openfeign.FeignCircuitBreakerInvocationHandler
的invoke
方法
3.5 组件包裹
public <T> T run(String id, Supplier<T> toRun, Function<Throwable, T> fallback, CircuitBreaker circuitBreaker,
TimeLimiter timeLimiter) {
Supplier<CompletionStage<T>> bulkheadCall = decorateBulkhead(id, toRun);
Supplier<CompletionStage<T>> timeLimiterCall = timeLimiter
.decorateCompletionStage(Executors.newSingleThreadScheduledExecutor(), bulkheadCall);
Supplier<CompletionStage<T>> circuitBreakerCall = circuitBreaker.decorateCompletionStage(timeLimiterCall);
try {
return circuitBreakerCall.get().toCompletableFuture().get();
}
catch (Exception e) {
System.out.println("exception " + e.getMessage());
return fallback.apply(e);
}
}
复制代码
3.2章节介绍了会使用
熔断器
包装目标业务逻辑,在SpringCloud
中集成了更多的组件:TimeLimiter
(调用时间限制)、Bulkhead
(调用资源隔离)、CircuitBreaker
(熔断)
执行顺序如下:
4.结语
只有对系统进采取相关措施进行保护,才会让系统更加健壮、持久对外提供服务