Ribbon負載均衡原理,Feign是如何整合Ribbon的?

 

 

1. 什麼是負載均衡?

       負載均衡是從多個服務中根據某個策略選擇一個進行訪問,常見的負載均衡分爲兩種java

  1. 客戶端負載均衡:即在客戶端就進行負載均衡算法分配。例如spring cloud中的ribbon,客戶端會有一個服務器地址列表,在發送請求前經過負載均衡算法選擇 一個服務器,而後進行訪問
  2. 服務端負載均衡:在消費者和服務提供方中間使用獨立的代理方式進行負載。例如Nginx,先發送請求,而後經過Nginx的負載均衡算法,在多個服務器之間選擇一 個進行訪問!

常見的負載均衡算法:算法

  • 隨機:經過隨機選擇服務進行執行,通常這種方式使用較少;
  • 輪詢:請求來以後排隊處理,輪着來
  • 加權輪詢:經過對服務器性能的分型,給高配置,低負載的服務器分配更高的權重,均衡各個 服務器的壓力;
  • 一致性hash:經過客戶端請求的地址的HASH值取模映射進行服務器調度。
  • 最少併發:將請求分配到當前壓力最小的服務器上

       

2. Ribbon的使用

Ribbon屬於netflix的產品,依賴以下spring

<!--添加ribbon的依賴-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>

       但 spring-cloud 體系下的大多數產品都整合和ribbon,如服務發現nacos-discovery,RPC調用feign組件等等,因此,使用時能夠不用再引入ribbon依賴後端

使用Ribbon時只需添加@LoadBalanced註解便可,表明當前請求擁有了負載均衡的能力數組

①:爲RestTemplate添加@LoadBalanced註解springboot

@Configuration
public class RestConfig {
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

②:使用RestTemplate進行遠程調用,這次調用有負載均衡效果!服務器

@Autowired
private RestTemplate restTemplate;

@RequestMapping(value = "/findOrderByUserId/{id}")
public R  findOrderByUserId(@PathVariable("id") Integer id) {

    String url = "http://order/findOrderByUserId/"+id;
    R result = restTemplate.getForObject(url,R.class);

    return result;
}

       

①:自定義負載均衡策略

自定義負載均衡策略方式有多種網絡

  • 實現 IRule 接口
  • 或者繼承AbstractLoadBalancerRule

實現基於Nacos權重的負載均衡策略:nacos中權重越大的實例請求頻次越高!併發

//繼承 AbstractLoadBalancerRule 類
@Slf4j
public class NacosRandomWithWeightRule extends AbstractLoadBalancerRule {

    @Autowired
    private NacosDiscoveryProperties nacosDiscoveryProperties;

    @Override
    public Server choose(Object key) {
    	//獲取負載均衡器
        DynamicServerListLoadBalancer loadBalancer = (DynamicServerListLoadBalancer) getLoadBalancer();
        String serviceName = loadBalancer.getName();
        NamingService namingService = nacosDiscoveryProperties.namingServiceInstance();
        try {
            //nacos基於權重的算法
            Instance instance = namingService.selectOneHealthyInstance(serviceName);
            return new NacosServer(instance);
        } catch (NacosException e) {
            log.error("獲取服務實例異常:{}", e.getMessage());
            e.printStackTrace();
        }
        return null;
    }
    @Override
    public void initWithNiwsConfig(IClientConfig clientConfig) {

    }

自定義負載均衡策略的配置也有兩種mvc

  • 全局配置:當前服務調用其餘微服務時,一概使用指定的負載均衡算法

    @Configuration
    public class RibbonConfig {
    
        /**
         * 全局配置
         * 指定負載均衡策略
         * @return
         */
        @Bean
        public IRule ribbonRule() {
            // 指定使用基於`Nacos`權重的負載均衡策略:`nacos`中權重越大的實例請求頻次越高!
            return new NacosRandomWithWeightRule();
        }
  • 局部配置:當前服務調用指定的微服務時,使用對應的負載均衡算法,好比調用order服務使用該算法,調用其餘的不使用!

    # 被調用的微服務名
    mall-order:
      ribbon:
        # 自定義的負載均衡策略(基於隨機&權重)
        NFLoadBalancerRuleClassName: com.china.test.ribbondemo.rule.NacosRandomWithWeightRule

       

②:Ribbon的飢餓加載

