Feign 系列(03)Feign 工作原理


Feign 系列(03)Feign 工作原理

Spring Cloud 系列目錄(https://www.cnblogs.com/binarylei/p/11563952.html#feign)

1. Feign 是如何設計的

首先回顧一下 Feign 的基本用法:

// 1. Feign 動態代理
GitHub github = Feign.builder()
    .decoder(new GsonDecoder())
    .target(GitHub.class, "https://api.github.com");
// 2. Feign 執行
List<Contributor> contributors = github.contributors("OpenFeign", "feign");

總結: Feign 使用時分成兩步:一是生成 Feign 的動態代理;二是 Feign 執行。

圖1:Feign 的整體設計

總結:

  1. 前兩步是生成動態對象:將 Method 方法的注解解析成 MethodMetadata,並最終生成 Feign 動態代理對象。
  2. 后幾步是調用過程:根據解析的 MethodMetadata 對象,將 Method 方法的參數轉換成 Request,最后調用 Client 發送請求。

2. Feign 動態代理

Feign 的默認實現是 ReflectiveFeign,通過 Feign.Builder 構建。再看代碼前,先了解一下 Target 這個對象。

public interface Target<T> {
  // 接口的類型
  Class<T> type();

  // 代理對象的名稱,默認為url,負載均衡時有用
  String name();
  // 請求的url地址,eg: https://api/v2
  String url();
}

其中 Target.type 是用來生成代理對象的,url 是 Client 對象發送請求的地址。

2.1 ReflectiveFeign 構建

public Feign build() {
    // client 有三種實現 JdkHttp/ApacheHttp/okHttp,默認是 jdk 的實現
    SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =
        new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,
                                             logLevel, decode404, closeAfterDecode, propagationPolicy);
    ParseHandlersByName handlersByName =
        new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder,
                                errorDecoder, synchronousMethodHandlerFactory);
    return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
}

總結: 介紹一下幾個主要的參數:

  • Client 這個沒什么可說的,有三種實現 JdkHttp/ApacheHttp/okHttp
  • RequestInterceptor 請求攔截器
  • Contract REST 注解解析器,默認為 Contract.Default(),即支持 Feign 的原生注解。
  • InvocationHandlerFactory 生成 JDK 動態代理,實際執行是委托給了 MethodHandler。

2.2 生成代理對象

public <T> T newInstance(Target<T> target) {
    // 1. Contract 將 target.type 接口類上的方法和注解解析成 MethodMetadata,
    //    並轉換成內部的MethodHandler處理方式
    Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
    Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
    List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();

    for (Method method : target.type().getMethods()) {
        if (method.getDeclaringClass() == Object.class) {
            continue;
        } else if (Util.isDefault(method)) {
            DefaultMethodHandler handler = new DefaultMethodHandler(method);
            defaultMethodHandlers.add(handler);
            methodToHandler.put(method, handler);
        } else {
            methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
        }
    }

    // 2. 生成 target.type 的 jdk 動態代理對象
    InvocationHandler handler = factory.create(target, methodToHandler);
    T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
                                         new Class<?>[]{target.type()}, handler);

    for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
        defaultMethodHandler.bindTo(proxy);
    }
    return proxy;
}

總結: newInstance 生成了 JDK 的動態代理,從 factory.create(target, methodToHandler) 也可以看出 InvocationHandler 實際委托給了 methodToHandler。methodToHandler 默認是 SynchronousMethodHandler.Factory 工廠類創建的。

2.3 MethodHandler 方法執行器

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

public Map<String, MethodHandler> apply(Target key) {
    // 1. contract 將接口類中的方法和注解解析 MethodMetadata
    List<MethodMetadata> metadata = contract.parseAndValidatateMetadata(key.type());
    Map<String, MethodHandler> result = new LinkedHashMap<String, MethodHandler>();
    for (MethodMetadata md : metadata) {
        // 2. buildTemplate 實際上將 Method 方法的參數轉換成 Request
        BuildTemplateByResolvingArgs buildTemplate;
        if (!md.formParams().isEmpty() && md.template().bodyTemplate() == null) {
            // 2.1 表單
            buildTemplate = new BuildFormEncodedTemplateFromArgs(md, encoder, queryMapEncoder);
        } else if (md.bodyIndex() != null) {
            // 2.2 @Body 注解
            buildTemplate = new BuildEncodedTemplateFromArgs(md, encoder, queryMapEncoder);
        } else {
            // 2.3 其余
            buildTemplate = new BuildTemplateByResolvingArgs(md, queryMapEncoder);
        }
        // 3. 將 metadata 和 buildTemplate 封裝成 MethodHandler
        result.put(md.configKey(),
                   factory.create(key, md, buildTemplate, options, decoder, errorDecoder));
    }
    return result;
}

總結: 這個方法由以下幾步:

  1. Contract 統一將方法解析 MethodMetadata(*),這樣就可以通過實現不同的 Contract 適配各種 REST 聲明式規范。
  2. buildTemplate 實際上將 Method 方法的參數轉換成 Request。
  3. 將 metadata 和 buildTemplate 封裝成 MethodHandler。

這樣通過以上三步就創建了一個 Target.type 的代理對象 proxy,這個代理對象就可以像訪問普通方法一樣發送 Http 請求,其實和 RPC 的 Stub 模型是一樣的。了解 proxy 后,其執行過程其實也就一模了然。

