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

作者:帆影匆匆 时间:2021-08-13 15:47:49 

需求

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

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

大致想法

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

  • feign 集成了ribbon 如何在负载均衡之后获取真实的ip地址

  • feign 实际请求 http 源码在哪

  • 能否替换 feign http 请求的组件

源码解析

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

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

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

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


FeignInvocationHandler
HystrixInvocationHandler

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

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

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

此时跟踪到


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 是


LoadBalancerFeignClient
org.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
// 获取 FeignLoadBalancer
lbClient(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,无非做了自己处理(负载),有些类似于静态代理

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


@Configuration
class DefaultFeignLoadBalancedConfiguration {
@Bean
@ConditionalOnMissingBean
public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
SpringClientFactory clientFactory) {
return new LoadBalancerFeignClient(new Client.Default(null, null),
cachingFactory, clientFactory);
}
}

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

实现代码

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


@Slf4j
public 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;
       }
   }
}

然后将这个类替换


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

来源:https://blog.csdn.net/weixin_39660224/article/details/115397061

标签:feign,ip,请求
0
投稿

猜你喜欢

  • 带大家认识Java语法之泛型与通配符

    2021-06-04 06:14:46
  • Java单例模式简单示例

    2022-04-24 03:46:20
  • java并发编程_线程池的使用方法(详解)

    2023-03-29 22:23:00
  • 详解java基于MyBatis使用示例

    2023-11-25 09:01:45
  • Java修饰符 abstract,static,final 的区别详解

    2023-12-19 22:11:25
  • Java文件操作之按行读取文件和遍历目录的方法

    2023-11-24 16:58:21
  • 微服务分布式架构实现日志链路跟踪的方法

    2023-11-29 15:43:16
  • 深入理解C#中的枚举

    2022-06-03 02:58:34
  • Java 中的 clone( ) 和 new哪个效率更高

    2023-11-09 05:27:44
  • Java实现级联下拉结构的示例代码

    2023-11-03 18:22:06
  • springboot自定义starter方法及注解实例

    2022-11-02 10:52:08
  • 为什么不要使用 async void的原因分析

    2023-11-24 21:10:27
  • C#中在WebClient中使用post发送数据实现方法

    2023-05-01 00:03:54
  • 详解Struts2动态方法调用

    2022-10-18 11:19:25
  • Android引导页面的简单实现

    2023-08-06 04:24:32
  • SpringBoot 自定义starter yaml提示失效问题及解决方法

    2022-08-03 14:58:42
  • java生成验证码图片的方法

    2023-12-09 08:57:15
  • Eclipse下编写java程序突然不会自动生成R.java文件和包的解决办法

    2023-11-19 00:24:05
  • 在Flutter中制作翻转卡片动画的完整实例代码

    2023-06-23 23:31:21
  • elasticsearch分布式及数据的功能源码分析

    2023-08-11 06:31:26
  • asp之家 软件编程 m.aspxhome.com