       Ribbon默認懶加載,意味着只有在發起調用的時候纔會建立客戶端。在第一次進行服務調用時會作一些初始化工做,好比:建立負載均衡器 ,若是網絡狀況很差,此次調用可能會超時。
在這裏插入圖片描述
能夠開啓飢餓加載,在項目啓動時就完成初始化工做,解決第一次調用慢的問題

ribbon:
  eager-load:
    # 開啓ribbon飢餓加載,源碼對應屬性配置類:RibbonEagerLoadProperties
    enabled: true
    # 配置mall-user使用ribbon飢餓加載,多個使用逗號分隔
    clients: mall-order

開啓以後,能夠看到,第一次調用日誌已經沒有初始化工做了
在這裏插入圖片描述

       

3. Ribbon的負載均衡原理

       

①:收集帶有@LoadBalanced註解的RestTemplate,併爲其添加一個負載均衡攔截器

       上面的使用案例中,若是不加@LoadBalanced註解的話,RestTemplate沒有負載均衡功能的,爲何一個@LoadBalanced註解就使RestTemplate具備負載均衡功能了呢?下面來看一下Ribbon的負載均衡原理

      Ribbon既然在springboot中使用,天然會想到springboot對Ribbon的自動配置類RibbonAutoConfiguration!這個自動配置類被加載的前置條件是:須要加載LoadBalancerAutoConfiguration類,以下所示

@Configuration
@Conditional(RibbonAutoConfiguration.RibbonClassesConditions.class)
@RibbonClients
@AutoConfigureAfter(name = "org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration")

//加載的前置條件:先加載 LoadBalancerAutoConfiguration類
@AutoConfigureBefore({ LoadBalancerAutoConfiguration.class,
		AsyncLoadBalancerAutoConfiguration.class })
@EnableConfigurationProperties({ RibbonEagerLoadProperties.class,
		ServerIntrospectorProperties.class })
public class RibbonAutoConfiguration {

      而 LoadBalancerAutoConfiguration類是屬於spring-cloud-common包下的,該包是spring cloud的基礎包,確定會被加載的
在這裏插入圖片描述
進入LoadBalancerAutoConfiguration類中,該類中註冊了幾個bean,主要作了如下幾件事

  1. 收集到全部帶有@LoadBalanced註解的RestTemplate,並放入restTemplates 集合
  2. 建立一個帶有負載均衡功能的攔截器LoadBalancerInterceptor
  3. 在容器類初始化完畢後,把全部的RestTemplate內部都添加上攔截器LoadBalancerInterceptor,當有請求通過ribbon,經過 restTemplate 發起調用時,會先走此攔截器,實現負載均衡
@Configuration
@ConditionalOnClass(RestTemplate.class)
@ConditionalOnBean(LoadBalancerClient.class)
@EnableConfigurationProperties(LoadBalancerRetryProperties.class)
public class LoadBalancerAutoConfiguration {

	//Springboot會將全部帶有@LoadBalanced註解的RestTemplate,都放進restTemplates這個集合中去
	@LoadBalanced
	@Autowired(required = false)
	private List<RestTemplate> restTemplates = Collections.emptyList();

	@Autowired(required = false)
	private List<LoadBalancerRequestTransformer> transformers = Collections.emptyList();

	//SmartInitializingSingleton :該方法會等待全部類都初始化完畢後執行
	// 拿到上面收集到的全部帶有@LoadBalanced註解的RestTemplate
	// 執行下面的函數式接口方法customize,把攔截器 放入每個restTemplate中,
	//當有請求通過ribbon,經過 restTemplate 發起調用時,會先走此攔截器,實現負載均衡
	@Bean
	public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(
			final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {
		return () -> restTemplateCustomizers.ifAvailable(customizers -> {
			for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
				for (RestTemplateCustomizer customizer : customizers) {
					customizer.customize(restTemplate);
				}
			}
		});
	}

	@Bean
	@ConditionalOnMissingBean
	public LoadBalancerRequestFactory loadBalancerRequestFactory(
			LoadBalancerClient loadBalancerClient) {
		return new LoadBalancerRequestFactory(loadBalancerClient, this.transformers);
	}

	@Configuration
	@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
	static class LoadBalancerInterceptorConfig {

