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.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
![](/images/zang.png)
![](/images/jiucuo.png)
猜你喜欢
带大家认识Java语法之泛型与通配符
![](https://img.aspxhome.com/file/2023/4/67494_0s.png)
Java单例模式简单示例
![](https://img.aspxhome.com/file/2023/3/61843_0s.png)
java并发编程_线程池的使用方法(详解)
![](https://img.aspxhome.com/file/2023/1/69711_0s.png)
详解java基于MyBatis使用示例
![](https://img.aspxhome.com/file/2023/0/59060_0s.png)
Java修饰符 abstract,static,final 的区别详解
Java文件操作之按行读取文件和遍历目录的方法
微服务分布式架构实现日志链路跟踪的方法
![](https://img.aspxhome.com/file/2023/3/60123_0s.jpg)
深入理解C#中的枚举
Java 中的 clone( ) 和 new哪个效率更高
![](https://img.aspxhome.com/file/2023/6/59326_0s.png)
Java实现级联下拉结构的示例代码
springboot自定义starter方法及注解实例
![](https://img.aspxhome.com/file/2023/7/71607_0s.jpg)
为什么不要使用 async void的原因分析
![](https://img.aspxhome.com/file/2023/5/60245_0s.jpg)
C#中在WebClient中使用post发送数据实现方法
详解Struts2动态方法调用
Android引导页面的简单实现
![](https://img.aspxhome.com/file/2023/3/86013_0s.gif)
SpringBoot 自定义starter yaml提示失效问题及解决方法
![](https://img.aspxhome.com/file/2023/5/70945_0s.jpg)
java生成验证码图片的方法
Eclipse下编写java程序突然不会自动生成R.java文件和包的解决办法
在Flutter中制作翻转卡片动画的完整实例代码
elasticsearch分布式及数据的功能源码分析
![](https://img.aspxhome.com/file/2023/7/58087_0s.png)