微服務通訊之feign整合負載均衡

泥粑發表於2020-10-12

前言

書接上文,feign介面是如何註冊到容器想必已然清楚,現在我們著重關心一個問題,feign呼叫服務的時候是如何抉擇的?上一篇主要是從讀原始碼的角度入手,後續將會逐步從軟體構架方面進行剖析。

一、ReflectiveFeign.FeignInvocationHandler

從上文知道feign介面呼叫實質上是呼叫的對應的動態代理介面的InvocationHandler,跟蹤原始碼發現預設的InvocationHandler實現就是FeignInvocationHandler。現在我們看一下這個FeignInvocationHandler.invoke(...)方法。

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      if ("equals".equals(method.getName())) {
        try {
          Object otherHandler =
              args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;
          return equals(otherHandler);
        } catch (IllegalArgumentException e) {
          return false;
        }
      } else if ("hashCode".equals(method.getName())) {
        return hashCode();
      } else if ("toString".equals(method.getName())) {
        return toString();
      }
      // dispath 是快取的method 以及 method對應的MethodHandler
      return dispatch.get(method).invoke(args);
    }

從程式碼中可以看到他是直接從快取中拿到對應的MethodHandler,然後呼叫的MethodHandler的invoke方法。我們看一下MethodHandler都有哪些實現:

可以看到就兩個實現, DefaultMethodHandler處理的是feign介面中的Default修飾的方法。我們呼叫的遠端介面用的是SynchronousMethodHandler實現。那麼可以看到我們最終對feing介面的某個方法的呼叫實際上呼叫的是SynchronousMethodHandler.invoke(...)方法。跟蹤程式碼發現,最終呼叫的是SynchronousMethodHandler持有的Client的例項的execute方法。那麼我們看一下Client都有那些實現:

這裡跟蹤SynchronousMethodHandler的建立過程發現Client的建立是按照如下邏輯進行的(FeignClientFactoryBean.loadBalance):


protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
			HardCodedTarget<T> target) {
		Client client = getOptional(context, Client.class);
		if (client != null) {
			builder.client(client);
			Targeter targeter = get(context, Targeter.class);
			return targeter.target(this, builder, context, target);
		}

		throw new IllegalStateException(
				"No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-netflix-ribbon?");
	}

從上述程式碼可以看到,他從context中獲取到client,然後通過client獲取執行。想必feign肯定是自動裝備了一個Client,我們看一下他的預設配置:

顯然配置必定是從FeignAutoConfiguration 或者 FeignRibbonClientAutoConfiguration進行配置的,檢視這兩個類最終發現Client是通過FeignRibbonClientAutoConfiguration進行注入的(通過@Import引入的DefaultFeignLoadBalancedConfiguration進行注入):


@Configuration
class DefaultFeignLoadBalancedConfiguration {

	@Bean
	@ConditionalOnMissingBean
	public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
			SpringClientFactory clientFactory) {
		return new LoadBalancerFeignClient(new Client.Default(null, null), cachingFactory,
				clientFactory);
	}

}

所以我們呼叫feign介面的某一個方法,最終呼叫的LoadBalancerFeignClient.execute()方法。那麼負載均衡相關邏輯應該是在此接入的。

二、LoadBalancerFeignClient 做了些什麼

先看核心程式碼,注意註釋部分:


public Response execute(Request request, Request.Options options) throws IOException {
		try {
                        // URL 處理  
			URI asUri = URI.create(request.url());
			String clientName = asUri.getHost();
			URI uriWithoutHost = cleanUrl(request.url(), clientName);
			FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(
					this.delegate, request, uriWithoutHost);
                        // 獲取呼叫服務配置
			IClientConfig requestConfig = getClientConfig(options, clientName);

                        // 建立負載均衡客戶端,執行請求
			return lbClient(clientName)
					.executeWithLoadBalancer(ribbonRequest, requestConfig).toResponse();
		}
		catch (ClientException e) {
			IOException io = findIOException(e);
			if (io != null) {
				throw io;
			}
			throw new RuntimeException(e);
		}
	}

從上面的程式碼可以看到,lbClient(clientName) 建立了一個負載均衡的客戶端,它實際上就是生成的如下所述的類:


public class FeignLoadBalancer extends
		AbstractLoadBalancerAwareClient<FeignLoadBalancer.RibbonRequest, FeignLoadBalancer.RibbonResponse> 

熟悉ribbon的朋友應該知道AbstractLoadBalancerAwareClient 就是Ribbon負載均衡呼叫的父類。具體的負載均衡實現策略,下一章在詳細描述。至此我們可以得出結論:feign整合負載均衡是通過將FeignLoadBalancer作為呼叫feign介面的實際執行者,從而達到負載均衡的效果。可以看到這裡與Ribbon高度的解耦,相當於我們獲取了服務名、呼叫地址、呼叫引數後,最終交由一個執行器去呼叫。執行器並不關心引數從何而來,這裡基於Ribbon提供的執行器實現只是更具傳遞的服務名找到了一個正確的例項去呼叫而已。

三 、 小結

至此我們可以看到初步職責劃分: 代理物件、請求與響應解析、執行器三個職能部門。

1) 代理物件職責是: 將feign介面中方法的呼叫轉接到對FeignInvocationHandler的invoke呼叫,在invoke函式中通過方法名稱找到對應的SynchronousMethodHandler。
2) 執行器: 負載根據請求與詳情解析出的呼叫資訊(呼叫服務名、呼叫地址、呼叫引數)發起呼叫,他不關心引數是如何來的

一個系統的好壞、可擴充套件性高低很大程式上取決於系統中各職能部門劃分是否清晰以及各個職能部分的許可權是否越界。

相關文章