		//向容器中放入一個帶有負載均衡功能的攔截器
		@Bean
		public LoadBalancerInterceptor ribbonInterceptor(
				LoadBalancerClient loadBalancerClient,
				LoadBalancerRequestFactory requestFactory) {
			return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
		}

		//這是一個函數式接口,此處實現的是接口的customize方法,定義了一個操做,操做內容以下:
		// 傳入一個restTemplate,並把上面的攔截器 放入restTemplate中
		//當有請求通過ribbon,經過 restTemplate 發起調用時
		//會先走此攔截器,實現負載均衡
		@Bean
		@ConditionalOnMissingBean
		public RestTemplateCustomizer restTemplateCustomizer(
				final LoadBalancerInterceptor loadBalancerInterceptor) {
			return restTemplate -> {
				List<ClientHttpRequestInterceptor> list = new ArrayList<>(
						restTemplate.getInterceptors());
				list.add(loadBalancerInterceptor);
				restTemplate.setInterceptors(list);
			};
		}

	}

       LoadBalancerAutoConfiguration是如何精準的收集到全部的帶有@LoadBalanced註解的RestTemplate呢?點開@LoadBalanced註解,發現他是帶有@Qualifier限定符的!

@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Qualifier  //spring的限定符
public @interface LoadBalanced {

}

@Qualifier限定符的做用:

  • 一個接口有兩個實現類,當咱們經過 @AutoWired 註解進行注入時,spring不知道應該綁定哪一個實現類,從而致使報錯。
  • 這時就能夠經過 @Qualifier註解來解決。經過它能夠標識咱們須要的實現類。而@LoadBalanced的元註解是 @Qualifier,因此 源碼中就能夠經過@LoadBalanced註解來限定收集全部帶有@LoadBalanced註解的RestTemplate實現

       

②:選擇負載均衡器,執行負載均衡算法(默認輪詢)

       上面說到請求通過ribbon的RestTemplate調用時,會先走其內部的攔截器LoadBalancerInterceptor的負載均衡邏輯。既然是走攔截器,那麼就能夠去看LoadBalancerInterceptorintercept()方法,通常該方法就有負載均衡邏輯!

@Override
	public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
			final ClientHttpRequestExecution execution) throws IOException {
		final URI originalUri = request.getURI();
		String serviceName = originalUri.getHost();
		Assert.state(serviceName != null,
				"Request URI does not contain a valid hostname: " + originalUri);
		//負載均衡器的 execute 方法
		return this.loadBalancer.execute(serviceName,
				this.requestFactory.createRequest(request, body, execution));
	}
public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint)
			throws IOException {
		//獲取負載均衡器 ILoadBalancer 
		ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
		// 經過負載均衡選擇一個服務器
		Server server = getServer(loadBalancer, hint);
		if (server == null) {
			throw new IllegalStateException("No instances available for " + serviceId);
		}
		RibbonServer ribbonServer = new RibbonServer(serviceId, server,
				isSecure(server, serviceId),
				serverIntrospector(serviceId).getMetadata(server));
		//向某一臺服務器 發起HTTP請求
		return execute(serviceId, ribbonServer, request);
	}

如上,Ribbon經過負載均衡 選擇一臺機器 發起http請求已經執行完畢!

       

Ribbon負載均衡器的默認實現:ZoneAwareLoadBalancer

       上面說到getLoadBalancer(serviceId)方法能夠獲取一個負載均衡器,用於執行負載均衡算法,這個負載均衡器已經在RibbonClientConfiguration配置類中初始化好了,獲取時直接從容器中取便可

@Bean
	@ConditionalOnMissingBean
	public ILoadBalancer ribbonLoadBalancer(IClientConfig config,
			ServerList<Server> serverList, ServerListFilter<Server> serverListFilter,
			IRule rule, IPing ping, ServerListUpdater serverListUpdater) {
		// 從配置類中找一個負載均衡器 ILoadBalancer
		if (this.propertiesFactory.isSet(ILoadBalancer.class, name)) {
			return this.propertiesFactory.get(ILoadBalancer.class, config, name);
		}
		// 若是沒有就建立一個負載均衡器的實現 ZoneAwareLoadBalancer
		return new ZoneAwareLoadBalancer<>(config, rule, ping, serverList,
				serverListFilter, serverListUpdater);
	}

能夠看到RibbonClientConfiguration配置類中,默認初始化的是ZoneAwareLoadBalancer,它具有區域感知的能力。
在這裏插入圖片描述

在建立默認負載均衡器時(new ZoneAwareLoadBalancer)作了什麼呢?

