基于
spring-cloud-starter-netflix-ribbon-2.2.5.RELEASE。
// ================== 配置
/**
* 让restTemplate具备Ribbon负载均衡的能力
*
因为 {@link LoadBalancerAutoConfiguration} 中的 restTemplates字段上的注解@LoadBalanced, 所以以下必须注解上 @LoadBalanced
*/
@Bean
@LoadBalanced
RestTemplate restTemplate() {
return new RestTemplate();
}
// ================== 应用
@Autowired // 使用时就不需要注解@LoadBalanced了
RestTemplate restTemplate;
public String ribbonTest() {
return restTemplate.getForEntity("http://micro-provider-srv/hi?id=" + UUID.randomUUID().toString(), String.class).getBody();
}
LoadBalancerAutoConfiguration本配置类的主要功能:
@LoadBalanced的RestTemplate实例。RestTemplateCustomizer扩展将 LoadBalancerInterceptor实例应用到上一步收集到的每个RestTemplate实例中。// ===== 注意本类位于 spring-cloud-commons-2.2.5.RELEASE.jar。另外一个同名类则是位于 spring-cloud-loadbalancer-2.2.5.RELEASE.jar (这个是springcloud用来替代Ribbon的)
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RestTemplate.class)
@ConditionalOnBean(LoadBalancerClient.class) // 和下方将要讲到的 RibbonAutoConfiguration 呼应
@EnableConfigurationProperties(LoadBalancerRetryProperties.class)
public class LoadBalancerAutoConfiguration {
// 1. 收集满足规则的 RestTemplate Bean
@LoadBalanced
@Autowired(required = false)
private List<RestTemplate> restTemplates = Collections.emptyList();
@Bean
public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(
final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {
// 基于Spring扩展机制, 向容器中注入SmartInitializingSingleton实现类, 目的是为每个RestTemplate实例, 回调自定义RestTemplateCustomizer扩展实现。
// 下方紧跟着的代码就是向容器中注入一个RestTemplateCustomizer实现类
return () -> restTemplateCustomizers.ifAvailable(customizers -> {
for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
for (RestTemplateCustomizer customizer : customizers) {
customizer.customize(restTemplate);
}
}
});
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
static class LoadBalancerInterceptorConfig {
@Bean
public LoadBalancerInterceptor ribbonInterceptor(
LoadBalancerClient loadBalancerClient,
LoadBalancerRequestFactory requestFactory) {
return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
}
//向容器中注入一个RestTemplateCustomizer实现类, 于上面应用RestTemplateCustomizer的代码逻辑呼应。
@Bean
@ConditionalOnMissingBean
public RestTemplateCustomizer restTemplateCustomizer(
final LoadBalancerInterceptor loadBalancerInterceptor) {
return restTemplate -> {
List<ClientHttpRequestInterceptor> list = new ArrayList<>(
restTemplate.getInterceptors());
list.add(loadBalancerInterceptor);
// 为RestTemplatet添加Interceptor扩展
restTemplate.setInterceptors(list);
};
}
}
RibbonAutoConfiguration本配置类的主要功能:
RibbonClientSpecification实例(NamedContextFactory.Specification实现类)。作为将其作为参数向容器中注册为SpringClientFactory实例。注:这一点和 FeignAutoConfiguration中的操作相同,区别只在于后者收集的配置Bean是FeignClientSpecification,注入到容器中的FeignContext实例。// ============= 为了聚焦,代码有删减。
@Configuration
@Conditional(RibbonAutoConfiguration.RibbonClassesConditions.class)
@RibbonClients // 所以如果没有特殊配置需要, 不需要在项目中显式引入该注解。
@AutoConfigureAfter(
name = "org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration")
@AutoConfigureBefore({ LoadBalancerAutoConfiguration.class,
AsyncLoadBalancerAutoConfiguration.class }) // 注意本配置类的执行时机是在 LoadBalancerAutoConfiguration 之前(这个LoadBalancerAutoConfiguration )。下方有相关介绍。
@EnableConfigurationProperties({ RibbonEagerLoadProperties.class,
ServerIntrospectorProperties.class })
public class RibbonAutoConfiguration {
// 1. 收集向容器中注入的RibbonClientSpecification实例. 该实例的注入可以参见 由注解@RibbonClients引入的RibbonClientConfigurationRegistrar里的实现。
@Autowired(required = false)
private List<RibbonClientSpecification> configurations = new ArrayList<>();
@Autowired
private RibbonEagerLoadProperties ribbonEagerLoadProperties;
@Bean
@ConditionalOnMissingBean
public SpringClientFactory springClientFactory() {
// SpringClientFactory实现自NamedContextFactory, 类似的还有FeignContext. 所以前者是Ribbon相关,后者是Feign相关, 两者原理和思路一样.
SpringClientFactory factory = new SpringClientFactory();
factory.setConfigurations(this.configurations);
return factory;
}
// 上面注解表明了本配置类生效时机早于LoadBalancerAutoConfiguration, 而这里的注入则会满足 LoadBalancerAutoConfiguration 生效的条件之一。
@Bean
@ConditionalOnMissingBean(LoadBalancerClient.class)
public LoadBalancerClient loadBalancerClient() {
return new RibbonLoadBalancerClient(springClientFactory());
}
@Bean
@ConditionalOnMissingBean
public PropertiesFactory propertiesFactory() {
return new PropertiesFactory();
}
// 响应配置项 ribbon.eager-load.enabled=true, 利用Spring的扩展机制, 通过响应ApplicationReadyEvent事件, 实现Ribbon配置子容器的早期加载.
@Bean
@ConditionalOnProperty("ribbon.eager-load.enabled")
public RibbonApplicationContextInitializer ribbonApplicationContextInitializer() {
return new RibbonApplicationContextInitializer(springClientFactory(),
ribbonEagerLoadProperties.getClients());
}
......
}
关于注解@RibbonClients:
RibbonClientConfigurationRegistrar 的启用。RibbonClientConfigurationRegistrar参与Spring生命周期,根据@RibbonClients配置项向容器中注入 RibbonClientSpecification (继承自 NamedContextFactory.Specification ) 并最终被收集到 SpringClientFactory 中(注意看SpringClientFactory 的基类)。RibbonClientConfiguration 。 被 SpringClientFactory 直接在构造函数中写死方式引入SpringClientFactory,其会在 OkHttpRibbonCommandFactory ,HttpClientRibbonCommandFactory 等中使用,目的是获取各个client对应的自定义配置项。LoadBalancerInterceptor(实现负载均衡的入口)LoadBalancerInterceptor的内部实现其实很简单:拦截每个请求,将控制权转移给LoadBalancerClient实现者(本例中是RibbonLoadBalancerClient),进而实现客户端的负载均衡。
// 本类定义在 spring-cloud-commons-2.2.5.RELEASE.jar 中。
public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {
// LoadBalancerClient接口也是定义在spring-cloud-commons-2.2.5.RELEASE.jar 中。本例中实际实现类为 RibbonLoadBalancerClient。
private LoadBalancerClient loadBalancer;
@Override
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
final ClientHttpRequestExecution execution) throws IOException {
final URI originalUri = request.getURI();
String serviceName = originalUri.getHost();
Assert.state(serviceName != null,
"Request URI does not contain a valid hostname: " + originalUri);
return this.loadBalancer.execute(serviceName,
this.requestFactory.createRequest(request, body, execution));
}
}
RibbonLoadBalancerClient(负载均衡的实际实现)作为LoadBalancerClient实现类,我们重点关注其实现的choose , execute,reconstructURI 方法。
1. 实现choose(String serviceId)方法。
RibbonLoadBalancerClient对于其所继承接口ServiceInstanceChooser的实现。// RibbonLoadBalancerClient.java 对于ServiceInstanceChooser接口的实现
public ServiceInstance choose(String serviceId, Object hint) {
// 借助Ribbon机制, 选举出对应于当前serviceId的服务节点Server。
// 注意Server类型定义在 ribbon-loadbalancer-2.3.0.jar 中。
// 也正是在这个逻辑中, 最终将会回调用户定义的IRule实现。
Server server = getServer(getLoadBalancer(serviceId), hint);
if (server == null) {
return null;
}
// 将选举出的Server适配为RibbonServer(ServiceInstance接口的实现类)。
// 注意RibbonServer类型定义spring-cloud-netflix-ribbon-2.2.5.RELEASE.jar 中。
return new RibbonServer(serviceId, server, isSecure(server, serviceId),
serverIntrospector(serviceId).getMetadata(server));
}
2. 实现reconstructURI(ServiceInstance instance, URI original)方法。
RibbonLoadBalancerClient所继承的直接接口LoadBalancerClient定义的。@Override
public URI reconstructURI(ServiceInstance instance, URI original) {
// 这里的instance, 正是在上一步choose(...)方法中筛选出来的。
Assert.notNull(instance, "instance can not be null");
String serviceId = instance.getServiceId();
RibbonLoadBalancerContext context = this.clientFactory
.getLoadBalancerContext(serviceId);
URI uri;
Server server;
if (instance instanceof RibbonServer) {
RibbonServer ribbonServer = (RibbonServer) instance;
server = ribbonServer.getServer();
uri = updateToSecureConnectionIfNeeded(original, ribbonServer);
}
else {
server = new Server(instance.getScheme(), instance.getHost(),
instance.getPort());
IClientConfig clientConfig = clientFactory.getClientConfig(serviceId);
ServerIntrospector serverIntrospector = serverIntrospector(serviceId);
uri = updateToSecureConnectionIfNeeded(original, clientConfig,
serverIntrospector, server);
}
// 拼接出发起最终访问请求时的http地址。
return context.reconstructURIWithServer(server, uri);
}
3. 实现execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest方法。
RibbonStatsRecorder来做调用情况统计。外界可以通过向容器中注入扩展的RibbonLoadBalancerContext实现类来参与这部分统计过程,加入自定义逻辑。
在feign实例构建阶段,决定是否启用 loadbalancer是在 FeignClientFactoryBean.getTarget()中。(feign源码解析 - 初始化)
// FeignClientFactoryBean.java
<T> T getTarget() {
FeignContext context = applicationContext.getBean(FeignContext.class);
Feign.Builder builder = feign(context);
// 如果url配置项为空, 则表明是非明确指向http://xxx的调用, 而是指向基于服务名的调用, 所以需要启用loadbalancer.
if (!StringUtils.hasText(url)) {
if (!name.startsWith("http")) {
url = "http://" + name;
}
else {
url = name;
}
url += cleanPath();
return (T) loadBalance(builder, context,
new HardCodedTarget<>(type, name, url));
}
// .... 如果是指向明确http服务地址的调用, 代码略
}
通过实现feign.Client接口(典型如LoadBalancerFeignClient ),保持外界的Feign调用习惯。同时在LoadBalancerFeignClient实现中将控制权调度给 ribbon中的 com.netflix.client.IClient 接口实现类(典型如FeignLoadBalancer)。所以在 LoadBalancerFeignClient类中你同时可以看到 feign.Client 和 com.netflix.client.IClient 实现类(前者代表:LoadBalancerFeignClient,后者代表:FeignLoadBalancer )。
// LoadBalancerFeignClient类型对于 feign.Client 的实现
@Override
public Response execute(Request request, Request.Options options) throws IOException {
try {
URI asUri = URI.create(request.url());
String clientName = asUri.getHost();
URI uriWithoutHost = cleanUrl(request.url(), clientName);
// 注意这里的构造函数中, 将内部feign.Client接口实现类 delegate(本例中就是实现了feign.Client的 OkHttpClient) 作为参数传入. 这会在接下来的FeignLoadBalancer中得到应用。
FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(
this.delegate, request, uriWithoutHost);
IClientConfig requestConfig = getClientConfig(options, clientName);
// lbClient(clientName)返回值类型为 FeignLoadBalancer, 其实现了ribbon的 com.netflix.client.IClient 接口, 所以相当于请求处理控制权由feign转交给了 ribbon
// 注意这里FeignLoadBalancer 并没有直接调用对于ribbon的 com.netflix.client.IClient 接口的实现, 而是调用继承自基类的 executeWithLoadBalancer(...)方法。
// 在基类 executeWithLoadBalancer方法中主要做了两件事情:
// 1. 基于客户端负载均衡选举出的 com.netflix.loadbalancer.Server实例( ribbon-loadbalancer-2.3.0.jar),构建出最终能够直接访问的 http://ip:port/xx/yy 地址 。 这里的选举操作就会应用到 IRule 实现类。
// 2. 基于上一步,最终调用 FeignLoadBalancer对于ribbon的 com.netflix.client.IClient 接口的实现来拿到请求响应。
return lbClient(clientName)
.executeWithLoadBalancer(ribbonRequest, requestConfig).toResponse();
}
catch (ClientException e) {
IOException io = findIOException(e);
if (io != null) {
throw io;
}
throw new RuntimeException(e);
}
}
// FeignLoadBalancer.java类型对于 com.netflix.client.IClient的实现(ribbon接口)
@Override
public RibbonResponse execute(RibbonRequest request, IClientConfig configOverride)
throws IOException {
Request.Options options;
if (configOverride != null) {
RibbonProperties override = RibbonProperties.from(configOverride);
options = new Request.Options(override.connectTimeout(this.connectTimeout),
override.readTimeout(this.readTimeout));
}
else {
options = new Request.Options(this.connectTimeout, this.readTimeout);
}
// 关键时这句,从request中取出 feign.Client实现类, 这就呼应上面的, 作为构造参数注入的delegate, 也就是 `feign.Client` 实现类 `feign.okhttp.OkHttpClient` 。
Response response = request.client().execute(request.toRequest(), options);
return new RibbonResponse(request.getUri(), response);
}
最终整体逻辑调用链条如下:
HystrixInvocationHandler.this.dispatch.get(method).invoke(args) // feign入口。类似的还有FeignInvocationHandler。属于feign中对于 InvocationHandler 的实现。
SynchronousMethodHandler.invoke(Object[] argv)
SynchronousMethodHandler.executeAndDecode(RequestTemplate template, Options options)
LoadBalancerFeignClient.execute(Request request, Request.Options options) // feign.Client实现类
FeignLoadBalancer.executeWithLoadBalancer(final S request, final IClientConfig requestConfig) // `com.netflix.client.IClient` ribbon接口实现类
FeignLoadBalancer.execute(RibbonRequest request, IClientConfig configOverride) // `com.netflix.client.IClient` ribbon接口实现类
feign.okhttp.OkHttpClient.execute(RibbonRequest request, IClientConfig configOverride) // feign.Client实现类
对于LoadBalancerFeignClient 实例,SpringCloud是借助 spring-cloud-openfeign-core-2.2.5.RELEASE.jar中定义的 FeignRibbonClientAutoConfiguration来实现的。
4.1 该类上采用 @Import方式引入 HttpClientFeignLoadBalancedConfiguration (feign.httpclient.enabled=true,默认就是它)和 OkHttpFeignLoadBalancedConfiguration (feign.okhttp.enabled=true) 和 DefaultFeignLoadBalancedConfiguration ,最终三选一。
4.2 以上@Import方式引入的feign.Client,统一都是 LoadBalancerFeignClient 。 其内部的 delegate 字段则是各自的 OkHttpClient / ApacheHttpClient 等,来完成实际的请求调用。
Spring容器里是没有这个 FeignLoadBalancer 实例的, 但是有 LoadBalancerFeignClient实例。所以前者是个过渡品。
最后将feign定义的http抽象调用接口, 和ribbon定义的http抽象调用接口放在一起,对比直观感受下。
########################################################## Feign
/**
* Submits HTTP {@link Request requests}. Implementations are expected to be thread-safe.
*/
public interface Client {
// Request 和 Response 类型为final, 不允许外界扩展
/**
* Executes a request against its {@link Request#url() url} and returns a response.
*
* @param request safe to replay.
* @param options options to apply to this request.
* @return connected response, {@link Response.Body} is absent or unread.
* @throws IOException on a network error connecting to {@link Request#url()}.
*/
Response execute(Request request, Options options) throws IOException;
}
########################################################## Ribbon
/**
* A client that can execute a single request.
*/
public interface IClient<S extends ClientRequest, T extends IResponse> {
// ClientRequest 和 IResponse 都允许外界扩展
/**
* Execute the request and return the response. It is expected that there is no retry and all exceptions
* are thrown directly.
*/
public T execute(S request, IClientConfig requestConfig) throws Exception;
}
Zuul与Ribbon的集成,入口是在类型 RibbonRoutingFilter 中。
// ================================= RibbonRoutingFilter.java
protected ClientHttpResponse forward(RibbonCommandContext context) throws Exception {
Map<String, Object> info = this.helper.debug(context.getMethod(),
context.getUri(), context.getHeaders(), context.getParams(),
context.getRequestEntity());
// command的实际类型, 视底层使用的http框架, 为 OkHttpRibbonCommand(对应okhttp), OkHttpRibbonCommand(对应apache httpclient)等
// RibbonCommand直接继承自 HystrixExecutable, 自身没有定义任何方法, 所以是个标识性接口. 这样的好处:
// 1. 一来实现了更强的自解释,
// 2. 二来也是通过父类的泛型类型限制了返回值类型ClientHttpResponse, 这一隶属于spring-web体系下的接口. 因为RibbonCommand是在spring-cloud-netflix-zuul模块中定义的, 所以这个约束是合适的.
也是因为复用了spring-web提供的ClientHttpResponse接口, java技术体系下的诸多http框架就能无缝扩展了. 这一点, 下一行代码我们马上就可以看到了.
RibbonCommand command = this.ribbonCommandFactory.create(context);
try {
// execute()方法, 实际定义在抽象类 HystrixCommand。"由主线程切换到对应的服务线程池线程"的操作, 正是在这里面实现的
// 返回值类型ClientHttpResponse属于spring-web模块,其中一个实现类为 RibbonHttpResponse, 负责将底层使用的http框架(apache httpclient, okhttp)的返回值适配到ClientHttpResponse.(这里Ribbon又进行了一层封装: HttpResponse).
// 所以最终的关系是:
// 1. HttpResponse接口的实现类负责将http框架(apache httpclient, okhttp)的返回值适配为ribbon IResponse. 毕竟com.netflix.client.http.HttpResponse就是直接继承自IResponse.
// 2. RibbonHttpResponse则负责将HttpResponse适配为ClientHttpResponse. 和上面一样的原理, RibbonHttpResponse继承自ClientHttpResponse, 同时通过构造函数要求使用者传入一个HttpResponse接口实现类.
ClientHttpResponse response = command.execute();
this.helper.appendDebug(info, response.getRawStatusCode(),
response.getHeaders());
return response;
}
catch (HystrixRuntimeException ex) {
return handleException(info, ex);
}
}
// ================================= AbstractRibbonCommand.java (hystrix体系下)
// 上述 command.execute() 的执行, 最终会将控制权跨线程转交给 run() 方法, 如下:
// AbstractRibbonCommand.run() ; 实现自基类HystrixCommand
protected ClientHttpResponse run() throws Exception {
final RequestContext context = RequestContext.getCurrentContext();
// 自身定义的抽象方法, 回调子类实现(例如 OkHttpRibbonCommand, HttpClientRibbonCommand)
// 也正是在
RQ request = createRequest();
RS response;
boolean retryableClient = this.client instanceof AbstractLoadBalancingClient
&& ((AbstractLoadBalancingClient) this.client)
.isClientRetryable((ContextAwareRequest) request);
// this.client 继承自 ribbon-loadbalancer中的AbstractLoadBalancerAwareClient , 自然也就是实现了ribbon IClient接口. 典型实现类: OkHttpLoadBalancingClient.
if (retryableClient) {
// 这里的方法调用来自 ribbon IClient
response = this.client.execute(request, config);
}
else {
// 这里的方法调用来自 AbstractLoadBalancerAwareClient, 不过最终依然会执行到 ribbon IClient的 execute 方法
response = this.client.executeWithLoadBalancer(request, config);
}
context.set("ribbonResponse", response);
// Explicitly close the HttpResponse if the Hystrix command timed out to
// release the underlying HTTP connection held by the response.
//
if (this.isResponseTimedOut()) {
if (response != null) {
response.close();
}
}
return new RibbonHttpResponse(response);
}
// zuul --> riibon
RibbonRoutingFilter.run() // 【zuul】【主线程】
OkHttpRibbonCommand.execute() // #主线程, 开始将任务提交给子线程。
//【ribbon + hystrix】其父类AbstractRibbonCommand, 三个泛型参数分别对应执行当前请求的Client,当前请求request,当前请求的响应response。); 返回值类型 ClientHttpResponse( 属于spring-web,其中一个实现类为 RibbonHttpResponse)
OkHttpLoadBalancingClient.executeWithLoadBalancer() //# 专门的服务线程池线程,该方法定义在基类AbstractLoadBalancerAwareClient. OkHttpRibbonCommand的基类 AbstractRibbonCommand.run() 中调用该方法
OkHttpLoadBalancingClient.execute(...) // # 其实是实现了ribbon IClient.execute(...) ; 基于http框架(例如 okhttp)发起远程调用, 返回值类型 OkHttpRibbonResponse
注意:
RequestContext.getCurrentContext().addZuulRequestHeader(...) 方法添加的自定义HTTP Header,首先在Zuul的RibbonRoutingFilter中传递给Ribbon中的RibbonCommandContext,然后在OkHttpRibbonRequest中由RibbonCommandContext传递给okhttp3.Request, 最终实现http header的透传. (类似的还有RibbonApacheHttpRequest)########## ribbon启用哪个http框架
ribbon:
# HttpClientRibbonConfiguration
httpclient: # 默认启用
enabled: false
# OkHttpRibbonConfiguration
okhttp:
enabled: true
IResponse,spring-mvc里的ClientHttpResponse,它们之间如何关联上的?### ribbon 继承链
IResponse # ribbon-core
HttpResponse # ribbon-httpclient
OkHttpRibbonResponse # spring-cloud-netflix-ribbon-2.2.5.RELEASE.jar
RibbonApacheHttpResponse # spring-cloud-netflix-ribbon-2.2.5.RELEASE.jar
### spring-mvc 继承链
ClientHttpResponse
RibbonHttpResponse # spring-cloud-netflix-ribbon-2.2.5.RELEASE.jar 。负责将 HttpResponse(上方有apache httpclient, okhttp等多个实现类)实例适配到 ClientHttpResponse. 结合RibbonCommand父类HystrixExecutable限制了其execute()方法返回值类型为ClientHttpResponse, RibbonCommand子类之一OkHttpRibbonCommand, 至此闭环完成.
OkHttp3ClientHttpResponse
其实仔细观察这两个的区别还是挺明显的,但笔者在阅读源码期间碰壁好几次,因此这里特意总结下。
前者是基于 spring-cloud-commons-2.2.5.RELEASE.jar 定义的 LoadBalancerClient 接口
后者基于 ribbon-core-2.3.0.jar中定义的 com.netflix.client.IClient接口。 spring-cloud-netflix-ribbon-2.2.5.RELEASE.jar
双方都有实现接口 ServiceInstanceChooser (spring-cloud-commons-2.2.5.RELEASE.jar 定义)。
3.1 AbstractLoadBalancingClient对其实现是基于ribbon筛选出ribbon对象Server,然后再做一次封装为RibbonLoadBalancerClient.RibbonServer这一ServiceInstance实现类。(实现了从ribbon到spring-cloud-commons-2.2.5.RELEASE.jar 的适配。)
最后直观感受下层级关系:

综上,其实只介绍了Ribbon中的执行主线逻辑,在实现负载均衡中的诸多考量与对应解决方案,本文不做更多的涉猎,感兴趣的读者可以自行查找资料,或者看看底部参考链接。