前幾天有個大兄弟問了我一個問題,注冊中心要內建SpringCloud,想實作SpringCloud的負載均衡,需要實作哪些接口和規範。
既然這個兄弟問到我了,而我又剛好知道,這不得好好寫一篇文章來回答這個問題,雖然在後面的聊天中我已經回答過了。
接下來本文就來探究一下Nacos、OpenFeign、Ribbon、loadbalancer等元件協調工作的原理,知道這些原理之後,就知道應該需要是實作哪些接口了。
再多說一句,本文并沒有詳細地深入剖析各個元件的源碼,如果有感興趣的兄弟可以從公衆号背景菜單欄中的文章分類中檢視我之前寫的關于Nacos、OpenFeign、Ribbon源碼剖析的文章。
Nacos
先從Nacos講起。
Nacos是什麼,官網中有這麼一段話
這一段話說的直白點就是Nacos是一個注冊中心和配置中心!
在Nacos中有用戶端和服務端的這個概念
- 服務端需要單獨部署,用來儲存服務執行個體資料的
- 用戶端就是用來跟服務端通信的SDK,支援不同語言
當需要向Nacos服務端注冊或者擷取服務執行個體資料的時候,隻需要通過Nacos提供的用戶端SDK就可以了,就像下面這樣:
引入依賴
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
<version>1.4.4</version>
</dependency>
示例代碼
Properties properties = new Properties();
properties.setProperty("serverAddr", "localhost");
properties.setProperty("namespace", "8848");
NamingService naming = NamingFactory.createNamingService(properties);
//服務注冊,注冊一個order服務,order服務的ip是192.168.2.100,端口8080
naming.registerInstance("order", "192.168.2.100", 8080);
//服務發現,擷取所有的order服務執行個體
List<Instance> instanceList = naming.selectInstances("order", true);
當服務注冊到Nacos服務端的時候,在服務端内部會有一個集合去存儲服務的資訊
這個集合在注冊中心界中有個響亮的名字,服務系統資料庫。
如何進行服務自動注冊?
用過SpringCloud的小夥伴肯定知道,在項目啟動的時候服務能夠自動注冊到服務注冊中心,并不需要手動寫上面那段代碼,那麼服務自動注冊是如何實作的呢?
服務自動注冊三闆斧
SpringCloud本身提供了一套服務自動注冊的機制,或者說是限制,其實就是三個接口,隻要注冊中心實作這些接口,就能夠在服務啟動時自動注冊到注冊中心,而這三個接口我稱為服務自動注冊三闆斧。
服務執行個體資料封裝--Registration
Registration是SpringCloud提供的一個接口,繼承了ServiceInstance接口
Registration
ServiceInstance
從ServiceInstance的接口定義可以看出,這是一個服務執行個體資料的封裝,比如這個服務的ip是多少,端口号是多少。
是以Registration就是目前服務執行個體資料封裝,封裝了目前服務的所在的機器ip和端口号等資訊。
Nacos既然要整合SpringCloud,自然而然也實作了這個接口
NacosRegistration
這樣目前服務需要被注冊到注冊中心的資訊就封裝好了。
服務注冊--ServiceRegistry
ServiceRegistry也是個接口,泛型就是上面提到的服務執行個體資料封裝的接口
ServiceRegistry
這個接口的作用就是把上面封裝的目前服務的資料Registration注冊通過register方法注冊到注冊中心中。
Nacos也實作了這個接口。
NacosServiceRegistry
并且核心的注冊方法的實作代碼跟前面的demo幾乎一樣
服務自動注冊--AutoServiceRegistration
AutoServiceRegistration
AutoServiceRegistration是一個标記接口,是以本身沒有實際的意義,僅僅代表了自動注冊的意思。
AutoServiceRegistration有個抽象實作AbstractAutoServiceRegistration
AbstractAutoServiceRegistration是個抽象類
AbstractAutoServiceRegistration實作了ApplicationListener,監聽了WebServerInitializedEvent事件。
WebServerInitializedEvent這個事件是SpringBoot在項目啟動時,當諸如tomcat這類Web服務啟動之後就會釋出,注意,隻有在Web環境才會釋出這個事件。
ServletWebServerInitializedEvent繼承自WebServerInitializedEvent。
是以一旦當SpringBoot項目啟動,tomcat等web伺服器啟動成功之後,就會觸發AbstractAutoServiceRegistration監聽器的執行。
最終就會調用ServiceRegistry注冊Registration,實作服務自動注冊
Nacos自然而然也繼承了AbstractAutoServiceRegistration
NacosAutoServiceRegistration
對于Nacos而言,就将目前的服務注冊的ip和端口等資訊,就注冊到了Nacos服務注冊中心。
是以整個注冊流程就可以用這麼一張圖概括
當然,不僅僅是Nacos是這麼實作的,常見的比如Eureka、Zookeeper等注冊中心在整合SpringCloud都是實作上面的三闆斧。
Ribbon
講完了SpringCloud環境底下是如何自動注冊服務到注冊中心的,下面來講一講Ribbon。
我們都知道,Ribbon是負載均衡元件,他的作用就是從衆多的服務執行個體中根據一定的算法選擇一個服務執行個體。
但是有個疑問,服務執行個體的資料都在注冊中心,Ribbon是怎麼知道的呢???
答案其實很簡單,那就是需要注冊中心去主動适配Ribbon,隻要注冊中心去适配了Ribbon,那麼Ribbon自然而然就知道服務執行個體的資料了。
Ribbon提供了一個擷取服務執行個體的接口,叫ServerList
ServerList
接口中提供了兩個方法,這兩個方法在衆多的實作中實際是一樣的,并沒有差別。
當Ribbon通過ServerList擷取到服務執行個體資料之後,會基于這些資料來做負載均衡的。
Nacos自然而然也實作了ServerList接口,為Ribbon提供Nacos注冊中心中的服務資料。
NacosServerList
這樣,Ribbon就能擷取到了Nacos服務注冊中心的資料。
同樣地,除了Nacos之外,Eureka、Zookeeper等注冊中心也都實作了這個接口。
到這,其實就明白了Ribbon是如何知道注冊中心的資料了,需要注冊中心來适配。
在這裡插個個人的看法,其實我覺得Ribbon在适配SpringCloud時對擷取服務執行個體這塊支援封裝的不太好。
因為SpringCloud本身就是一套限制、規範,隻要遵守這套規範,那麼就可以實作各個元件的替換,這就是為什麼換個注冊中心隻需要換個依賴,改個配置檔案就行。
而Ribbon本身是一個具體的負載均衡元件,注冊中心要想整合SpringCloud,還得需要單獨去适配Ribbon,有點違背了SpringCloud限制的意義。
就類似mybatis一樣,mybatis依靠jdbc,但是mybatis根本不關心哪個資料庫實作的jdbc。
真正好的做法是Ribbon去适配SpringCloud時,用SpringCloud提供的api去擷取服務執行個體,這樣不同的注冊中心隻需要适配這個api,無需單獨适配Ribbon了。
而SpringCloud實際上是提供了這麼一個擷取服務執行個體的api,DiscoveryClient
DiscoveryClient
通過DiscoveryClient就能夠擷取到服務執行個體,當然也是需要不同注冊中心的适配。
随着Ribbon等元件停止維護之後,SpringCloud官方自己也搞了一個負載均衡元件loadbalancer,用來平替Ribbon。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
<version>2.2.5.RELEASE</version>
</dependency>
這個元件底層在擷取服務執行個體的時候,就是使用的DiscoveryClient。
是以對于loadbalancer這個負載均衡組價來說,注冊中心隻需要實作DiscoveryClient之後就自然而然适配了loadbalancer。
OpenFeign
OpenFeign是一個rpc架構,當我們需要調用遠端服務的時候,隻需要聲明個接口就可以遠端調用了,就像下面這樣
聽上去很神奇,其實本質上就是後面會為接口建立一個動态代理對象,解析類上,方法上的注解。
當調用方法的時候,會根據方法上面的參數拼接一個http請求位址,這個位址的格式是這樣的http://服務名/接口路徑。
比如,上面的例子,當調用saveOrder方法的時候,按照這種規律拼出的位址就是這樣的 http://order/order,第一個order是服務名,第二個order是PostMapping注解上面的。
但是由于隻知道需要調用服務的服務名,不知道服務的ip和端口,還是無法調用遠端服務,這咋辦呢?
這時就輪到Ribbon登場了,因為Ribbon這個大兄弟知道服務執行個體的資料。
于是乎,OpenFeign就對Ribbon說,兄弟,你不是可以從注冊中心擷取到order服務所有服務執行個體資料麼,幫我從這些服務執行個體資料中找一個給我。
于是Ribbon就會從注冊中心擷取到的服務執行個體中根據負載均衡政策選擇一個服務執行個體傳回給OpenFeign。
OpenFeign拿到了服務執行個體,此時就擷取到了服務所在的ip和端口,接下來就會重新建構請求路徑,将路徑中的服務名替換成ip和端口,代碼如下
reconstructURIWithServer
- Server就是服務執行個體資訊的封裝
- orignal就是原始的url,就是上面提到的,http://order/order
假設擷取到的orde服務所在的ip和端口分别是192.168.2.100和8080,最終重構後的路徑就是http://192.168.2.100:8080/order,之後OpenFeign就可以發送http請求了。
至于前面提到的loadbalancer,其實也是一樣的,他也會根據負載均衡算法,從DiscoveryClient擷取到的服務執行個體中選擇一個服務執行個體給OpenFeign,後面也會根據服務執行個體重構url,再發送http請求。
loadbalancer元件重構url代碼
總結
到這,就把Nacos、OpenFeign、Ribbon、loadbalancer等元件協調工作的原理講完了,其實就是各個元件會預留一些擴充接口,這也是很多開源架構都會幹的事,當第三方架構去适配的,隻要實作這些接口就可以了。
最後畫一張圖來總結一下上述組價的工作的原理。
原文:https://mp.weixin.qq.com/s/NKSlSfHsO-tr5UFJqRy_pQ
如果感覺本文對你有幫助,點贊關注支援一下