系列文章:
SpringCloud 源码系列(1)— 注册中心Eureka 之 启动初始化
SpringCloud 源码系列(2)— 注册中心Eureka 之 服务注册、续约
SpringCloud 源码系列(3)— 注册中心Eureka 之 抓取注册表
SpringCloud 源码系列(4)— 注册中心Eureka 之 服务下线、故障、自我保护机制
SpringCloud 源码系列(5)— 注册中心Eureka 之 EurekaServer集群
SpringCloud 源码系列(6)— 注册中心Eureka 之 总结篇
SpringCloud 源码系列(7)— 负载均衡Ribbon 之 RestTemplate
SpringCloud 源码系列(8)— 负载均衡Ribbon 之 核心原理
SpringCloud 源码系列(9)— 负载均衡Ribbon 之 核心组件与配置
SpringCloud 源码系列(10)— 负载均衡Ribbon 之 HTTP客户端组件
Ribbon 重试
通过前面几篇文章的分析,可以知道有重试功能的其实有两个组件,一个是 Ribbon 的 LoadBalancerCommand,一个是 spring-retry 的 RetryTemplate。RetryableRibbonLoadBalancingHttpClient 和 RetryableOkHttpLoadBalancingClient 都要依赖 RetryTemplate,所以必须先引入 spring-retry 依赖,它们最终都是使用 RetryTemplate 实现请求重试的能力的。除了 RetryTemplate,其它客户端想要获取重试的功能,就要用 ribbon 中的 AbstractLoadBalancerAwareClient 相关的组件,并调用 executeWithLoadBalancer 方法。
负载均衡客户端
1、AbstractLoadBalancerAwareClient
再看下 AbstractLoadBalancerAwareClient 的体系,通过源码可以了解到:
- RetryableFeignLoadBalancer、RetryableRibbonLoadBalancingHttpClient、RetryableOkHttpLoadBalancingClient 都是使用
RetryTemplate实现重试功能的,也就是 spring-retry 的重试。 - RestClient、FeignLoadBalancer、RibbonLoadBalancingHttpClient、OkHttpLoadBalancingClient 是在 AbstractLoadBalancerAwareClient 中使用
LoadBalancerCommand实现重试功能的,就是是 Ribbon 的重试。
2、负载均衡调用
具体的 AbstractLoadBalancerAwareClient 客户端想要负载均衡调用以及能进行重试,需调用 AbstractLoadBalancerAwareClient 的 executeWithLoadBalancer 方法。
在这个方法里面,它先构建了 LoadBalancerCommand,然后用 command 提交了一个 ServerOperation,这个 ServerOperation 中对 URI 进行了 重构,转到具体的 LoadBalancerContext 去执行请求。
public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException {
// 负载均衡命令
LoadBalancerCommand<T> command = buildLoadBalancerCommand(request, requestConfig);
try {
return command.submit(
new ServerOperation<T>() {
@Override
public Observable<T> call(Server server) {
// 重构URI
URI finalUri = reconstructURIWithServer(server, request.getUri());
S requestForServer = (S) request.replaceUri(finalUri);
try {
// 使用具体的 AbstractLoadBalancerAwareClient 客户端执行请求
return Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));
}
catch (Exception e) {
return Observable.error(e);
}
}
})
.toBlocking()
.single();
}
}
3、构建 LoadBalancerCommand
再看 buildLoadBalancerCommand 方法,它首先会通过 getRequestSpecificRetryHandler() 方法获取请求重试处理器 RequestSpecificRetryHandler,而 getRequestSpecificRetryHandler() 是一个抽象方法。这里就要重点注意了。
// 抽象方法,获取请求重试处理器
public abstract RequestSpecificRetryHandler getRequestSpecificRetryHandler(S request, IClientConfig requestConfig);
protected LoadBalancerCommand<T> buildLoadBalancerCommand(final S request, final IClientConfig config) {
// 获取请求重试处理器
RequestSpecificRetryHandler handler = getRequestSpecificRetryHandler(request, config);
LoadBalancerCommand.Builder<T> builder = LoadBalancerCommand.<T>builder()
.withLoadBalancerContext(this)
.withRetryHandler(handler)
.withLoadBalancerURI(request.getUri());
customizeLoadBalancerCommandBuilder(request, config, builder);
return builder.build();
}
请求重试处理器
RequestSpecificRetryHandler
先了解下 RequestSpecificRetryHandler:
- 首先看它的构造方法,注意第一个参数和第二个参数,因为不同的 getRequestSpecificRetryHandler() 方法实现,主要差异就在于这两个参数。
- 然后看
isRetriableException,这个方法就是 LoadBalancerCommand 用来判断异常后是否需要重试的方法,可以了解到okToRetryOnAllErrors=true时就可以重试,否则okToRetryOnConnectErrors=true才可能重试。需要注意的是就算这个方法返回 true 也不一定会重试,这跟重试次数也是有一定关系的。
public RequestSpecificRetryHandler(boolean okToRetryOnConnectErrors, boolean okToRetryOnAllErrors, RetryHandler baseRetryHandler, @Nullable IClientConfig requestConfig) {
Preconditions.checkNotNull(baseRetryHandler);
this.okToRetryOnConnectErrors = okToRetryOnConnectErrors;
this.okToRetryOnAllErrors = okToRetryOnAllErrors;
this.fallback = baseRetryHandler;
if (requestConfig != null) {
// 在同一个Server上重试的次数
if (requestConfig.containsProperty(CommonClientConfigKey.MaxAutoRetries)) {
retrySameServer = requestConfig.get(CommonClientConfigKey.MaxAutoRetries);
}
// 重试下一个Server的次数
if (requestConfig.containsProperty(CommonClientConfigKey.MaxAutoRetriesNextServer)) {
retryNextServer = requestConfig.get(CommonClientConfigKey.MaxAutoRetriesNextServer);
}
}
}
@Override
public boolean isRetriableException(Throwable e, boolean sameServer) {
// 所有错误都重试
if (okToRetryOnAllErrors) {
return true;
}
// ClientException 才可能重试
else if (e instanceof ClientException) {
ClientException ce = (ClientException) e;
if (ce.getErrorType() == ClientException.ErrorType.SERVER_THROTTLED) {
return !sameServer;
} else {
return false;
}
}
else {
// 连接错误才重试,就是抛出 SocketException 异常时才重试
return okToRetryOnConnectErrors && isConnectionException(e);
}
}
不同HTTP客户端的重试处理器
1、RestClient
默认配置下,RestClient 的 getRequestSpecificRetryHandler 会走到最后一步,okToRetryOnConnectErrors、okToRetryOnAllErrors 都为 true,也就是说 isRetriableException 始终返回 true,也就是说抛出异常都会重试。非GET请求时,okToRetryOnAllErrors为 false,只有连接异常时才会重试。
@Override
public RequestSpecificRetryHandler getRequestSpecificRetryHandler(
HttpRequest request, IClientConfig requestConfig) {
if (!request.isRetriable()) {
return new RequestSpecificRetryHandler(false, false, this.getRetryHandler(), requestConfig);
}
if (this.ncc.get(CommonClientConfigKey.OkToRetryOnAllOperations, false)) {
return new RequestSpecificRetryHandler(true, true, this.getRetryHandler(), requestConfig);
}
if (request.getVerb() != HttpRequest.Verb.GET) {
return new RequestSpecificRetryHandler(true, false, this.getRetryHandler(), requestConfig);
} else {
// okToRetryOnConnectErrors、okToRetryOnAllErrors 都为 true
return new RequestSpecificRetryHandler(true, true, this.getRetryHandler(), requestConfig);
}
}
2、AbstractLoadBalancingClient
AbstractLoadBalancingClient 中的 getRequestSpecificRetryHandler 相当于一个默认实现,默认情况下 okToRetryOnAllOperations 为 false,最后也会到最后一步,即 okToRetryOnConnectErrors、okToRetryOnAllErrors 都为 true,isRetriableException 始终返回 true。非GET请求时,okToRetryOnAllErrors为 false,只有连接异常时才会重试。
@Override
public RequestSpecificRetryHandler getRequestSpecificRetryHandler(final S request, final IClientConfig requestConfig) {
// okToRetryOnAllOperations:是否所有操作都重试,默认 false
if (this.okToRetryOnAllOperations) {
return new RequestSpecificRetryHandler(true, true, this.getRetryHandler(), requestConfig);
}
if (!request.getContext().getMethod().equals("GET")) {
return new RequestSpecificRetryHandler(true, false, this.getRetryHandler(), requestConfig);
}
else {
return new RequestSpecificRetryHandler(true, true, this.getRetryHandler(), requestConfig);
}
}
3、RibbonLoadBalancingHttpClient
RibbonLoadBalancingHttpClient 也重载了 getRequestSpecificRetryHandler,但是它设置了 okToRetryOnConnectErrors、okToRetryOnAllErrors 都为 false,isRetriableException 始终返回 false。
至此我们应该就知道为什么调用 RibbonLoadBalancingHttpClient 的 executeWithLoadBalancer 不具备重试的功能的原因了。所以启用 apache httpclient 时,RibbonLoadBalancingHttpClient 调用是不支持重试的。
@Override
public RequestSpecificRetryHandler getRequestSpecificRetryHandler(RibbonApacheHttpRequest request, IClientConfig requestConfig) {
// okToRetryOnConnectErrors、okToRetryOnAllErrors 都为 false
return new RequestSpecificRetryHandler(false, false, RetryHandler.DEFAULT, requestConfig);
}
RetryableRibbonLoadBalancingHttpClient 中也重写了 getRequestSpecificRetryHandler,同样也是设置 okToRetryOnConnectErrors、okToRetryOnAllErrors 都为 false。但是在引入 spring-retry 后,它会使用 RetryTemplate 实现重试的功能。
@Override
public RequestSpecificRetryHandler getRequestSpecificRetryHandler(RibbonApacheHttpRequest request, IClientConfig requestConfig) {
// okToRetryOnConnectErrors、okToRetryOnAllErrors 都为 false
return new RequestSpecificRetryHandler(false, false, RetryHandler.DEFAULT, null);
}
4、OkHttpLoadBalancingClient
OkHttpLoadBalancingClient 并没有重写 getRequestSpecificRetryHandler,所以它是使用父类 AbstractLoadBalancingClient 中的方法,也就是 okToRetryOnConnectErrors、okToRetryOnAllErrors 都为 true。
所以,启用 okhttp 时,OkHttpLoadBalancingClient 是支持所有GET重试的,非GET请求则在抛出连接异常(SocketException)时支持重试。
而 RetryableOkHttpLoadBalancingClient 跟 RetryableRibbonLoadBalancingHttpClient 一样的重写方式,使用 RetryTemplate 实现重试。
@Override
public RequestSpecificRetryHandler getRequestSpecificRetryHandler(RibbonApacheHttpRequest request, IClientConfig requestConfig) {
// okToRetryOnConnectErrors、okToRetryOnAllErrors 都为 false
return new RequestSpecificRetryHandler(false, false, RetryHandler.DEFAULT, null);
}
LoadBalancerCommand 重试
看 LoadBalancerCommand 的 submit 方法,这个方法是重试的核心代码。
- 首先获取了同一个Server重试次数
maxRetrysSame和 重试下一个Server的次数maxRetrysNext,其实就是前面配置的ribbon.MaxAutoRetries和ribbon.MaxAutoRetriesNextServer,我设置的是 1。 - 然后创建了一个 Observable,它的第一层会先通过
loadBalancerContext获取 Server。在重试下一个 Server 时,这里就会获取下一个 Server。 - 在第二层,又创建了一个 Observable,这个 Observable 就是调用
ServerOperation的,就是重构 URI,调用具体的 AbstractLoadBalancerAwareClient 执行请求。 - 在第二层里,会根据
maxRetrysSame重试同一个 Server,从retryPolicy()中可以了解到,当重试次数大于maxRetrysSame后,同一个 Server 重试就结束了,否则就用retryHandler.isRetriableException()判断是否重试,这个前面已经分析过了。 - 在外层,则根据
maxRetrysNext重试不同的 Server,从retryPolicy()中可以了解到,当不同Server重试次数大于maxRetrysNext后,就重试结束了,整个重试也就结束了,如果还是失败,就会进入 onErrorResumeNext 进行最后的失败处理。
public Observable<T> submit(final ServerOperation<T> operation) {
final ExecutionInfoContext context = new ExecutionInfoContext();
// 同一个Server重试次数
final int maxRetrysSame = retryHandler.getMaxRetriesOnSameServer();
// 重试下一个Server的次数
final int maxRetrysNext = retryHandler.getMaxRetriesOnNextServer();
// 创建一个 Observable
Observable<T> o =
// 使用 loadBalancerContext 获取 Server
(server == null ? selectServer() : Observable.just(server))
.concatMap(new Func1<Server, Observable<T>>() {
@Override
public Observable<T> call(Server server) {
// 设置Server
context.setServer(server);
final ServerStats stats = loadBalancerContext.getServerStats(server);
// 创建 Observable
Observable<T> o = Observable
.just(server)
.concatMap(new Func1<Server, Observable<T>>() {
@Override
public Observable<T> call(final Server server) {
// 增加尝试次数
context.incAttemptCount();
// ...
// 调用 ServerOperation
return operation.call(server).doOnEach(new Observer<T>() {
// 一些回调方法
});
}
});
// 重试同一个Server
if (maxRetrysSame > 0)
o = o.retry(retryPolicy(maxRetrysSame, true));
return o;
}
});
if (maxRetrysNext > 0 && server == null)
// 重试不同Server
o = o.retry(retryPolicy(maxRetrysNext, false));
return o.onErrorResumeNext(new Func1<Throwable, Observable<T>>() {
@Override
public Observable<T> call(Throwable e) {
// 异常处理
return Observable.error(e);
}
});
}
// retryPolicy 返回一个是否重试的断言
private Func2<Integer, Throwable, Boolean> retryPolicy(final int maxRetrys, final boolean same) {
return new Func2<Integer, Throwable, Boolean>() {
@Override
public Boolean call(Integer tryCount, Throwable e) {
// 请求拒绝异常就不允许重试
if (e instanceof AbortExecutionException) {
return false;
}
// 尝试次数是否大于最大重试次数
if (tryCount > maxRetrys) {
return false;
}
// 使用 RequestSpecificRetryHandler 判断是否重试
return retryHandler.isRetriableException(e, same);
}
};
}
最后来总结一下 LoadBalancerCommand 重试:
- 重试分为同一个 Server 重试和重试下一个Server,当重试次数大于设置的重试值时,就停止重试。否则通过
retryHandler.isRetriableException()判断是否重试。 - 那这里一共请求了多少次呢?可以总结出如下公式:
请求次数 = (maxRetrysSame + 1) * (maxRetrysNext + 1),所以按 ribbon.MaxAutoRetries = 1、ribbon.MaxAutoRetriesNextServer = 1 的配置,如果每次请求都超时,总共就会发起 4 次请求。
RetryTemplate 重试
spring-retry
要启用 RetryTemplate 需先引入 spring-retry:
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
以 RetryableRibbonLoadBalancingHttpClient 为例,先看看它的 execute 方法,它先创建了负载均衡重试策略类 LoadBalancedRetryPolicy,然后将请求调用的逻辑封装到 RetryCallback 中,最后其实就是用 RetryTemplate 执行这个 RetryCallback,也就是说请求重试的逻辑都在 RetryTemplate 中。
public RibbonApacheHttpResponse execute(final RibbonApacheHttpRequest request, final IClientConfig configOverride) throws Exception {
//...
// 负载均衡重试策略 RibbonLoadBalancedRetryPolicy
final LoadBalancedRetryPolicy retryPolicy = loadBalancedRetryFactory.createRetryPolicy(this.getClientName(), this);
RetryCallback<RibbonApacheHttpResponse, Exception> retryCallback = context -> {
// ...
// delegate => CloseableHttpClient
final HttpResponse httpResponse = RetryableRibbonLoadBalancingHttpClient.this.delegate.execute(httpUriRequest);
// ...
// 成功 返回结果
return new RibbonApacheHttpResponse(httpResponse, httpUriRequest.getURI());
};
return this.executeWithRetry(request, retryPolicy, retryCallback, recoveryCallback);
}
private RibbonApacheHttpResponse executeWithRetry(RibbonApacheHttpRequest request, LoadBalancedRetryPolicy retryPolicy,
RetryCallback<RibbonApacheHttpResponse, Exception> callback,
RecoveryCallback<RibbonApacheHttpResponse> recoveryCallback) throws Exception {
RetryTemplate retryTemplate = new RetryTemplate();
// retryable => 取自 RibbonCommandContext 设置的 retryable 参数
boolean retryable = isRequestRetryable(request);
// 设置重试策略
retryTemplate.setRetryPolicy(retryPolicy == null || !retryable
? new NeverRetryPolicy() : new RetryPolicy(request, retryPolicy, this, this.getClientName()));
BackOffPolicy backOffPolicy = loadBalancedRetryFactory.createBackOffPolicy(this.getClientName());
retryTemplate.setBackOffPolicy(backOffPolicy == null ? new NoBackOffPolicy() : backOffPolicy);
// 利用 retryTemplate 执行请求 callback
return retryTemplate.execute(callback, recoveryCallback);
}
需要注意的是,在 executeWithRetry 中,会判断是否要重试,判断的逻辑中 getRetryable 其实就是取的 ApacheClientHttpRequest 中 executeInternal 方法里创建的 RibbonCommandContext 设置的 retryable 参数,这就和前面定制化的逻辑衔接上了。
private boolean isRequestRetryable(ContextAwareRequest request) {
if (request.getContext() == null || request.getContext().getRetryable() == null) {
return true;
}
return request.getContext().getRetryable();
}
RetryTemplate
进入 RetryTemplate 的 execute 方法,核心的逻辑我精简成如下代码,主要就是一个 while 循环判断是否可以重试,然后调用 retryCallback 执行请求。请求失败后,比如超时,抛出异常,就会用 registerThrowable() 来注册异常。
protected <T, E extends Throwable> T doExecute(RetryCallback<T, E> retryCallback,
RecoveryCallback<T> recoveryCallback, RetryState state) throws E, ExhaustedRetryException {
// retryPolicy => InterceptorRetryPolicy
RetryPolicy retryPolicy = this.retryPolicy;
BackOffPolicy backOffPolicy = this.backOffPolicy;
//....
try {
// ...
// canRetry 判断是否重试
while (canRetry(retryPolicy, context) && !context.isExhaustedOnly()) {
try {
// retryCallback 调用
return retryCallback.doWithRetry(context);
}
catch (Throwable e) {
// ...
// 注册异常
registerThrowable(retryPolicy, state, context, e);
// ...
}
}
exhausted = true;
return handleRetryExhausted(recoveryCallback, context, state);
}
//...
}
看 canRetry 方法,它实际是调用了 InterceptorRetryPolicy 的 canRetry。第一次调用时,会去获取 Server;否则就用 RibbonLoadBalancedRetryPolicy 判断是否重试下一个 Server,注意它判断的逻辑是 GET 请求或者允许所有操作操作重试,且 Server 重试次数 nextServerCount 小于等于配置的 MaxAutoRetriesNextServer。也就是说,while 循环判断的 canRetry 是重试下一个 Server 的。
protected boolean canRetry(RetryPolicy retryPolicy, RetryContext context) {
return retryPolicy.canRetry(context);
}
//////////// InterceptorRetryPolicy
public boolean canRetry(RetryContext context) {
LoadBalancedRetryContext lbContext = (LoadBalancedRetryContext) context;
if (lbContext.getRetryCount() == 0 && lbContext.getServiceInstance() == null) {
// 获取 Server
lbContext.setServiceInstance(this.serviceInstanceChooser.choose(this.serviceName));
return true;
}
// RibbonLoadBalancedRetryPolicy => 重试下一个Server
return this.policy.canRetryNextServer(lbContext);
}
///////// RibbonLoadBalancedRetryPolicy
public boolean canRetryNextServer(LoadBalancedRetryContext context) {
// 判断重试下一个Server
return nextServerCount <= lbContext.getRetryHandler().getMaxRetriesOnNextServer()
&& canRetry(context);
}
public boolean canRetry(LoadBalancedRetryContext context) {
// GET 请求或者允许所有操作重试时,就允许重试
HttpMethod method = context.getRequest().getMethod();
return HttpMethod.GET == method || lbContext.isOkToRetryOnAllOperations();
}
接着看请求失败后的注册异常 registerThrowable(),它最后会向 RibbonLoadBalancedRetryPolicy 注册异常。在 RibbonLoadBalancedRetryPolicy 的 registerThrowable() 方法中,如果不重试同一个Server且可以重试下一个Server,就会轮询获取下一个Server。如果可以在同一个Server上重试,sameServerCount 计数器就+1,否则重置 sameServerCount,然后 nextServerCount +1。
protected void registerThrowable(RetryPolicy retryPolicy, RetryState state,
RetryContext context, Throwable e) {
retryPolicy.registerThrowable(context, e);
registerContext(context, state);
}
///////// InterceptorRetryPolicy /////////
public void registerThrowable(RetryContext context, Throwable throwable) {
LoadBalancedRetryContext lbContext = (LoadBalancedRetryContext) context;
lbContext.registerThrowable(throwable);
// RibbonLoadBalancedRetryPolicy
this.policy.registerThrowable(lbContext, throwable);
}
///////// RibbonLoadBalancedRetryPolicy /////////
public void registerThrowable(LoadBalancedRetryContext context, Throwable throwable) {
//...
// 如果不在在同一个Server 上重试且可以重试下一个Server,则重新选择一个 Server
if (!canRetrySameServer(context) && canRetryNextServer(context)) {
context.setServiceInstance(loadBalanceChooser.choose(serviceId));
}
// 同一个Server重试超过设置的值后,就重置 sameServerCount
if (sameServerCount >= lbContext.getRetryHandler().getMaxRetriesOnSameServer()
&& canRetry(context)) {
// 重置 nextServerCount
sameServerCount = 0;
// 下一个Server重试次数+1
nextServerCount++;
if (!canRetryNextServer(context)) {
// 不能重试下一个Server了
context.setExhaustedOnly();
}
}
else {
// 同一个Server重试次数+1
sameServerCount++;
}
}
// 判断是否重试同一个Server
public boolean canRetrySameServer(LoadBalancedRetryContext context) {
return sameServerCount < lbContext.getRetryHandler().getMaxRetriesOnSameServer()
&& canRetry(context);
}
public boolean canRetry(LoadBalancedRetryContext context) {
// GET 请求或者允许所有操作重试时,就允许重试
HttpMethod method = context.getRequest().getMethod();
return HttpMethod.GET == method || lbContext.isOkToRetryOnAllOperations();
}
Ribbon 与 RetryTemplate 重试总结
1、首先,Ribbon 关于超时和重试的配置参数如下,这些参数也可以针对某个客户端配置
ribbon:
# 客户端读取超时时间
ReadTimeout: 1000
# 客户端连接超时时间
ConnectTimeout: 1000
# 默认只重试 GET,设置为 true 时将重试所有类型,如 POST、PUT、DELETE
OkToRetryOnAllOperations: false
# 同一个Server重试次数
MaxAutoRetries: 1
# 最多重试几个Server
MaxAutoRetriesNextServer: 1
2、RetryTemplate 是 spring-retry 的重试组件,LoadBalancerCommand 是 Ribbon 的重试组件。它们重试的请求次数是一样的,重试逻辑也是类似,都是先重试当前 Server,再重试下一个Server,总的请求次数 = (MaxAutoRetries + 1) * (MaxAutoRetriesNextServer + 1)。
3、但是有点差别的是,RetryTemplate 会判断请求方法为 GET 或者 OkToRetryOnAllOperations=true 时才允许重试,而 LoadBalancerCommand 是GET方法都可以重试,非GET方法在抛出连接异常时也可以重试。这个要注意下,一般只有GET才允许重试,因为GET是查询操作,接口是幂等的,而POST、PUT、DELETE一般是非幂等的。所以一般更建议使用 RetryTemplate,并且配置 OkToRetryOnAllOperations=false。
4、为了提升服务间通信性能,一般可以启用 apache HttpClient 或者 OkHttp,如果要启用重试功能,还需要引入 spring-retry 依赖。重试时,当前Server就不要重试了(MaxAutoRetries=0),直接重试下一个Server。
ribbon:
# 客户端读取超时时间
ReadTimeout: 1000
# 客户端连接超时时间
ConnectTimeout: 1000
# 默认只重试 GET,设置为 true 时将重试所有类型,如 POST、PUT、DELETE
OkToRetryOnAllOperations: false
# 同一个Server重试次数
MaxAutoRetries: 0
# 最多重试几个Server
MaxAutoRetriesNextServer: 1
# 启用 httpclient
httpclient:
enabled: false
# 启用 RestClient
restclient:
enabled: false
# 启用 okhttp
okhttp:
enabled: true
Ribbon 架构总结
最后,将 Ribbon 核心组件架构用两张类图总结下。