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

本文深入剖析了Feign如何利用Ribbon实现负载均衡。从Controller调用FeignClient开始,经过动态代理,FeignInvocationHandler和SynchronousMethodHandler,最后到LoadBalancerFeignClient的execute()方法。在execute()方法中,FeignLoadBalancer通过ILoadBalancer选择服务器,使用ZoneAwareLoadBalancer进行负载均衡。整个流程涉及缓存、Ribbon的负载均衡策略、Feign的超时设置和异常处理,最终发送HTTP请求并反序列化响应。理解这一流程对于掌握微服务间的调用至关重要。

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

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在企业中作为一个客户端负载均衡组件使用还是比较广泛的。

### Feign 调用中 POST 请求执行失败的排查与解决 在微服务架构下,Feign 客户端常用于服务间的通信。当出现 `feign.RetryableException: Read timed out executing POST` 异常时,表明客户端在尝试调用远程服务并等待响应的过程中发生了超时。 #### 1. 配置 Feign 客户端超时时间 Feign 默认的连接和读取超时时间较短,在网络状况不佳或服务响应较慢的情况下容易触发超时异常。可以通过配置文件调整 Feign 的连接和读取超时时间,以提高容错能力: ```yaml feign: client: config: default: connect-timeout: 20000 read-timeout: 20000 ``` 此配置将默认的连接和读取超时时间延长至 20 秒,有助于避免因短暂延迟导致的请求失败[^2]。 #### 2. 检查线程池配置 如果服务本身运行正常但仍然频繁出现连接重置(Connection reset)等异常,则可能是由于线程池配置不当引起的。例如,核心线程数过少可能导致并发请求处理不及时,从而引发超时或连接中断。建议根据实际负载情况合理设置线程池参数: ```java @Bean public Executor feignExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(30); executor.setMaxPoolSize(60); executor.setQueueCapacity(500); executor.setThreadNamePrefix("feign-pool-"); executor.initialize(); return executor; } ``` 通过增加核心线程数和最大线程数,可以提升并发处理能力,减少因资源不足导致的请求失败[^3]。 #### 3. 确认服务地址与网络可达性 若 Feign 客户端使用服务名进行调用,并且服务注册中心(如 Eureka、Nacos)未能正确解服务实例地址,则可能造成请求失败。此时可尝试直接通过 IP 地址指定服务端点: ```java @FeignClient(value = "leak", url = "http://192.168.104.71:8888") public interface LeakFeignServiceFeign { // 接口定义 } ``` 该方式绕过了服务发现机制,适用于测试环境或单节点部署场景,有助于排除服务发现组件带来的不确定性因素[^4]。 #### 4. 日志分与链路追踪 对于生产环境中偶发的 `feign.RetryableException`,应结合日志系统(如 ELK)和服务链路追踪工具(如 SkyWalking、Zipkin)进行深入分,定位是网络层问题、服务端性能瓶颈还是客户端配置不当所致。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值