Feign源码深度刨析-(7)终章:请求处理组件LoadBalancerCommand

“不积跬步,无以至千里。”

Controller中调用FeignClient 接口方法,经过动态代理机制,由FeignInvocationHandler 的invoke() 方法处理,转而交给SynchronousMethodHandler 的invoke() 方法处理,继续调用LoadBalancerFeignClient 的execute() 方法

return lbClient(clientName).executeWithLoadBalancer(ribbonRequest, requestConfig).toResponse();

clientName,服务名称,即被调服务名称

lbClient(String clientName),lbClient方法,把服务名称传进去,获取了一个FeignLoadBalancer,一个Feign负载均衡器!

private FeignLoadBalancer lbClient(String clientName) {
    return this.lbClientFactory.create(clientName);
}

猜想一下,这里面肯定是包含跟Ribbon整合的东西来实现负载均衡,极有可能是ILoadBalancer

public FeignLoadBalancer create(String clientName) {
    FeignLoadBalancer client = this.cache.get(clientName);
    if(client != null) {
        return client;
    }
    IClientConfig config = this.factory.getClientConfig(clientName);
    ILoadBalancer lb = this.factory.getLoadBalancer(clientName);
    ServerIntrospector serverIntrospector = this.factory.getInstance(clientName, ServerIntrospector.class);
    client = loadBalancedRetryFactory != null ? new RetryableFeignLoadBalancer(lb, config, serverIntrospector,
                                                                               loadBalancedRetryFactory) : new FeignLoadBalancer(lb, config, serverIntrospector);
    this.cache.put(clientName, client);
    return client;
}

可以看到,首先会尝试从缓存中根据服务名称获取一个FeignLoadBalancer,如果缓存中有,就直接返回了;第一次进来,缓存中肯定是没有的

ILoadBalancer lb = this.factory.getLoadBalancer(clientName);

public ILoadBalancer getLoadBalancer(String name) {
    return getInstance(name, ILoadBalancer.class);
}

这个factory就是服务名称对应的spring容器,这行代码,意思就是说根据服务名获取到对应的spring容器,然后获取容器里面的ILoadBalancer 组件,就是负载均衡组件,这个之前在Ribbon那里已经深入刨析过了,这里就不赘述了

不过这里还是要看一下,这个负载均衡组件用的是哪个,因为我们之前在Ribbon那里看过,如果使用的是

ZoneAwareLoadBalancer 的话,就是走ZoneAwareLoadBalancer 的初始化流程,从eureka client的本地注册表中加载server list(服务列表)到DomainExtractingServerList 里面,还会搞一个PollingServerListUpdater 之类的东西定时去更新eureka client本地的注册表信息,然后定时Ping一下来检查拉取的server list中Instance 的状态是不是ON 等等。。。

getLoadBalancer()

DynamicServerListLoadBalancer,这个是ZoneAwareLoadBalancer 的父类,可见这里用的就是Ribbon负载均衡的那一套东西!!!

ok,到这里,Feign与Ribbon、Eureka的整合已经搞清楚了,用的就是ZoneAwareLoadBalancer

因为我们没有配置重试相关的东西,所以loadBalancedRetryFactory!=null这个东西就不会成立,就会走 new FeignLoadBalancer(lb, config, serverIntrospector) 的逻辑把负载均衡器,Ribbon配置还有拦截器等封装进FeignLoadBalancer 里,并放进一个cache缓存,下次feign再来请求的时候,就可以走缓存来取了,最后把这个FeignLoadBalancer 返回

至此,lbClient(clientName) 方法就结束了,拿到了一个FeignLoadBalancer ,里面封装了Ribbon实现负载均衡的所有东西,并整合了Ribbon

回到 lbClient(clientName).executeWithLoadBalancer(ribbonRequest,requestConfig).toResponse(); 这行代码

executeWithLoadBalancer(ribbonRequest,requestConfig)

现在看看这个方法,看方法名称以及程序执行的步骤,应该就是最终的发送http请求到具体的server上拿到结果了

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 finalUri = reconstructURIWithServer(server, request.getUri());
                    S requestForServer = (S) request.replaceUri(finalUri);
                    try {
                        return Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));
                    } 
                    catch (Exception e) {
                        return Observable.error(e);
                    }
                }
            })
            .toBlocking()
            .single();
    } catch (Exception e) {
        Throwable t = e.getCause();
        if (t instanceof ClientException) {
            throw (ClientException) t;
        } else {
            throw new ClientException(e);
        }
    }

}

LoadBalancerCommand command = buildLoadBalancerCommand(request, requestConfig);

这里先搞出来一个LoadBalancerCommand,然后调用了command.submit并传入一个ServerOperation

这段逻辑,有经验的工程师一看就知道是一个异步回调的方式来提交最终的请求,其核心方法就是ServerOperation 的call()方法,一定会在某个时间来调用,而传入的Server 参数就是经过负载均衡组件挑选出来发送请求的地址,那么这个Server是在哪里确定的?不妨看看这个submit 方法

