前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Feign源码解析4:调用过程

Feign源码解析4:调用过程

作者头像
低级知识传播者
发布2024-01-17 14:27:16
1790
发布2024-01-17 14:27:16
举报

背景

Feign源码解析:初始化过程(一)

Feign源码解析:初始化过程(二)

Feign源码解析:初始化过程(三)

前面几篇分析了Feign的初始化过程,历经艰难,可算是把@FeignClient注解的接口对应的代理对象给创建出来了。今天看下在实际Feign调用过程中的一些源码细节。

我们这里Feign接口如下:

代码语言:javascript
复制
@FeignClient(value = "echo-service-provider") // 指向服务提供者应用
public interface EchoService {

    @GetMapping("/echo/{message}")
    String echo(@PathVariable("message") String message);
}

调用代码:

http://localhost:8080/feign/echo/ddd

代码语言:javascript
复制
@GetMapping("/feign/echo/{message}")
public String feignEcho(@PathVariable String message) {
    return echoService.echo(message);
}

完整解析

动态代理

这里的echoService此时的类型其实是jdk动态代理,内部有一个字段,就是实现了jdk的InvocationHandler接口:

实际逻辑如下,会根据被调用的method找到一个合适的handler:

代码语言:javascript
复制
import feign.InvocationHandlerFactory.MethodHandler

static class FeignInvocationHandler implements InvocationHandler{
 private final Map<Method, MethodHandler> dispatch;
}    

一般获取到的都是如下类型:

这个类有如下核心属性:

都是和http请求息息相关的,如重试、请求拦截器、响应拦截器、logger、logger级别、options(包含了超时时间等参数)。

重试

接下来看实际请求的大体框架:

上面主要是一个while循环,内部会执行请求,如果请求报错,抛出了RetryableException类型的异常,此时就会由重试组件(Retryer retryer),判断是否要重试,如果要的话,就会continue,就会再次执行请求。

但如果重试组件认为不需要重试或重试次数已经超过,就会抛出异常,此时就走不到continue部分了,会直接向上层抛异常。

image-20240111202202423

注意,这个重试接口实现了Cloneable,因为每次请求的时候,都要有一个对应的重试对象来记录当前请求的重试状态(比如重试了几次了),正因为有状态,所以得每次clone一个新的。

代码语言:javascript
复制
Retryer retryer = this.retryer.clone();

默认情况下,不做任何配置,都是不重试的,此时的retryer类型为一个内部类,意思是NEVER_RETRY:

这里再拓展一点,什么情况下,Feign会抛出RetryableException呢?

其实就是在执行上图的正常执行部分,遇到了java.io.IOException的时候,就会抛这种RetryableException。

代码语言:javascript
复制
  static FeignException errorExecuting(Request request, IOException cause) {
    return new RetryableException(
        -1,
        format("%s executing %s %s", cause.getMessage(), request.httpMethod(), request.url()),
        request.httpMethod(),
        cause,
        null, request);
  }

还有一种情况是啥呢?是服务端返回的http header中包含了Retry-After这个header的时候:

这个header一般是在503的时候返回:

根据模版创建请求

大家看看我们的接口,用了path variable,所以,在真正进行请求前,必须先完成一些准备工作,比如把path variable替换为真实的值:

代码语言:javascript
复制
@GetMapping("/echo/{message}")
String echo(@PathVariable("message") String message);

另外,大家看代码,还有个参数传给下层:

这个就是控制请求超时参数的。这个options从哪里来呢,从我们的传参来。我们可以在方法中加一个参数:

代码语言:javascript
复制
@GetMapping("/echo/{message}")
String echo(@PathVariable("message") String message, Request.Options options);

这个的优先级,我觉得应该是最高的。

这样就能支持,每个接口用不一样的超时时间。

executeAndDecode概览

生成绝对路径请求

先说说1处:

代码语言:javascript
复制
Request targetRequest(RequestTemplate template) {
    // 使用请求拦截器
    for (RequestInterceptor interceptor : requestInterceptors) {
        interceptor.apply(template);
    }
    return target.apply(template);
}

请求拦截器的类型:

代码语言:javascript
复制
public interface RequestInterceptor {

  /**
   * Called for every request. Add data using methods on the supplied {@link RequestTemplate}.
   */
  void apply(RequestTemplate template);
}

这里是对模版进行修改,我看注释,有如下场景(增加全局的header):

接下来,再看看模版如何转化为请求:

代码语言:javascript
复制
return target.apply(template);

结果,其实也没干啥,就是模版里只有接口的相对路径,此处要拼接为完整路径:

client.execute接口

实际执行请求,靠client对象,它的默认类型为:

它是自动装配的:

它内部包裹了实际发http请求的库,上面的代码中,默认用的是feign自带的(位于feign-core依赖):

代码语言:javascript
复制
new Client.Default(null, null)

比如,假设我们想用httpclient (在classpath中包含),就会触发自动装配:

也支持okhttp(feign.okhttp.enabled为true):

这里看看FeignBlockingLoadBalancerClient的几个构造参数:

代码语言:javascript
复制
public FeignBlockingLoadBalancerClient(Client delegate, LoadBalancerClient loadBalancerClient, LoadBalancerClientFactory loadBalancerClientFactory) {
    this.delegate = delegate;
    this.loadBalancerClient = loadBalancerClient;
    this.loadBalancerClientFactory = loadBalancerClientFactory;
}

第一个是上面提到的http客户端,第二个、第三个呢,其实是负责负载均衡的客户端(不只是要负责客户端负载均衡算法,还要负责从nacos这些地方获取服务对应的实例)

client获取服务实例

这个execute方法实际有两部分,一部分是如下,根据service名称,获取服务实例(包含了真实的ip、端口),再一部分才是对服务实例发起请求。

下面的1处,是获取一些listener,然后通知listener,我们已经开始请求了,这里的listener的类型是:

代码语言:javascript
复制
org.springframework.cloud.client.loadbalancer.LoadBalancerLifecycle

包含了好些个生命周期方法,如onStart方法:

代码语言:javascript
复制
void onStart(Request<RC> request);

然后是2处,利用loadBalancerClient,根据服务名,如我们这里的echo-service-provider,获取到一个实例(类型为org.springframework.cloud.client.ServiceInstance)。

3处会判断,如果没获取到实例,此时就会报503了,服务实例不存在。

这里面,获取实例的部分,比较复杂,我们得单独开一篇来讲。

发起真实请求

根据服务实例,组装真实的url进行请求。

这个等负载均衡部分写完了,再讲解这部分,这块的逻辑也还好,无非是对httpclient这些的封装。

总结

我们把整体的feign调用的脉络梳理了一遍,下篇继续loadbalancer的部分,那里也是有点坑的。

用讲师的话来结个尾:啥也不是,散会!

本文参与?腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2024-01-11,如有侵权请联系?cloudcommunity@tencent.com 删除

本文分享自 低级知识传播者 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与?腾讯云自媒体同步曝光计划? ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 背景
  • 完整解析
    • 动态代理
      • 重试
        • 根据模版创建请求
          • executeAndDecode概览
            • 生成绝对路径请求
            • client.execute接口
          • client获取服务实例
            • 发起真实请求
            • 总结
            相关产品与服务
            负载均衡
            负载均衡(Cloud Load Balancer,CLB)提供安全快捷的流量分发服务,访问流量经由 CLB 可以自动分配到云中的多台后端服务器上,扩展系统的服务能力并消除单点故障。负载均衡支持亿级连接和千万级并发,可轻松应对大流量访问,满足业务需求。
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
            http://www.vxiaotou.com