微服务架构之均衡组件 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)