微服务架构之均衡组件 Ribbon解析:和Feign一起使用

举报
qingting-fly 发表于 2021/07/02 17:34:26 2021/07/02
【摘要】  RestTemplate是Spring的同步客户端HTTP请求接口,它可以简化客户端与HTTP服务器之间的交互,并且它强制使用RESTful风格,它会处理HTTP连接,只需要使用者提供URLs和一些模板变量。 Spring Cloud为客户端负载均衡创建了特定的注解@LoadBalanced,我们只需要使用该注解修饰创建RestTemplate实例的@Bean函数,Spring Cloud...

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实现了FeignClient接口,负责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);
    }
}

LoadBalancerFeignClientexecute函数会将普通的Request对象转化为RibbonRequest,并使用FeignLoadBalancer实例来发送RibbonRequestexecute函数会首先将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;
}

FeignLoadBalancerFeign在不需要重试机制的情况下默认的负载均衡实现。它的execute函数的实现很简单,使用RibbonRequest对象的client来发送网络请求,然后将Response包装成RibbonResponse进行返回。RibbonRequestrequest函数返回的对象就是构造RibbonRequest对象时传入的delegate参数。该参数是Client接口的实例,Client接口是Feign真正发送网络请求的client,比如说OkHttpClientApacheClient

@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);
}

FeignLoadBalancerAbstractLoadBalancerAwareClient的子类,其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();
}

LoadBalancerCommandsubmit函数使用了响应式编程的原理,创建了一个Observable实例来订阅,从而使用通过负载均衡器选出的服务器来进行异步的网络请求。

LoadBalancerCommandsubmit的具体实现基于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)}

LoadBalancerCommandselectServer函数调用了LoadBalancerContextgetServerFromLoadBalancer函数。

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);
            }
        }
    });
}

LoadBalancerContextgetServerFromLoadBalancer函数。该函数中调用了ILoadBalancerchooseServer函数,从而完成了负载均衡中服务器的选择。这部分的具体实现,在之后的文章中会进行详细的讲述。

小结

LoadBalancerAutoConfiguration类初始化一个匿名的SmartInitializingSingleton实例在所有的RestTemplate实例初始化结束之后对该进行配置。根据Spring框架的原理,SmartInitializingSingletonafterSingletonsInstantiated会在所有单例类型的Bean实例创建完成之后被调用。在afterSingletonsInstantiated中,Ribbon使用RestTemplateCustomizerRestTemplate实例进行配置,加入自己的拦截器。

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。