多平台统一管理软件接口,如何实现多平台统一管理软件接口
509
2022-10-17
feign 如何获取请求真实目的ip地址
需求
最近的项目中出现了很多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
try {
// 在com.netflix.loadbalancer.reactive.LoadBalancerCommand#submit 中会根据 负载均衡算法之后获取到真实的ip地址
return command.submit(
new ServerOperation
hBLwA @Override
// 传入的server 就是真实的ip
public Observable
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
rhBLwAeturn 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,无非做了自己处理(负载),有些类似hBLwA于静态代理
那么上面的问题就只剩下能否替换的问题了
@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);
}
}
版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。
发表评论
暂时没有评论,来抢沙发吧~