  1. nacos註冊中心上當即獲取最新的服務信息,保存在ribbon的本地服務列表中
  2. 使用延時定時線程池,定時從nacos上拉取最新服務地址,更新ribbon的本地服務列表中

進入new ZoneAwareLoadBalancer中:

void restOfInit(IClientConfig clientConfig) {
        boolean primeConnection = this.isEnablePrimingConnections();
        // turn this off to avoid duplicated asynchronous priming done in BaseLoadBalancer.setServerList()
        this.setEnablePrimingConnections(false);
        
        //開啓定時延時任務(會延後執行),定時從nacos上拉取最新服務地址,更新`ribbon`的本地服務列表中
        enableAndInitLearnNewServersFeature();
        
		//進入後當即執行,從`nacos`註冊中心上當即獲取最新的服務信息,保存在`ribbon`的本地服務列表中
        updateListOfServers();
        
        if (primeConnection && this.getPrimeConnections() != null) {
            this.getPrimeConnections()
                    .primeConnections(getReachableServers());
        }
        this.setEnablePrimingConnections(primeConnection);
        LOGGER.info("DynamicServerListLoadBalancer for client {} initialized: {}", clientConfig.getClientName(), this.toString());
    }

開啓定時任務方法enableAndInitLearnNewServersFeature();以下:

@Override
    public synchronized void start(final UpdateAction updateAction) {
        if (isActive.compareAndSet(false, true)) {
        	//開啓一個線程,執行更新任務
            final Runnable wrapperRunnable = new Runnable() {
                @Override
                public void run() {
                    if (!isActive.get()) {
                        if (scheduledFuture != null) {
                            scheduledFuture.cancel(true);
                        }
                        return;
                    }
                    try {
                    	//UpdateAction 又是一個函數式接口,
                    	//doUpdate方法須要看一下傳進來的方法內容,下文展現
                        updateAction.doUpdate();
                        lastUpdated = System.currentTimeMillis();
                    } catch (Exception e) {
                        logger.warn("Failed one update cycle", e);
                    }
                }
            };
            
			//開啓定時延時任務,定時執行上面的線程
            scheduledFuture = getRefreshExecutor().scheduleWithFixedDelay(
                    wrapperRunnable,
                    initialDelayMs,
                    refreshIntervalMs,
                    TimeUnit.MILLISECONDS
            );
        } else {
            logger.info("Already active, no-op");
        }
    }

================== 函數式接口的 updateAction.doUpdate()方法內容以下=============

    protected final ServerListUpdater.UpdateAction updateAction = new ServerListUpdater.UpdateAction() {
        @Override
        public void doUpdate() {
            updateListOfServers();
        }
    };

	// updateListOfServers方法以下:
    @VisibleForTesting
    public void updateListOfServers() {
        List<T> servers = new ArrayList<T>();
        if (serverListImpl != null) {
        	// 該方法會從對應的配置中心中取最新數據
            servers = serverListImpl.getUpdatedListOfServers();
            LOGGER.debug("List of Servers for {} obtained from Discovery client: {}",
                    getIdentifier(), servers);

            if (filter != null) {
                servers = filter.getFilteredListOfServers(servers);
                LOGGER.debug("Filtered List of Servers for {} obtained from Discovery client: {}",
                        getIdentifier(), servers);
            }
        }
        // 更新本地服務列表!
        updateAllServerList(servers);
    }

負載均衡器初始化時,當即從註冊中心獲取最新服務的方法updateListOfServers(),以下:

@VisibleForTesting
    public void updateListOfServers() {
        List<T> servers = new ArrayList<T>();
        if (serverListImpl != null) {
        	//從註冊中心上獲取服務地址
            servers = serverListImpl.getUpdatedListOfServers();
            LOGGER.debug("List of Servers for {} obtained from Discovery client: {}",
                    getIdentifier(), servers);

            if (filter != null) {
                servers = filter.getFilteredListOfServers(servers);
                LOGGER.debug("Filtered List of Servers for {} obtained from Discovery client: {}",
                        getIdentifier(), servers);
            }
        }
        //更新本地服務列表!
        updateAllServerList(servers);
    }

在這裏插入圖片描述

       

Ribbon負載均衡算法的默認實現:ZoneAvoidanceRule

       有了負載均衡器ZoneAwareLoadBalancer,接下來執行負載均衡算法便可getServer(loadBalancer, hint);,回顧上邊第②條的代碼:

public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint)
			throws IOException {
		//獲取負載均衡器 ILoadBalancer 
		ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
		// 經過負載均衡算法 選擇一個服務器地址
		Server server = getServer(loadBalancer, hint);
		if (server == null) {
			throw new IllegalStateException("No instances available for " + serviceId);
		}
		RibbonServer ribbonServer = new RibbonServer(serviceId, server,
				isSecure(server, serviceId),
				serverIntrospector(serviceId).getMetadata(server));
		//向某一臺服務器 發起HTTP請求
		return execute(serviceId, ribbonServer, request);
	}

進入負載均衡算法選擇服務器的方法getServer(loadBalancer, hint)

public Server chooseServer(Object key) {
        if (counter == null) {
            counter = createCounter();
        }
        counter.increment();
        if (rule == null) {
            return null;
        } else {
            try {
            	//這個rule就是ribbon的負載均衡算法!
            	//默認是 ZoneAvoidanceRule ,在沒有區域的環境下,相似於輪詢(RandomRule)
                return rule.choose(key);
            } catch (Exception e) {
                logger.warn("LoadBalancer [{}]:  Error choosing server for key {}", name, key, e);
                return null;
            }
        }
    }

ZoneAvoidanceRule 的核心邏輯以下:使用cas+ 死循環輪詢服務器地址

private int incrementAndGetModulo(int modulo) {
        for (;;) {
            int current = nextIndex.get();
            //取模獲得其中一個
            int next = (current + 1) % modulo;
            //cas賦值 ,返回nextIndex的機器
            if (nextIndex.compareAndSet(current, next) && current < modulo)
                return current;
        }
    }

       其中,ZoneAvoidanceRule的初始化和負載均衡器ZoneAwareLoadBalancer的初始化同樣,也在RibbonClientConfiguration配置類中完成!

@Bean
	@ConditionalOnMissingBean
	public IRule ribbonRule(IClientConfig config) {
		if (this.propertiesFactory.isSet(IRule.class, name)) {
			return this.propertiesFactory.get(IRule.class, config, name);
		}
		// 負載均衡策略的 默認實現ZoneAvoidanceRule 
		ZoneAvoidanceRule rule = new ZoneAvoidanceRule();
		rule.initWithNiwsConfig(config);
		return rule;
	}

Ribbon的負載均衡策略有以下幾種

  • RandomRule:隨機策略, 隨機選擇一個Server。
  • RetryRule: 重試策略 。對選定的負載均衡策略機上重試機制,在一個配置時間段內當選擇Server不成功,則一直嘗試使用subRule的方式選擇一個可用的server。
  • RoundRobinRule: 輪詢策略 。 輪詢index,選擇index對應位置的Server。
  • AvailabilityFilteringRule:可用性過濾策略 。 過濾掉一直鏈接失敗的被標記爲circuit tripped的後端Server,並過濾掉那些高併發的後端Server或者使用一個AvailabilityPredicate來包含過濾server的邏輯,其實就是檢查status裏記錄的各個Server的運行狀態。
  • BestAvailableRule:最低併發策略 。 選擇一個最小的併發請求的Server,逐個考察Server,若是Server被tripped了,則跳過。
  • WeightedResponseTimeRule:響應時間加權重策略。根據響應時間加權,響應時間越長,權重越小,被選中的可能性越低。
  • ZoneAvoidanceRule:區域權重策略。默認的負載均衡策略,綜合判斷server所在區域的性能和server的可用性,輪詢選擇server而且判斷一個AWS Zone的運行性能是否可用,剔除不可用的Zone中的全部server。在沒有區域的環境下,相似於輪詢(RandomRule)
  • NacosRule: 同集羣優先調用

       

4. Feign的原理

Feign和OpenFeign的區別?

  • Feign:Feign是Netflix開發的聲明式、模板化的HTTP客戶端,Feign可幫助咱們更加便捷、優雅地調用HTTP API。能夠單獨使用
  • OpenFeign:Spring Cloud openfeign對Feign進行了 加強,使其支持Spring MVC註解,另外還整合了RibbonEureka,從而使得Feign的使用更加方便
           

Feign的調用原理圖(可在每一層作擴展)

在這裏插入圖片描述

OpenFeign的經常使用配置項:(對應上圖,能夠在配置中作擴展)

  • 日誌配置:有時候咱們遇到 Bug,好比接口調用失敗、參數沒收到等問題,或者想看看調用性能,就須要配置 Feign 的 日誌了,以此讓 Feign 把請求信息輸出來。日誌配置分爲局部配置和全局配置!
  • 攔截器配置:每次 feign 發起http調用以前,會去執行攔截器中的邏輯,就相似mvc中的攔截器。好比:作權限認證。
  • 超時時間配置:經過 Options 能夠配置鏈接超時時間(默認2秒)和讀取超時時間(默認5秒),注意:Feign的底層用的是Ribbon,但超時時間以Feign配置爲準
  • 客戶端組件配置:Feign 中默認使用 JDK 原生的 URLConnection 發送 HTTP 請求,咱們能夠集成別的組件來替換掉 URLConnection,好比 Apache HttpClient,OkHttp。
  • GZIP 壓縮配置:再配置文件中開啓壓縮能夠有效節約網絡資源,提高接口性能
  • 編碼器解碼器配置:Feign 中提供了自定義的編碼解碼器設置,同時也提供了多種編碼器的實現,好比 Gson、Jaxb、Jackson。 咱們能夠用不一樣的編碼解碼器來處理數據的傳輸。若是你想傳輸 XML 格式的數據,能夠自定義 XML 編碼解 碼器來實現獲取使用官方提供的 Jaxb

       

5. OpenFeign是如何整合Ribbon的?

       經過上邊,咱們已經知道Ribbon能夠把微服務的 服務名 經過負載均衡策略替換成某一臺機器的IP地址,而後經過http請求進行訪問!以下所示:

http://mall-order/order/findOrderByUserId ====> http://192.168.100.15/order/findOrderByUserId

Feign則是把參數組裝到url中去,實現一個完整的RPC調用

http://mall-order/order/findOrderByUserId/5(參數) ====> http://192.168.100.15/order/findOrderByUserId/5(參數)

①:掃描全部@FeignClient註解,以FactoryBean的形式註冊到容器中

       Feign的使用須要用到@EnableFeignClients@FeignClient("gulimall-ware")這兩個註解,其中,@EnableFeignClients經過@Import向容器中添加了一個bean定義註冊器,用於掃描@FeignClient("gulimall-ware")註解,註冊bean定義

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
//經過`@Import`向容器中添加了一個bean定義註冊器 FeignClientsRegistrar
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {

}

進入FeignClientsRegistrarregisterBeanDefinitions方法,查看具體註冊了什麼

@Override
	public void registerBeanDefinitions(AnnotationMetadata metadata,
			BeanDefinitionRegistry registry) {
		//註冊一下默認配置
		registerDefaultConfiguration(metadata, registry);
		//註冊全部@FeignClient註解 標註的類
		registerFeignClients(metadata, registry);
	}

註冊邏輯中,最主要的就是把全部@FeignClient註解 標註的類以FactoryBean的形式註冊到容器中

public void registerFeignClients(AnnotationMetadata metadata,
			BeanDefinitionRegistry registry) {
		// 1. 獲取一個掃描器
		ClassPathScanningCandidateComponentProvider scanner = getScanner();
		scanner.setResourceLoader(this.resourceLoader);

		Set<String> basePackages;

		//2. 拿到全部@FeignClient註解標註的類
		Map<String, Object> attrs = metadata
				.getAnnotationAttributes(EnableFeignClients.class.getName());
		AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
				FeignClient.class);
		final Class<?>[] clients = attrs == null ? null
				: (Class<?>[]) attrs.get("clients");
		if (clients == null || clients.length == 0) {
			scanner.addIncludeFilter(annotationTypeFilter);
			basePackages = getBasePackages(metadata);
		}
		else {
			final Set<String> clientClasses = new HashSet<>();
			basePackages = new HashSet<>();
			for (Class<?> clazz : clients) {
				basePackages.add(ClassUtils.getPackageName(clazz));
				clientClasses.add(clazz.getCanonicalName());
			}
			AbstractClassTestingTypeFilter filter = new AbstractClassTestingTypeFilter() {
				@Override
				protected boolean match(ClassMetadata metadata) {
					String cleaned = metadata.getClassName().replaceAll("\\$", ".");
					return clientClasses.contains(cleaned);
				}
			};
			scanner.addIncludeFilter(
					new AllTypeFilter(Arrays.asList(filter, annotationTypeFilter)));
		}

