Feign原理

1. Feign远程调用的基本流程

Feign远程调用,核心就是通过一系列的封装和处理,将以JAVA注解的方式定义的远程调用API接口,最终转换成HTTP的请求形式,然后将HTTP的请求的响应结果,解码成JAVA Bean,放回给调用者。Feign远程调用的基本流程,大致如下图所示。

image.png
从上图可以看到,Feign通过处理注解,将请求模板化,当实际调用的时候,传入参数,根据参数再应用到请求上,进而转化成真正的 Request 请求。通过Feign以及JAVA的动态代理机制,使得Java 开发人员,可以不用通过HTTP框架去封装HTTP请求报文的方式,完成远程服务的HTTP调用。

2. Feign动态代理

SpringCloud - 图2
Feign 的默认实现是 ReflectiveFeign,通过 Feign.Builder 构建。再看代码前,先了解一下 Target 这个对象。

  1. public interface Target<T> {
  2. // 接口的类型
  3. Class<T> type();
  4. // 代理对象的名称,默认为url,负载均衡时有用
  5. String name();
  6. // 请求的url地址,eg: https://api/v2
  7. String url();
  8. }

其中 Target.type 是用来生成代理对象的,url 是 Client 对象发送请求的地址。

2.1 ReflectiveFeign构建

  1. public Feign build() {
  2. // client 有三种实现 JdkHttp/ApacheHttp/okHttp,默认是 jdk 的实现
  3. SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =
  4. new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,
  5. logLevel, decode404, closeAfterDecode, propagationPolicy);
  6. ParseHandlersByName handlersByName =
  7. new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder,
  8. errorDecoder, synchronousMethodHandlerFactory);
  9. return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
  10. }

总结: 介绍一下几个主要的参数

  • Client:有三种实现 JdkHttp/ApacheHttp/okHttp
  • RequestInterceptor:请求拦截器
  • Contract REST:注解解析器默认为 Contract.Default(),即支持 Feign 的原生注解。
  • InvocationHandlerFactory: 生成 JDK 动态代理,实际执行是委托给了 MethodHandler。

    2.2 生成代理对象

    1. public <T> T newInstance(Target<T> target) {
    2. // 1. Contract 将 target.type 接口类上的方法和注解解析成 MethodMetadata,
    3. // 并转换成内部的MethodHandler处理方式
    4. Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
    5. Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
    6. List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();
    7. for (Method method : target.type().getMethods()) {
    8. if (method.getDeclaringClass() == Object.class) {
    9. continue;
    10. } else if (Util.isDefault(method)) {
    11. DefaultMethodHandler handler = new DefaultMethodHandler(method);
    12. defaultMethodHandlers.add(handler);
    13. methodToHandler.put(method, handler);
    14. } else {
    15. methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
    16. }
    17. }
    18. // 2. 生成 target.type 的 jdk 动态代理对象
    19. InvocationHandler handler = factory.create(target, methodToHandler);
    20. T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
    21. new Class<?>[]{target.type()}, handler);
    22. for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
    23. defaultMethodHandler.bindTo(proxy);
    24. }
    25. return proxy;
    26. }

    总结: newInstance 生成了 JDK 的动态代理,从 factory.create(target, methodToHandler) 也可以看出 InvocationHandler 实际委托给了 methodToHandler。methodToHandler 默认是 SynchronousMethodHandler.Factory 工厂类创建的。

    2.3 MethodHandler方法执行器

    ParseHandlersByName.apply 生成了每个方法的执行器 MethodHandler,其中最重要的一步就是通过 Contract 解析 MethodMetadata。

    1. public Map<String, MethodHandler> apply(Target key) {
    2. // 1. contract 将接口类中的方法和注解解析 MethodMetadata
    3. List<MethodMetadata> metadata = contract.parseAndValidatateMetadata(key.type());
    4. Map<String, MethodHandler> result = new LinkedHashMap<String, MethodHandler>();
    5. for (MethodMetadata md : metadata) {
    6. // 2. buildTemplate 实际上将 Method 方法的参数转换成 Request
    7. BuildTemplateByResolvingArgs buildTemplate;
    8. if (!md.formParams().isEmpty() && md.template().bodyTemplate() == null) {
    9. // 2.1 表单
    10. buildTemplate = new BuildFormEncodedTemplateFromArgs(md, encoder, queryMapEncoder);
    11. } else if (md.bodyIndex() != null) {
    12. // 2.2 @Body 注解
    13. buildTemplate = new BuildEncodedTemplateFromArgs(md, encoder, queryMapEncoder);
    14. } else {
    15. // 2.3 其余
    16. buildTemplate = new BuildTemplateByResolvingArgs(md, queryMapEncoder);
    17. }
    18. // 3. 将 metadata 和 buildTemplate 封装成 MethodHandler
    19. result.put(md.configKey(),
    20. factory.create(key, md, buildTemplate, options, decoder, errorDecoder));
    21. }
    22. return result;
    23. }

    总结: 这个方法由以下几步:

  1. Contract 统一将方法解析 MethodMetadata(*),这样就可以通过实现不同的 Contract 适配各种 REST 声明式规范。
  2. buildTemplate 实际上将 Method 方法的参数转换成 Request。
  3. 将 metadata 和 buildTemplate 封装成 MethodHandler。

这样通过以上三步就创建了一个 Target.type 的代理对象 proxy,这个代理对象就可以像访问普通方法一样发送 Http 请求,其实和 RPC 的 Stub 模型是一样的。了解 proxy 后,其执行过程其实也就一模了然。

3. Feign调用过程

SpringCloud - 图3