3. Feign 調用過程

3.1 FeignInvocationHandler#invoke

private final Map<Method, MethodHandler> dispatch;
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    ...
    // 每個Method方法對應一個MethodHandler
    return dispatch.get(method).invoke(args);
}

總結: 和上面的結論一樣,實際的執行邏輯實際上是委托給了 MethodHandler。

3.2 SynchronousMethodHandler#invoke

// 發起 http 請求,並根據 retryer 進行重試
public Object invoke(Object[] argv) throws Throwable {
    // template 將 argv 參數構建成 Request
    RequestTemplate template = buildTemplateFromArgs.create(argv);
    Options options = findOptions(argv);
    Retryer retryer = this.retryer.clone();

    // 調用client.execute(request, options)
    while (true) {
        try {
            return executeAndDecode(template, options);
        } catch (RetryableException e) {
            try {
                // 重試機制
                retryer.continueOrPropagate(e);
            } catch (RetryableException th) {
                ...
            }
            continue;
        }
    }
}

總結: invoke 主要進行請求失敗的重試機制,至於具體執行過程委托給了 executeAndDecode 方法。

// 一是編碼生成Request;二是http請求;三是解碼生成Response
Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
    // 1. 調用攔截器 RequestInterceptor,並根據 template 生成 Request
    Request request = targetRequest(template);
    // 2. http 請求
    Response response = client.execute(request, options);
	// 3. response 解碼
    if (Response.class == metadata.returnType()) {
        byte[] bodyData = Util.toByteArray(response.body().asInputStream());
        return response.toBuilder().body(bodyData).build();
    }
    ...
}

Request targetRequest(RequestTemplate template) {
    // 執行攔截器
    for (RequestInterceptor interceptor : requestInterceptors) {
        interceptor.apply(template);
    }
    // 生成 Request
    return target.apply(template);
}

總結: executeAndDecode 最終調用 client.execute(request, options) 進行 http 請求。

4. 思考:如何基於 Feign 實現負載均衡與熔斷

4.1 基於 Feign 的負載均衡 - 整合 Ribbon

想要進行負載均衡,那就要對 Client 進行包裝,實現負載均衡。 相關代碼見RibbonClientLBClient

// RibbonClient 主要邏輯
private final Client delegate;
private final LBClientFactory lbClientFactory;
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);
        // 封裝 RibbonRequest,包含 Client、Request、uri
        LBClient.RibbonRequest ribbonRequest =
            new LBClient.RibbonRequest(delegate, request, uriWithoutHost);
        // executeWithLoadBalancer 實現負載均衡
        return lbClient(clientName).executeWithLoadBalancer(
            ribbonRequest,
            new FeignOptionsClientConfig(options)).toResponse();
    } catch (ClientException e) {
        propagateFirstIOException(e);
        throw new RuntimeException(e);
    }
}

總結: 實際上是把 Client 對象包裝了一下,通過 executeWithLoadBalancer 進行負載均衡,這是 Ribbon 提供了 API。更多關於 Ribbon 的負載均衡就不在這進一步說明了。

public final class LBClient extends AbstractLoadBalancerAwareClient
	<LBClient.RibbonRequest, LBClient.RibbonResponse> {
	
	// executeWithLoadBalancer 方法通過負載均衡算法后,最終調用 execute
	@Override
    public RibbonResponse execute(RibbonRequest request, IClientConfig configOverride)
            throws IOException, ClientException {
        Request.Options options;
        if (configOverride != null) {
            options = new Request.Options(
                    configOverride.get(CommonClientConfigKey.ConnectTimeout, connectTimeout),
                    configOverride.get(CommonClientConfigKey.ReadTimeout, readTimeout),
                    configOverride.get(CommonClientConfigKey.FollowRedirects, followRedirects));
        } else {
            options = new Request.Options(connectTimeout, readTimeout);
        }
        // http 請求
        Response response = request.client().execute(request.toRequest(), options);
        if (retryableStatusCodes.contains(response.status())) {
            response.close();
            throw new ClientException(ClientException.ErrorType.SERVER_THROTTLED);
        }
        return new RibbonResponse(request.getUri(), response);
    }
}

4.2 基於 Feign 的熔斷與限流 - 整合 Hystrix

想要進行限流,那就要在方法執行前進行攔截,也就是重寫 InvocationHandlerFactory,在方法執行前進行熔斷與限流。相關代碼見 HystrixFeign,其實也就是實現了 HystrixInvocationHandler。

// HystrixInvocationHandler 主要邏輯
public Object invoke(final Object proxy, final Method method, final Object[] args)
      throws Throwable {
    HystrixCommand<Object> hystrixCommand =
        new HystrixCommand<Object>(setterMethodMap.get(method)) {
            @Override
            protected Object run() throws Exception {
                return HystrixInvocationHandler.this.dispatch.get(method).invoke(args);
            }
            @Override
            protected Object getFallback() {
            };
        }
    ...
	return hystrixCommand.execute();
}

參考:

  1. Spring Cloud Feign設計原理:https://www.jianshu.com/p/8c7b92b4396c


    每天用心記錄一點點。內容也許不重要,但習慣很重要!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM