負載均衡是從多個服務中根據某個策略選擇一個進行訪問,常見的負載均衡分爲兩種java
ribbon
,客戶端會有一個服務器地址列表,在發送請求前經過負載均衡算法選擇 一個服務器,而後進行訪問Nginx
,先發送請求,而後經過Nginx
的負載均衡算法,在多個服務器之間選擇一 個進行訪問!常見的負載均衡算法:算法
隨機
:經過隨機選擇服務進行執行,通常這種方式使用較少;輪詢
:請求來以後排隊處理,輪着來加權輪詢
:經過對服務器性能的分型,給高配置,低負載的服務器分配更高的權重,均衡各個 服務器的壓力;一致性hash
:經過客戶端請求的地址的HASH值取模映射進行服務器調度。最少併發
:將請求分配到當前壓力最小的服務器上
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: eager-load: # 開啓ribbon飢餓加載,源碼對應屬性配置類:RibbonEagerLoadProperties enabled: true # 配置mall-user使用ribbon飢餓加載,多個使用逗號分隔 clients: mall-order
開啓以後,能夠看到,第一次調用日誌已經沒有初始化工做了
上面的使用案例中,若是不加@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,主要作了如下幾件事
@LoadBalanced
註解的RestTemplate
,並放入restTemplates
集合LoadBalancerInterceptor
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
的負載均衡邏輯。既然是走攔截器,那麼就能夠去看LoadBalancerInterceptor
的intercept()
方法,通常該方法就有負載均衡邏輯!
@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
請求已經執行完畢!
上面說到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
)作了什麼呢?
nacos
註冊中心上當即獲取最新的服務信息,保存在ribbon
的本地服務列表中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); }
有了負載均衡器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
: 同集羣優先調用
Feign和OpenFeign的區別?
Feign
:Feign是Netflix開發的聲明式、模板化的HTTP客戶端,Feign可幫助咱們更加便捷、優雅地調用HTTP API。能夠單獨使用OpenFeign
:Spring Cloud openfeign對Feign進行了 加強,使其支持Spring MVC
註解,另外還整合了Ribbon
和Eureka
,從而使得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
經過上邊,咱們已經知道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(參數)
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 { }
進入FeignClientsRegistrar
的registerBeanDefinitions
方法,查看具體註冊了什麼
@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); }
既然是把@FeignClient("gulimall-ware")
註解標註的類 以FactoryBean
的子類FeignClientFactoryBean
的形式注入到容器,那麼RPC調用時確定是經過調用FeignClientFactoryBean
的getObject
方法來使用的!
@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邏輯:進入LoadBalancerFeignClient
的execute
方法中,使用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
調用!