淮南哪里做網(wǎng)站錦州網(wǎng)站seo
上一篇文章中, 詳細(xì)介紹了 Nacos 注冊中心的原理, 相信看完后, 大家應(yīng)該完全掌握了 Nacos 客戶端是如何自動進(jìn)行服務(wù)注冊的, 以及 Nacos 客戶端是如何訂閱服務(wù)實(shí)例信息的, 以及 Nacos 服務(wù)器是如何處理客戶端的注冊和訂閱請求的;
本文承上啟下, 在訂閱服務(wù)實(shí)例的基礎(chǔ)上, 介紹如何在實(shí)例之間進(jìn)行選擇, 實(shí)現(xiàn)負(fù)載均衡; 并詳細(xì)介紹了負(fù)載均衡組件 LocaBanlancer 和函數(shù)式調(diào)用組件 OpenFeign 是如何與 Nacos 注冊中心進(jìn)行集成的;
如果在閱讀過程中對文中提到的 SpringBoot 啟動過程以及擴(kuò)展機(jī)制不太了解, 或者對 @Import 注解不了解, 參考這篇文章 SpringBoot啟動流程與配置類處理機(jī)制詳解, 附源碼與思維導(dǎo)圖, 強(qiáng)烈建議學(xué)習(xí)后再來讀本文;
LoadBalancer
-
Nacos 1.X 版本自動引入 Ribbon 依賴, 使用 Ribbon 做負(fù)載均衡;
-
Nacos 2.X 版本就沒有了, 因?yàn)?Ribbon 的特性已經(jīng)不滿足 SpringBoot 的要求;
-
2.X 版本可以用 SpringCloud 團(tuán)隊(duì)提供的 LoadBalancer 組件來做負(fù)載均衡;
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
LoadBalancerClient
Spring Cloud LoadBalancer :: Spring Cloud Commons
-
SpringCloud 定義的接口, 用于負(fù)載均衡地選擇服務(wù)實(shí)例, 是SpringCloud制定的負(fù)載均衡規(guī)范;
-
由具體廠商去實(shí)現(xiàn)這個(gè)接口, 比如 Ribbon 和 LoadBanlancer 都提供了實(shí)現(xiàn)類;
例如 LoadBalancer 包下 spring.factories 指定了自動配置類
BlockingLoadBalancerClientAutoConfiguration
;這個(gè)配置類向 Spring 容器注冊了一個(gè)
BlockingLoadBalancerClient
bean;這樣就可以使用 @Autowired 注解獲取
LoadBalancerClient
并使用; -
兩個(gè)關(guān)鍵方法,
choose
和execute
choose 方法底層實(shí)現(xiàn)本質(zhì)上仍然是調(diào)用了
NamingService
( 使用 Nacos 時(shí)自然就是NacosNamingService
)的selectInstances
方法, 訂閱服務(wù)并獲取所有實(shí)例, 然后根據(jù)負(fù)載均衡算法選擇一個(gè)實(shí)例返回; -
execute 方法有兩個(gè)重載, 一個(gè)需要傳入具體的 ServiceInstance, 一個(gè)不需要;
調(diào)用不傳 Instance 的 execute 方法時(shí), 底層實(shí)現(xiàn)會先調(diào)用 choose 方法選出一個(gè)實(shí)例, 然后調(diào)用有 Instance 參數(shù)的 execute 方法完成請求發(fā)送;
-
LoadBalancerClient 使用, 可以通過直接注入 LoadBanlancerClient對象來使用
@RequestMapping("/order/*")
public class OrderController {@AutowiredLoadBalancerClient loadBalancerClient;@AutowiredRestTemplate restTemplate;@GetMapping("loadbalancer")public String test0(){ServiceInstance instance = loadBalancerClient.choose("stock-service");String url = instance.getUri() + "/stock/test0";return restTemplate.getForObject(url, String.class);}
}
- 或者使用
@LoadBalanced
注解, 注冊有復(fù)雜均衡功能的 RestTemplate
@Bean
@LoadBalanced
public RestTemplate restTemplate(){return new RestTemplate();
}@GetMapping("loadbalanced")
public String test1(){return restTemplate.getForObject("http://stock-service/stock/test0", String.class);
}
RestTemplate
持有一個(gè)ClientHttpRequestInterceptor
的鏈表; RestTemplate 發(fā)送請求之前會先調(diào)用這些 Interceptor 的攔截方法;在 LoadBalancer 的自動配置類中, 會注入所有有
@LoadBalanced
注解修飾的 RestTemplate bean, 并且會將一個(gè)LoadBalancerInterceptor
對象放到這些 RestTemplate 對象的攔截器列表中;
這個(gè)攔截器會攔截
RestTemplate
發(fā)送的請求, 調(diào)用LoadBalancerClient
不帶ServiceInstance
參數(shù)的execute
方法發(fā)送請求; 實(shí)現(xiàn)負(fù)載均衡;
負(fù)載均衡策略
默認(rèn)的是輪詢策略RoundRobinLoadBalancer
; 也可以選擇RandomLoadBalancer
, 這倆都是 SpringCloud 提供的;
@LoadBalancerClients({@LoadBalancerClient(name = "order-service", configuration = RandomLoadBalancerConfig.class),@LoadBalancerClient(name = "stock-service", configuration = RandomLoadBalancerConfig.class)
})// 對所有服務(wù)有效
@LoadBalancerClients(defaultConfiguration = RandomLoadBalancerConfig.class)
public class RandomLoadBalancerConfig {@BeanReactorLoadBalancer<ServiceInstance> randomLoadBalancer(Environment environment,LoadBalancerClientFactory loadBalancerClientFactory) {String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);return new RandomLoadBalancer(loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class),name);}
}
OpenFeign
@FeignClient("course-service")
public interface CourseFeignClient {@GetMapping("/feign/course")String course();
}@FeignClient("student-service")
public interface CourseFeignClient {@GetMapping("/feign/stu")String course();
}
@EnableFeignClients
public class OrderApp {public static void main(String[] args) {SpringApplication.run(OrderApp.class, args);}
}
-
@EnableFeignClients
注解通過@Import
注解引入了一個(gè)ImportBeanDefinitionRegistrar
; 其注冊方法如下:public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {// .....一些不重要的代碼this.registerDefaultConfiguration(metadata, registry);this.registerFeignClients(metadata, registry); }
registerFeignClients
-
拿到 @EnableFeignClients 注解的屬性;
-
創(chuàng)建一個(gè)空 Set, 保存 BeanDefinition;
-
看你的 @EnableFeignClients 注解的
clients
屬性是否為空, 如果不為空, 就遍歷該屬性指定的類, 創(chuàng)建BeanDefinition, 放到 Set 中; -
如果
clients
為空, 就根據(jù)value
屬性和basePackages
屬性和basePackageClasses
屬性指定的值去掃描包, 找到有@FeignClient
注解修飾的類, 創(chuàng)建 BeanDefinition, 添加到 Set;如果這些屬性都為空, 就掃描啟動類所在的包;
basePackageClasses
用法: 該注解指定的類所在的包將被掃描; -
遍歷 Set , 獲取 BeanDefinition 的
AnnotationMetadata
, 進(jìn)而拿到@FeignClient
注解的name
屬性( 表示服務(wù)名 ); -
調(diào)用
registerFeignClient
, 向容器中注冊當(dāng)前BeanDefinition
對應(yīng)的一個(gè) FactoryBean;
registerFeignClient
- 創(chuàng)建一個(gè)
FeignClientFactoryBean
, 在其getObejct
方法中, 封裝了創(chuàng)建代理對象的邏輯; - 其
getObject
方法層層調(diào)用, 最終調(diào)用了ReflectiveFeign
中的一個(gè)方法, 該方法使用 JDK 動態(tài)代理, 為 @FeignClient 注解修飾的接口, 創(chuàng)建了代理對象; - 將
FeignClientFactoryBean
的 BeanDefinition 添加Spring容器中;
動態(tài)代理
- 動態(tài)代理使用的
InvocationHandler
是FeignInvocationHandler
(最終是SynchronousMethodHandler
) - Handler 的 invoke 方法中, 又經(jīng)過層層調(diào)用, 最終執(zhí)行了如下邏輯:
- 獲取
@FeignClient 注解的
name
屬性, 通過LoadBanlancerClient
負(fù)載均衡地獲取一個(gè)實(shí)例; (所以類加載路徑下必須有 LoadBalancerClient 的實(shí)現(xiàn)類才行, 換言之, 必須引入 Ribbon 或 LoadBanlancer 之類的組件) - 然后根據(jù)被調(diào)用的方法的 Mapping 注解, 得到請求路徑; 和實(shí)例的地址進(jìn)行拼接, 得到最終的請求地址;
- 然后通過 HTTP 工具發(fā)送請求;