// Use the load balancer
Observable<T> o = 
    (server == null ? selectServer() : Observable.just(server))
    .concatMap(new Func1<Server, Observable<T>>() {
        @Override
        // Called for each server being selected
        public Observable<T> call(Server server) {
            context.setServer(server);
            final ServerStats stats = loadBalancerContext.getServerStats(server);

无关代码略过,只看有用的一段逻辑,Use the load balancer ,使用这个负载均衡器,学会从冗余代码中提取关键信息,看代码片段的注释也是一个不错的方式

首先会判断一下 server == null ,这里server我们没有设置过,所以会是null

然后 selectServer()

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

Server server = loadBalancerContext.getServerFromLoadBalancer(loadBalancerURI, loadBalancerKey);

Server svc = lb.chooseServer(loadBalancerKey);

这里,就是从FeignLoadBalancer 里面封装的ZoneAwareLoadBalancer 的chooseServer()方法中拿到一个Server,这里就不展开说了,一样的,Ribbon源码那里已经详细写过,感兴趣的可以回看一下我的Ribbon专题

所以,总结:server是通过FeignLoadBalancer 里封装的ILoadBalancer 组件的chooseServer()方法获取的

接着走,return operation.call(server).doOnEach(new Observer()…

调用operation 的call() 方法,把server传进去了!!!

ok,这不就回调了sumbit() 方法里传入的operation 的call 方法了嘛!

@Override
public Observable<T> call(Server server) {
    URI finalUri = reconstructURIWithServer(server, request.getUri());
    S requestForServer = (S) request.replaceUri(finalUri);
    try {
        return Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));
    } 
    catch (Exception e) {
        return Observable.error(e);
    }
}

方便阅读,我把这个方法再拷贝一遍

URI finalUri = reconstructURIWithServer(server, request.getUri());

这里是将拿到的最终发送请求的server填充进request的uri中

http:///test_1/zhangsan(uri) + localhost:10001(server) = http://localhost:10001/test_1/zhangsan(finalUri)

然后调用FeignLoadBalancer 的execute() 方法设置了一些超时和重试的参数,

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

可以看到,feign的超时时间默认是1秒

Request.Options

Response response = request.client().execute(request.toRequest(), options);

这里最终调用Feign 的client组件,发送http请求,获取最终响应。

SynchronousMethodHandler#executeAndDecode() 方法拿到Response之后

Object result = decode(response);

Object decode(Response response) throws Throwable {
    try {
        return decoder.decode(response, metadata.returnType());
    } catch (FeignException e) {
        throw e;
    } catch (RuntimeException e) {
        throw new DecodeException(e.getMessage(), e);
    }
}

使用Feign的decoder 组件根据接口方法的返回值类型反序列化,并把处理后的响应结果返回controller

Final response

至此,从Controller 调用feignclient(动态代理),到FeignInvocationHandler,到SynchronousMethodHandler的invoke() ,再到LoadBalancerFeignClient 的execute() 方法,直到最终LoadBalancerCommand 的submit() 获取Response,整个过程已经很清晰了,这里整个流程的一个处理,至少要做到脑子里有一个大图的,随时知道链路每一个节点,就算是把feign搞明白了,这一块不熟悉的建议多看几遍,现在feign在企业中作为一个客户端负载均衡组件使用还是比较广泛的。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
`feign-spring-mvc-starter` 是一个 Feign 的扩展,它支持使用 Spring MVC 注解来定义和调用 REST 服务。使用 `feign-spring-mvc-starter`,你可以像使用 Spring MVC 控制器一样定义 Feign 客户端,从而更方便地进行 REST 服务的开发。 在使用 `feign-spring-mvc-starter` 之前,你需要先了解 Feign 和 Spring MVC 的基本概念和用法。 Feign 是一个声明式的 Web 服务客户端,它可以帮助你更方便地定义和调用 REST 服务。Feign 的基本使用方法是定义一个接口,用于描述 REST 服务的 API,然后使用 Feign 注解来声明这个接口。 Spring MVC 是一个基于 Java 的 Web 框架,它提供了一组注解和 API,用于处理 Web 请求和响应。 `feign-spring-mvc-starter` 将 Feign 和 Spring MVC 结合起来,使你可以使用 Spring MVC 注解来定义和调用 REST 服务。使用 `feign-spring-mvc-starter`,你可以更方便地使用 Feign 来调用 REST 服务。 以下是一个使用 `feign-spring-mvc-starter` 的示例: 1. 添加 Maven 依赖 在 pom.xml 文件中添加以下依赖项: ```xml <dependency> <groupId>io.github.openfeign</groupId> <artifactId>feign-spring-mvc</artifactId> <version>5.3.1</version> </dependency> ``` 2. 定义 Feign 接口 定义一个 Feign 接口,用于描述 REST 服务的 API。例如: ```java @FeignClient(name = "example-service") public interface ExampleClient { @GetMapping("/example") String getExample(); } ``` 在这个接口中,我们使用了 `@FeignClient` 注解来声明这个接口是一个 Feign 客户端,并指定了服务的名称。然后,我们定义了一个 `getExample()` 方法,用于调用 example-service 服务的 /example 路径。 3. 定义 Spring MVC 控制器 定义一个 Spring MVC 控制器,用于处理来自客户端的请求。例如: ```java @RestController public class ExampleController { private final ExampleClient exampleClient; public ExampleController(ExampleClient exampleClient) { this.exampleClient = exampleClient; } @GetMapping("/") public String index() { return exampleClient.getExample(); } } ``` 在这个控制器中,我们注入了 `ExampleClient`,并在 `index()` 方法中使用它来调用 example-service 服务的 /example 路径。 4. 运行应用程序 现在,你可以运行应用程序并访问 http://localhost:8080/ ,你应该会看到来自 example-service 服务的响应。 这就是一个使用 `feign-spring-mvc-starter` 的示例。使用 `feign-spring-mvc-starter`,你可以更方便地使用 Feign 来调用 REST 服务。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值