		for (String basePackage : basePackages) {
			Set<BeanDefinition> candidateComponents = scanner
					.findCandidateComponents(basePackage);
			for (BeanDefinition candidateComponent : candidateComponents) {
				if (candidateComponent instanceof AnnotatedBeanDefinition) {
					// verify annotated class is an interface
					AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
					AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
					Assert.isTrue(annotationMetadata.isInterface(),
							"@FeignClient can only be specified on an interface");

					Map<String, Object> attributes = annotationMetadata
							.getAnnotationAttributes(
									FeignClient.class.getCanonicalName());

					String name = getClientName(attributes);
					registerClientConfiguration(registry, name,
							attributes.get("configuration"));
					// 3. 把這些類注入容器
					registerFeignClient(registry, annotationMetadata, attributes);
				}
			}
		}
	}

修改bean定義爲FactoryBean的子類FeignClientFactoryBean

private void registerFeignClient(BeanDefinitionRegistry registry,
			AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
		String className = annotationMetadata.getClassName();
		//把bean定義構建成一個FactoryBean
		BeanDefinitionBuilder definition = BeanDefinitionBuilder
				.genericBeanDefinition(FeignClientFactoryBean.class);
		validate(attributes);
		definition.addPropertyValue("url", getUrl(attributes));
		definition.addPropertyValue("path", getPath(attributes));
		String name = getName(attributes);
		definition.addPropertyValue("name", name);
		String contextId = getContextId(attributes);
		definition.addPropertyValue("contextId", contextId);
		definition.addPropertyValue("type", className);
		definition.addPropertyValue("decode404", attributes.get("decode404"));
		definition.addPropertyValue("fallback", attributes.get("fallback"));
		definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory"));
		definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);

		String alias = contextId + "FeignClient";
		AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();

		boolean primary = (Boolean) attributes.get("primary"); // has a default, won't be
																// null

		beanDefinition.setPrimary(primary);

		String qualifier = getQualifier(attributes);
		if (StringUtils.hasText(qualifier)) {
			alias = qualifier;
		}

		BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
				new String[] { alias });
		// 注入!
		BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
	}

②:RPC調用時,經過LoadBalancerFeignClient整合Ribbon,實現負載均衡調用

       既然是把@FeignClient("gulimall-ware")註解標註的類 以FactoryBean的子類FeignClientFactoryBean的形式注入到容器,那麼RPC調用時確定是經過調用FeignClientFactoryBeangetObject方法來使用的!

@Override
	public Object getObject() throws Exception {
		return getTarget();
	}

getTarget()

<T> T getTarget() {
		FeignContext context = this.applicationContext.getBean(FeignContext.class);
		Feign.Builder builder = feign(context);

		if (!StringUtils.hasText(this.url)) {
			if (!this.name.startsWith("http")) {
				this.url = "http://" + this.name;
			}
			else {
				this.url = this.name;
			}
			this.url += cleanPath();
			return (T) loadBalance(builder, context,
					new HardCodedTarget<>(this.type, this.name, this.url));
		}

loadBalance()

protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
			HardCodedTarget<T> target) {
		//獲取的實際上是LoadBalanceFeignClient,用於整合Ribbon的負載均衡
		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?");
	}

Client client = getOptional(context, Client.class);獲取的是Feign的客戶端實現:
在這裏插入圖片描述

整合Ribbon邏輯:進入LoadBalancerFeignClientexecute方法中,使用Feign的負載均衡器向Ribbon發請求,已達到整合Ribbon的目的!

org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient # execute

@Override
	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);
			//使用Feign的負載均衡器向Ribbon發請求,已達到整合Ribbon的目的!
			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);
		}
	}

綜上所述,負載均衡的邏輯仍是在Ribbon中,而Feign經過整合Ribbon實現了帶有負載均衡的RPC調用!

       

6. 總結圖

在這裏插入圖片描述

相關文章
相關標籤/搜索