3.1 FeignInvocationhandler

  1. private final Map<Method, MethodHandler> dispatch;
  2. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  3. ...
  4. // 每个Method方法对应一个MethodHandler
  5. return dispatch.get(method).invoke(args);
  6. }

总结: 和上面的结论一样,实际的执行逻辑实际上是委托给了 MethodHandler。

3.2 SynchronousMethodHandler

  1. // 发起 http 请求,并根据 retryer 进行重试
  2. public Object invoke(Object[] argv) throws Throwable {
  3. // template 将 argv 参数构建成 Request
  4. RequestTemplate template = buildTemplateFromArgs.create(argv);
  5. Options options = findOptions(argv);
  6. Retryer retryer = this.retryer.clone();
  7. // 调用client.execute(request, options)
  8. while (true) {
  9. try {
  10. return executeAndDecode(template, options);
  11. } catch (RetryableException e) {
  12. try {
  13. // 重试机制
  14. retryer.continueOrPropagate(e);
  15. } catch (RetryableException th) {
  16. ...
  17. }
  18. continue;
  19. }
  20. }
  21. }

总结: invoke 主要进行请求失败的重试机制,至于具体执行过程委托给了 executeAndDecode 方法。

  1. // 一是编码生成Request;二是http请求;三是解码生成Response
  2. Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
  3. // 1. 调用拦截器 RequestInterceptor,并根据 template 生成 Request
  4. Request request = targetRequest(template);
  5. // 2. http 请求
  6. Response response = client.execute(request, options);
  7. // 3. response 解码
  8. if (Response.class == metadata.returnType()) {
  9. byte[] bodyData = Util.toByteArray(response.body().asInputStream());
  10. return response.toBuilder().body(bodyData).build();
  11. }
  12. ...
  13. }
  14. Request targetRequest(RequestTemplate template) {
  15. // 执行拦截器
  16. for (RequestInterceptor interceptor : requestInterceptors) {
  17. interceptor.apply(template);
  18. }
  19. // 生成 Request
  20. return target.apply(template);
  21. }

总结: executeAndDecode 最终调用 client.execute(request, options) 进行 http 请求。

4. 如何基于Feign实现负载均衡与熔断

4.1 基于Feign的负载均衡 - 整合Ribbon

  1. 想要进行负载均衡,那就要对 Client 进行包装,实现负载均衡。 相关代码见RibbonClientLBClient
  1. // RibbonClient 主要逻辑
  2. private final Client delegate;
  3. private final LBClientFactory lbClientFactory;
  4. public Response execute(Request request, Request.Options options) throws IOException {
  5. try {
  6. URI asUri = URI.create(request.url());
  7. String clientName = asUri.getHost();
  8. URI uriWithoutHost = cleanUrl(request.url(), clientName);
  9. // 封装 RibbonRequest,包含 Client、Request、uri
  10. LBClient.RibbonRequest ribbonRequest =
  11. new LBClient.RibbonRequest(delegate, request, uriWithoutHost);
  12. // executeWithLoadBalancer 实现负载均衡
  13. return lbClient(clientName).executeWithLoadBalancer(
  14. ribbonRequest,
  15. new FeignOptionsClientConfig(options)).toResponse();
  16. } catch (ClientException e) {
  17. propagateFirstIOException(e);
  18. throw new RuntimeException(e);
  19. }
  20. }

总结: 实际上是把 Client 对象包装了一下,通过 executeWithLoadBalancer 进行负载均衡,这是 Ribbon 提供了 API。更多关于 Ribbon 的负载均衡就不在这进一步说明了。

  1. public final class LBClient extends AbstractLoadBalancerAwareClient
  2. <LBClient.RibbonRequest, LBClient.RibbonResponse> {
  3. // executeWithLoadBalancer 方法通过负载均衡算法后,最终调用 execute
  4. @Override
  5. public RibbonResponse execute(RibbonRequest request, IClientConfig configOverride)
  6. throws IOException, ClientException {
  7. Request.Options options;
  8. if (configOverride != null) {
  9. options = new Request.Options(
  10. configOverride.get(CommonClientConfigKey.ConnectTimeout, connectTimeout),
  11. configOverride.get(CommonClientConfigKey.ReadTimeout, readTimeout),
  12. configOverride.get(CommonClientConfigKey.FollowRedirects, followRedirects));
  13. } else {
  14. options = new Request.Options(connectTimeout, readTimeout);
  15. }
  16. // http 请求
  17. Response response = request.client().execute(request.toRequest(), options);
  18. if (retryableStatusCodes.contains(response.status())) {
  19. response.close();
  20. throw new ClientException(ClientException.ErrorType.SERVER_THROTTLED);
  21. }
  22. return new RibbonResponse(request.getUri(), response);
  23. }
  24. }

4.2 基于Feign的熔断与限流 - 整合Hystrix

想要进行限流,那就要在方法执行前进行拦截,也就是重写 InvocationHandlerFactory,在方法执行前进行熔断与限流。相关代码见 HystrixFeign,其实也就是实现了 HystrixInvocationHandler。

  1. // HystrixInvocationHandler 主要逻辑
  2. public Object invoke(final Object proxy, final Method method, final Object[] args)
  3. throws Throwable {
  4. HystrixCommand<Object> hystrixCommand =
  5. new HystrixCommand<Object>(setterMethodMap.get(method)) {
  6. @Override
  7. protected Object run() throws Exception {
  8. return HystrixInvocationHandler.this.dispatch.get(method).invoke(args);
  9. }
  10. @Override
  11. protected Object getFallback() {
  12. };
  13. }
  14. ...
  15. return hystrixCommand.execute();
  16. }