feign 如何获取请求真实目的ip地址

需求

最近小编的项目中出现了很多feign 调用出现 Read Time out 的异常,但因为没有集成链路追踪的第三方框架,查不到原因。

所以想到打印请求的ip地址,判断是指定的服务器出现的问题还是所有服务器都有这个问题,但是feign 打印异常日志不会显示目的端地址,这就很难受了没办法只能自己改装下

大致想法

需要改装肯定需要知道feign 具体请求调用的源码,大致需要知道下面几个问题

feign 集成了ribbon 如何在负载均衡之后获取真实的ip地址 feign 实际请求 http 源码在哪 能否替换 feign http 请求的组件源码解析

之前小编有两篇文章分析过 feign相关的源码

自定义 feign 调用实现 hystrix 超时、异常熔断

Feign 集成 Hystrix实现不同的调用接口不同的设置

这其中有个关键的源码位置在于 InvocationHandler 的 invoke 方法,在feign 组件中大致有两个类实现了此接口

FeignInvocationHandlerHystrixInvocationHandler

如果 项目中使用了 Hystrix 那么会用到HystrixInvocationHandler那个,否则一般是FeignInvocationHandler(自定义组件的除外)

那么此时只需要在invoke 方法中打个断点就行

此时跟踪到

feign.SynchronousMethodHandler#executeAndDecode
Object executeAndDecode(RequestTemplate template) throws Throwable {    Request request = targetRequest(template);.......    Response response;    long start = System.nanoTime();    try {      // 真正执行请求       response = client.execute(request, options);            response.toBuilder().request(request).build();    } catch (IOException e) {      ....      throw errorExecuting(request, e);    }    .....  }

通过debug就知道这个 client 是

LoadBalancerFeignClientorg.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient#execute
public Response execute(Request request, Request.Options options) throws IOException {try {URI asUri = URI.create(request.url());String clientName = asUri.getHost();URI uriWithoutHost = cleanUrl(request.url(), clientName);// 封装 ribbon 请求组件FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(this.delegate, request, uriWithoutHost);IClientConfig requestConfig = getClientConfig(options, clientName);// 这行是关键return // 获取 FeignLoadBalancerlbClient(clientName)// 负载之后请求真实的url// com.netflix.client.AbstractLoadBalancerAwareClient#executeWithLoadBalancer(....).executeWithLoadBalancer(ribbonRequest,requestConfig).toResponse();}catch (ClientException e) {....throw new RuntimeException(e);}}
com.netflix.client.AbstractLoadBalancerAwareClient#executeWithLoadBalancer(....)
public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException {        LoadBalancerCommand<T> command = buildLoadBalancerCommand(request, requestConfig);        try {        // 在com.netflix.loadbalancer.reactive.LoadBalancerCommand#submit 中会根据 负载均衡算法之后获取到真实的ip地址            return command.submit(                new ServerOperation<T>() {                    @Override                    // 传入的server 就是真实的ip                    public Observable<T> call(Server server) {                        URI finalUri = reconstructURIWithServer(server, request.getUri());                        // 路径替换把原本 http://client-name/xxxx 地址改为 http://127.0.0.1:9090/xxxx                        S requestForServer = (S) request.replaceUri(finalUri);                        try {                        // 请求父类中的 execute 方法,也就是 上面 lbClient(clientName) 返回的 FeignLoadBalancer                            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);            }        }            }
org.springframework.cloud.openfeign.ribbon.FeignLoadBalancer#execute
@Override public RibbonResponse execute(RibbonRequest request, IClientConfig configOverride)   throws IOException {  Request.Options options;  .....  // 这里的 request 就是 `org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient#execute`   // 封装的FeignLoadBalancer.RibbonRequest  // request.client() 返回就是 feign.Client.Default    Response response = request.client().execute(request.toRequest(), options);  return new RibbonResponse(request.getUri(), response); }
feign.Client.Default#execute
 @Override    public Response execute(Request request, Options options) throws IOException {      HttpURLConnection connection = convertAndSend(request, options);      return convertResponse(connection).toBuilder().request(request).build();    }

这里的request 中 url 就是真实的url资源路径了

现在屡屡逻辑

org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient和feign.Client.Default

都实现了 feign.Client 接口,但是 LoadBalancerFeignClient 实际上调用的还是 feign.Client.Default,无非做了自己处理(负载),有些类似于静态代理

那么上面的问题就只剩下能否替换的问题了

@Configurationclass DefaultFeignLoadBalancedConfiguration {@Bean@ConditionalOnMissingBeanpublic Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,SpringClientFactory clientFactory) {return new LoadBalancerFeignClient(new Client.Default(null, null),cachingFactory, clientFactory);}}

这就不需要我来过多解释了,我们只需要自定义一个 LoadBalancerFeignClient 或者 实现Client的类就行 然后注入就行

实现代码

我选择的是 自定义实现一个 Client,去继承 feign.Client.Default

@Slf4jpublic class InFeignClient extends Client.Default {    /**     */    public InFeignClient(SSLSocketFactory sslContextFactory, HostnameVerifier hostnameVerifier) {        super(sslContextFactory, hostnameVerifier);    }    @Override    public Response execute(Request request, Request.Options options) throws IOException {        try {            return super.execute(request, options);        } catch (IOException e) {            log.warn(" 请求 {} 异常 ======> {}", request.url(), e.getMessage());            throw e;        }    }}

然后将这个类替换

@Componentpublic class RestConfig {    public CachingSpringLoadBalancerFactory cachingLBClientFactory(            SpringClientFactory factory) {        return new CachingSpringLoadBalancerFactory(factory);    }    @Bean    public Client feignClient(SpringClientFactory clientFactory) {        CachingSpringLoadBalancerFactory bean = cachingLBClientFactory(clientFactory);        return new LoadBalancerFeignClient(new InFeignClient(null, null), bean, clientFactory);    }    }

不敢接受失败的人,往往是那些追求完美的人,

feign 如何获取请求真实目的ip地址

相关文章:

你感兴趣的文章:

标签云: