微服务架构之均衡组件 Ribbon解析:和Feign一起使用
RestTemplate
是Spring的同步客户端HTTP请求接口,它可以简化客户端与HTTP服务器之间的交互,并且它强制使用RESTful风格,它会处理HTTP连接,只需要使用者提供URLs和一些模板变量。
Spring Cloud
为客户端负载均衡创建了特定的注解@LoadBalanced
,我们只需要使用该注解修饰创建RestTemplate
实例的@Bean
函数,Spring Cloud
就会让RestTemplate
使用相关的负载均衡策略,在示例当中就是使用Ribbon
。
除了@LoadBalanced
之外,Ribbon
还提供@RibbonClient
注解。该注解是为ribbon client声明名称和配置的。,name
属性可以设置client的名称,configuration
属性则会设置Ribbon
相关的配置类。
和Feign一起使用
Ribbon
除了显示的和RestTemplate
一起使用之外,还会是Feign
有关负载均衡的默认实现。
在前面的文章有关Feign
的中讲解了Feign
相关实例的初始化过程。FeignClientFactoryBean
是创造FeignClient
的工厂类。在其getObject
方法中有一个分支判断,如果请求URL不为空时,就会生成一个具有负载均衡的FeignClient
。在这个过程中,Feign
就默认引入了Ribbon
的负载均衡实现。
public Object getObject() throws Exception {
FeignContext context = applicationContext.getBean(FeignContext.class);
Feign.Builder builder = feign(context);
//如果url不为空,则需要负载均衡
if (!StringUtils.hasText(this.url)) {
String url;
if (!this.name.startsWith("http")) {
url = "http://" + this.name;
}
else {
url = this.name;
}
url += cleanPath();
//需要负载均衡的情况,下一章节再学习。
return loadBalance(builder, context, new HardCodedTarget<>(this.type,
this.name, url));
}
//....生成普通的FeignClient
}
根据Feign
的源码,loadBalance
函数会生成LoadBalancerFeignClient
实例进行返回。LoadBalancerFeignClient
实现了Feign
的Client
接口,负责Feign
中网络请求的发送和响应的接收,并带有客户端负载均衡机制。
protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
HardCodedTarget<T> target) {
//会得到`LoadBalancerFeignClient`
Client client = getOptional(context, Client.class);
if (client != null) {
builder.client(client);
Targeter targeter = get(context, Targeter.class);
return targeter.target(this, builder, context, target);
}
}
LoadBalancerFeignClient
的execute
函数会将普通的Request
对象转化为RibbonRequest
,并使用FeignLoadBalancer
实例来发送RibbonRequest
。execute
函数会首先将Request
的url转化为对应的服务名称,然后将构造出RibbonRequest
对象,接着调用lbClient
函数来生成FeignLoadBalancer
实例,最后调用FeignLoadBalancer
实例的executeWithLoadBalancer
函数来处理网络请求。
@Override
public Response execute(Request request, Request.Options options) throws IOException {
try {
//负载均衡时,host就是需要调用的service的name
URI asUri = URI.create(request.url());
String clientName = asUri.getHost();
URI uriWithoutHost = cleanUrl(request.url(), clientName);
//构造RibbonRequest,delegate一般就是真正发送网络请求的client,比如说OkHttpClient和ApacheClient。
FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(
this.delegate, request, uriWithoutHost);
IClientConfig requestConfig = getClientConfig(options, clientName);
//executeWithLoadBalancer是进行负载均衡的关键
return lbClient(clientName).executeWithLoadBalancer(ribbonRequest,
requestConfig).toResponse();
}
catch (ClientException e) {
IOException io = findIOException(e);
if (io != null) {
throw io;
}
throw new RuntimeException(e);
}
}
private FeignLoadBalancer lbClient(String clientName) {
//调用CachingSpringLoadBalancerFactory类的create方法。
return this.lbClientFactory.create(clientName);
}
lbClientFactory
参数是CachingSpringLoadBalancerFactory
的实例,它是带有缓存机制的FeignLoadBalancer
的工厂类。create
函数的clientName参数是指HTTP请求对应的服务端名称,它会首先使用这个名称去缓存中查找是否已经存在对应的实例。如果没有,在根据系统是否支持请求重试来创建出不同的FeignLoadBalancer
实例,最后将该实例存储到缓存中。
//CachingSpringLoadBalancerFactory.java
public FeignLoadBalancer create(String clientName) {
if (this.cache.containsKey(clientName)) {
return this.cache.get(clientName);
}
IClientConfig config = this.factory.getClientConfig(clientName);
ILoadBalancer lb = this.factory.getLoadBalancer(clientName);
ServerIntrospector serverIntrospector = this.factory.getInstance(clientName, ServerIntrospector.class);
//如果需要重试就是RetryableFeignLoadBalancer否则是FeignLoadBalancer。
FeignLoadBalancer client = enableRetry ? new RetryableFeignLoadBalancer(lb, config, serverIntrospector,
loadBalancedRetryPolicyFactory, loadBalancedBackOffPolicyFactory, loadBalancedRetryListenerFactory) : new FeignLoadBalancer(lb, config, serverIntrospector);
this.cache.put(clientName, client);
return client;
}
FeignLoadBalancer
是Feign
在不需要重试机制的情况下默认的负载均衡实现。它的execute
函数的实现很简单,使用RibbonRequest
对象的client来发送网络请求,然后将Response
包装成RibbonResponse
进行返回。RibbonRequest
的request
函数返回的对象就是构造RibbonRequest
对象时传入的delegate
参数。该参数是Client
接口的实例,Client
接口是Feign
真正发送网络请求的client,比如说OkHttpClient
和ApacheClient
。
@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);
}
Response response = request.client().execute(request.toRequest(), options);
return new RibbonResponse(request.getUri(), response);
}
FeignLoadBalancer
是AbstractLoadBalancerAwareClient
的子类,其executeWithLoadBalancer
会首先创建一个LoadBalancerCommand
实例,然后在该实例的submit
函数的回调中调用子类的execute
函数。
//AbstractLoadBalancerAwareClient.java 是FeignLoadBalancer的父类。
public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException {
//创建LoadBalancerCommand
LoadBalancerCommand<T> command = buildLoadBalancerCommand(request, requestConfig);
return command.submit(
new ServerOperation<T>() {
@Override
public Observable<T> call(Server server) {
URI finalUri = reconstructURIWithServer(server, request.getUri());
S requestForServer = (S) request.replaceUri(finalUri);
try {
//调用子类的execute函数,进行HTTP请求的处理
return Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));
}
catch (Exception e) {
return Observable.error(e);
}
}
})
.toBlocking()
.single();
}
buildLoadBalancerCommand
函数使用LoadBalancerCommand.Builder
来创建LoadBalancerCommand
实例,并将AbstractLoadBalancerAwareClient
作为LoadBalancerContext
接口的实例设置给command实例。
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();
}
LoadBalancerCommand
的submit
函数使用了响应式编程的原理,创建了一个Observable
实例来订阅,从而使用通过负载均衡器选出的服务器来进行异步的网络请求。
LoadBalancerCommand
的submit
的具体实现基于Observable
机制,较为复杂,笔者将submit
源码进行了简化,可以让读者在不了解Observable
的情况下了解源码的基本原理。在submit
函数中调用了selectServer
函数来选择一个server,这里就是进行负载均衡的地方。
public Observable<T> submit(final ServerOperation<T> operation) {
final ExecutionInfoContext context = new ExecutionInfoContext();
// 使用loadBalancerContext通过负载均衡来选择Server
Server server = loadBalancerContext.getServerFromLoadBalancer(loadBalancerURI, loadBalancerKey);
context.setServer(server);
//调用operation对象,就是调用AbstractLoadBalancerAwareClient中executeWithLoadBalancer函数中创建的匿名Operation对象。
return operation.call(server);
}
LoadBalancerCommand
的selectServer
函数调用了LoadBalancerContext
的getServerFromLoadBalancer
函数。
private Observable<Server> selectServer() {
return Observable.create(new OnSubscribe<Server>() {
@Override
public void call(Subscriber<? super Server> next) {
try {
Server server = loadBalancerContext.getServerFromLoadBalancer(loadBalancerURI, loadBalancerKey);
next.onNext(server);
next.onCompleted();
} catch (Exception e) {
next.onError(e);
}
}
});
}
LoadBalancerContext
的getServerFromLoadBalancer
函数。该函数中调用了ILoadBalancer
的chooseServer
函数,从而完成了负载均衡中服务器的选择。这部分的具体实现,在之后的文章中会进行详细的讲述。
小结
LoadBalancerAutoConfiguration
类初始化一个匿名的SmartInitializingSingleton
实例在所有的RestTemplate
实例初始化结束之后对该进行配置。根据Spring
框架的原理,SmartInitializingSingleton
的afterSingletonsInstantiated
会在所有单例类型的Bean实例创建完成之后被调用。在afterSingletonsInstantiated
中,Ribbon
使用RestTemplateCustomizer
对RestTemplate
实例进行配置,加入自己的拦截器。
- 点赞
- 收藏
- 关注作者
评论(0)