Ⅰ 微服務架構 | *3.5 Nacos 服務注冊與發現的源碼分析
參考資料 :
《Spring Microservices in Action》
《Spring Cloud Alibaba 微服務原理與實戰》
《B站 尚矽谷 SpringCloud 框架開發教程 周陽》
為方便理解與表達,這里把 Nacos 控制台和 Nacos 注冊中心稱為 Nacos 伺服器(就是 web 界面那個),我們編寫的業務服務稱為 Nacso 客戶端;
Nacos 客戶端將自己注冊進 Nacos 伺服器。《1. 服務如何注冊進 Nacos 注冊中心》主要從 Nacos 客戶端角度解釋如何發送信息給 Nacos 伺服器;《2. Nacos 伺服器注冊服務》主要從 Nacos 伺服器角度解釋注冊原理;
《3. 客戶端查詢所有服務實例》將從服務消費者和提供者的角度,解釋服務消費者如何獲取提供者的所有實例。服務消費者和提供者都是 Nacos 的客戶端;
《4. 客戶端監聽 Nacos 伺服器以動態獲取服務實例》從消費者客戶端角度出發監聽 Nacos 伺服器,以動態獲知提供者的變化;
Ⅱ 閑聊注冊中心——ZK、Eureka、Sofa-Registry
最開始服務之間的調用藉助的是域名,域名其實是個好東西,使用起來很方便,但所有調用請求都得走域名解析和負載均衡,相對來說性能就差一點,而且這些夾在中間的零部件會演變成性能瓶頸和潛在的單點風險,後來大家一比較發現還不如直接端到端調用,那麼就需要一個東西把調用鏈的兩端連起來,這就是注冊中心。
注冊中心提供服務的注冊發現,用來連接調用鏈路的 Provider 和 Consumer 這兩個端點。一個注冊中心得管理好服務的擴縮容、故障轉移、流量分配這類的核心功能,自身同樣需要高可用,妥善解決跨地域部署情況下注冊中心節點之間的數據一致性,因此我更傾向於認為注冊中心是一個偏向體系化的產品,它的價值依賴於與之相匹配的服務規模和治理體系,它的思想雖然產生得很早,但是規模化地落地並不容易,偏小的服務規模本身對於注冊中心的依賴並不明顯,實現的手段也沒有定式;大型服務又會對注冊中心的定位、功能提出更苛刻的需求。
究竟是選擇強一致性還是高可用性,當下的主流技術選型傾向於後者,但這並不是說明 CP 組件沒有存在的意義,而是 AP 組件更契合互聯網對於注冊中心的定位。
互聯網系統高可用永遠是擺在首位的東西,因為系統不可用的時間通常跟財產損失直接掛鉤。服務列表在短時間少了一兩個節點、多了一兩個節點,這點流量傾斜對於大多數服務來說感知並不明顯,只需要注冊中心在某個短暫的時間窗口內重新達到一致性的狀態就可以,注冊中心不應該在運行時影響到處於正常調用的兩個端點之間的連通性。
CP 的代表就是 zookeeper ,至今 zookeeper 也是非常流行的用於注冊中心的組件,這在一定程度上要感謝 bbo 。 bbo 在 zookeeper 中是用一個樹形結構的兩個節點,分別維護服務的 Provider 和 Comsumer 的,列表注冊在這兩個節點下面,服務的健康檢查依賴 zookeeper 提供的臨時節點特性,實際上跟 session 的生命周期綁定在一起,但臨時節點的問題就是數據易失,一旦集群故障節點重啟丟失服務列表,可能造成服務大面積癱瘓,電商新貴 PDD 就曾經出現過這個問題。
CP 最關鍵的問題在於無法支持機房容災,例如 ABC 三個機房,機房 C 和其他兩個機房產生了網路隔離,部署在機房 C 的 zookeeper 節點是無法提供寫入的,這意味著機房 C 部署的 Provider 不能擴縮容、不能重啟,最好在故障期間一直保持原樣,否則 Provider 任何操作都會是高危的,會直接影響到部署在機房 C 的 Comsumer 。
順帶說一句,我認為 zookeeper 真挺好的,它本身的定位就是個分布式協調服務,並不是專門為注冊中心設計的,你不能拿評價注冊中心的眼光去評價它,你要的功能 zookeeper 基本上都有,藉助 Curator 拿來就可以用,提供了 election ,提供了 ZAB , TPS 低了點(其實也可以了)但是也滿足大部分服務規模。
Eureka —— 奈飛的網紅組件, AP 注冊中心的代表。 Eureka 1.x 今天回頭看來是個很牽強的實現,因為它的架構從誕生之日起,就意味著將來數據規模增長之後大概率會出問題,集群整體可伸縮性很低,每擴容一台 Eureka 節點意味著整體點對點之間需要多一份數據流,而這種 Peer 點對點機制的數據流很容易打滿網卡,造成服務端壓力。根據公開的資料, Eureka 在實例規模五千左右時就會出現明顯的性能瓶頸,甚至是服務不可用的情況。
但是從另一個方面來說, Eureka 的架構清晰、運維部署都很方便,而且 Eureka 作為 SpringCloud 推薦的注冊中心實現,國內用戶數目也相當可觀,可見在服務規模恰當的情況下其性能並沒有問題,從攜程的分享資料也可以看出,其內部的注冊中心也是類似於 Eureka 的實現,可見沒有絕對完美的架構設計,適合自己、滿足業務需求才是最主要的。
Eureka 服務端多節點架構其實有點去中心化的意思,有意保持了每個節點的無狀態特性,但是代價就是每個節點都持有全量數據,新增的數據可以往任意一個節點寫入,然後由這個節點向其他節點廣播,最終達到一致性。
數據都沒有持久化,僅保存在內存中,帶來了更好的讀寫性能、更短的響應時間,但是無法處理數據瓶頸,大節點擴容造成的數據同步存在打滿網卡的風險,這點跟 redis 集群很像。客戶端 30s 定期上報心跳、客戶端緩存服務列表這些都沒什麼好談的, Eureka 有個很有意思的點是,它的集群節點實現了一個自我保護機制,用來預測到底是集群出問題還是客戶端出問題,實現很簡單但是思想挺實用的。
Eureka 2.0 的設計文檔上體現了讀寫分離集群的思想,本質上是為了提升集群性能和容量,但是非常遺憾,目前 2.0 的狀態是 Discontinued ,短時間應該不會有成熟的產品出現。
雖然沒有 Eureka 2.0 ,但是可以從螞蟻開源的 Sofa-Registry 中了解類似思想的實現,Sofa-Registry 脫胎於阿里的 ConfigServer ,從公開的資料顯示,國內阿里應該是最早做注冊中心讀寫分離實踐的。
這種架構帶來的直接好處就是可以支持海量數據,第一層的 session 集群可以無限水平擴展,彼此之間完全不需要通信, session 集群在內存中維護了注冊中心需要的一切拓撲關系,客戶端僅僅連接一部分 session 集群中的機器,如果某台 session 機器掛了客戶端會選擇另一台重連;背後的 data 集群則通過分片存儲所有的源數據,分片之間通過主從副本保證高可用,再將源數據推送到 session 集群保存下來,供客戶端讀取。
缺點也很明顯,這種架構人力投入、運維成本肯定高於 Eureka 。
這幾乎是注冊中心不可避免的問題,高可用、機房容災、網路延遲等都會催生出要求注冊中心節點能夠跨地域部署,這會引申出另一個問題就是如何做數據同步。
這種情況下已經不可能通過客戶端注冊或者是集群節點數據流復制來保證異地注冊中心集群之間的數據一致,而是會研發單獨的數據同步組件,通過跨地域專線實現地域間注冊中心的數據同步,如果專線斷開,此時各自地域的注冊中心依舊可以獨立服務於本地域內服務的調用,等到專線恢復,二者的數據繼續進行同步合並糾正,最終達到一致性。對這個知識點感興趣的可以了解下 Nacos-Sync 組件,是 Nacos 項目專門推出的開源數據同步服務。
文章略過了一些注冊中心通用的概念,例如數據模型的分層、服務列表本地緩存、健康檢查的方式、推拉權衡等,我認為敘述這些細節的意義並不大。
很多時候,理解某個組件我認為最重要的是理清它的架構演進過程,這是寶貴的經驗財富,你可以抓住並學習每次架構演進的方向和原因。
Ⅲ 「SpringCloud原理」Ribbon核心組件以及運行原理萬字源碼剖析
大家好,本文我將繼續來剖析SpringCloud中負載均衡組件Ribbon的源碼。本來我是打算接著OpenFeign動態代理生成文章直接講Feign是如何整合Ribbon的,但是文章寫了一半發現,如果不把Ribbon好好講清楚,那麼有些Ribbon的細節理解起來就很困難,所以我還是打算單獨寫一篇文章來剖析Ribbon的源碼,這樣在講Feign整合Ribbon的時候,我就不再贅述這些細節了。好了,話不多說,直接進入主題。
這是個很簡單的東西,就是服務實例數據的封裝,裡面封裝了服務實例的ip和埠之類的,一個服務有很多台機器,那就有很多個Server對象。
ServerList是個介面,泛型是Server,提供了兩個方法,都是獲取服務實例列表的,這兩個方法其實在很多實現類中實現是一樣的,沒什麼區別。這個介面很重要,因為這個介面就是Ribbon獲取服務數據的來源介面,Ribbon進行負載均衡的服務列表就是通過這個介面來的,那麼可以想一想是不是只要實現這個介面就可以給Ribbon提供服務數據了?事實的確如此,在SpringCloud中,eureka、nacos等注冊中心都實現了這個介面,都將注冊中心的服務實例數據提供給Ribbon,供Ribbon來進行負載均衡。
通過名字也可以知道,是用來更新服務注冊表的數據,他有唯一的實現,就是PollingServerListUpdater,這個類有一個核心的方法,就是start,我們來看一下start的實現。
通過這段方法我們可以看出,首先通過isActive.compareAndSet(false, true)來保證這個方法只會被調用一下,然後封裝了一個Runnable,這個Runnable幹了一件核心的事,就是調用傳入的updateAction的doUpdate方法,然後將Runnable扔到了帶定時調度功能的線程池,經過initialDelayMs(默認1s)時間後,會調用一次,之後都是每隔refreshIntervalMs(默認30s)調用一次Runnable的run方法,也就是調用updateAction的doUpdate方法。
所以這個類的核心作用就是每隔30s會調用一次傳入的updateAction的doUpdate方法的實現,記住這個結論。
IRule是負責負載均衡的演算法的,也就是真正實現負載均衡獲取一個服務實例就是這個介面的實現。比如說實現類RandomRule,就是從一堆服務實例中隨機選取一個服務實例。
就是一個配置介面,有個默認的實現DefaultClientConfigImpl,通過這個可以獲取到一些配置Ribbon的一些配置。
這個介面的作用,對外主要提供了獲取服務實例列表和選擇服務實例的功能。雖然對外主要提供獲取服務的功能,但是在實現的時候,主要是用來協調上面提到的各個核心組件的,使得他們能夠協調工作,從而實現對外提供獲取服務實例的功能。
這個介面的實現有好幾個實現類,但是我講兩個比較重要的。
BaseLoadBalancer
核心屬性
allServerList:緩存了所有的服務實例數據
upServerList:緩存了能夠使用的服務實例數據。
rule:負載均衡演算法組件,默認是RoundRobinRule
核心方法
setRule:這個方法是設置負載均衡演算法的,並將當前這個ILoadBalancer對象設置給IRule,從這可以得出一個結論,IRule進行負載均衡的服務實例列表是通過ILoadBalancer獲取的,也就是 IRule 和 ILoadBalancer相互引用。setRule(rule)一般是在構造對象的時候會調用。
chooseServer:就是選擇一個服務實例,是委派給IRule的choose方法來實現服務實例的選擇。
BaseLoadBalancer這個實現類總體來說,已經實現了ILoadBalancer的功能的,所以這個已經基本滿足使用了。
說完BaseLoadBalancer這個實現類,接下來說一下DynamicServerListLoadBalancer實現類。DynamicServerListLoadBalancer繼承自BaseLoadBalancer,DynamicServerListLoadBalancer主要是對BaseLoadBalancer功能進行擴展。
DynamicServerListLoadBalancer
成員變數
serverListImpl:上面說過,通過這個介面獲取服務列表
filter:起到過濾的作用,一般不care
updateAction:是個匿名內部類,實現了doUpdate方法,會調用updateListOfServers方法
serverListUpdater:上面說到過,默認就是唯一的實現類PollingServerListUpdater,也就是每個30s就會調用傳入的updateAction的doUpdate方法。
這不是巧了么,serverListUpdater的start方法需要一個updateAction,剛剛好成員變數有個updateAction的匿名內部類的實現,所以serverListUpdater的start方法傳入的updateAction的實現其實就是這個匿名內部類。
那麼哪裡調用了serverListUpdater的start方法傳入了updateAction呢?是在構造的時候調用的,具體的調用鏈路是調用 restOfInit -> (),這里就不貼源碼了
所以,其實DynamicServerListLoadBalancer在構造完成之後,默認每隔30s中,就會調用updateAction的匿名內部類的doUpdate方法,從而會調用updateListOfServers。所以我們來看一看 updateListOfServers 方法幹了什麼。
這個方法實現很簡單,就是通過調用 ServerList 的getUpdatedListOfServers獲取到一批服務實例數據,然後過濾一下,最後調用updateAllServerList方法,進入updateAllServerList方法。
其實很簡單,就是調用每個服務實例的setAlive方法,將isAliveFlag設置成true,然後調用setServersList。setServersList這個方法的主要作用是將服務實例更新到內部的緩存中,也就是上面提到的allServerList和upServerList,這里就不貼源碼了。
其實分析完updateListOfServers方法之後,再結合上面源碼的分析,我們可以清楚的得出一個結論,那就是默認每隔30s都會重新通過ServerList組件獲取到服務實例數據,然後更新到BaseLoadBalancer緩存中,IRule的負載均衡所需的服務實例數據,就是這個內部緩存。
從DynamicServerListLoadBalancer的命名也可以看出,他相對於父類BaseLoadBalancer而言,提供了動態更新內部服務實例列表的功能。
為了便於大家記憶,我畫一張圖來描述這些組件的關系以及是如何運作的。
說完一些核心的組件,以及他們跟ILoadBalancer的關系之後,接下來就來分析一下,ILoadBalancer是在ribbon中是如何使用的。
ILoadBalancer是一個可以獲取到服務實例數據的組件,那麼服務實例跟什麼有關,那麼肯定是跟請求有關,所以在Ribbon中有這么一個抽象類,,這個是用來執行請求的,我們來看一下這個類的構造。
通過上面可以看出,在構造的時候需要傳入一個ILoadBalancer。
中有一個方法executeWithLoadBalancer,這個是用來執行傳入的請求,以負載均衡的方式。
這個方法構建了一個LoadBalancerCommand,隨後調用了submit方法,傳入了一個匿名內部類,這個匿名內部類中有這么一行代碼很重要。
這行代碼是根據給定的一個Server重構了URI,這是什麼意思呢?舉個例子,在OpenFeign那一篇文章我說過,會根據服務名拼接出類似 http:// ServerA 的地址,那時是沒有伺服器的ip地址的,只有服務名,假設請求的地址是 http:// ServerA/api/sayHello ,那麼reconstructURIWithServer乾的一件事就是將ServerA服務名替換成真正的服務所在的機器的ip和埠,假設ServerA所在的一台機器(Server裡面封裝了某台機器的ip和埠)是192.168.1.101:8088,那麼重構後的地址就變成 http:// 192.168.1.101:8088/api/ sayHello ,這樣就能發送http請求到ServerA服務所對應的一台伺服器了。
之後根據新的地址,調用這個類中的execute方法來執行請求,execute方法是個抽象方法,也就是交給子類實現,子類就可以通過實現這個方法,來發送http請求,實現rpc調用。
那麼這台Server是從獲取的呢?其實猜猜也知道,肯定是通過ILoadBalancer獲取的,因為submit方法比較長,這里我直接貼出submit方法中核心的一部分代碼
就是通過selectServer來選擇一個Server的,selectServer我就不翻源碼了,其實最終還是調用ILoadBalancer的方法chooseServer方法來獲取一個服務,之後就會調用上面的說的匿名內部類的方法,重構URI,然後再交由子類的execut方法來實現發送http請求。
所以,通過對的executeWithLoadBalancer方法,我們可以知道,這個抽象類的主要作用就是通過負載均衡演算法,找到一個合適的Server,然後將你傳入的請求路徑 http:// ServerA/api/sayHello 重新構建成類似 http:// 192.168.1.101:8088/api/ sayHello 這樣,之後調用子類實現的execut方法,來發送http請求,就是這么簡單。
到這里其實Ribbon核心組件和執行原理我就已經說的差不多了,再來畫一張圖總結一下
說完了Ribbon的一些核心組件和執行原理之後,我們再來看一下在SpringCloud環境下,這些組件到底是用的哪些實現,畢竟有寫時介面,有的是抽象類。
Ribbon的自動裝配類:RibbonAutoConfiguration,我拎出了核心的源碼
RibbonAutoConfiguration配置類上有個@RibbonClients註解,接下來講解一下這個註解的作用
SpringClientFactory是不是感覺跟OpenFeign中的FeignContext很像,其實兩個的作用是一樣的,SpringClientFactory也繼承了NamedContextFactory,實現了配置隔離,同時也在構造方法中傳入了每個容器默認的配置類RibbonClientConfiguration。至於什麼是配置隔離,我在OpenFeign那篇文章說過,不清楚的小夥伴可以後台回復feign01即可獲得文章鏈接。
配置優先順序問題
優先順序最低的就是FeignContext和SpringClientFactory構造時傳入的配置類
至於優先順序怎麼來的,其實是在NamedContextFactory中createContext方法中構建時按照配置的優先順序一個一個傳進去的。
RibbonClientConfiguration提供的默認的bean
接下來我們看一下RibbonClientConfiguration都提供了哪些默認的bean
配置類對應的bean,這里設置了ConnectTimeout和ReadTimeout都是1s中。
IRule,默認是ZoneAvoidanceRule,這個Rule帶有過濾的功能,過濾哪些不可用的分區的服務(這個過濾可以不用care),過濾成功之後,繼續採用線性輪詢的方式從過濾結果中選擇一個出來。至於這個propertiesFactory,可以不用管,這個是默認讀配置文件的中的配置,一般不設置,後面看到都不用care。
至於為什麼容器選擇NacosServerList而不是ConfigurationBasedServerList,主要是因為這個配置類是通過@RibbonClients導入的,也就是比SpringClientFactory導入的RibbonClientConfiguration配置類優先順序高。
ServerListUpdater,就是我們剖析的PollingServerListUpdater,默認30s更新一次BaseLoadBalancer內部服務的緩存。
那麼在springcloud中,上圖就可以加上注冊中心。
三、總結
本文剖析了Ribbon這個負載均衡組件中的一些核心組件的源碼,並且將這些組件之間的關系一一描述清楚,同時也剖析了在發送請求的時候是如何通過ILoadBalancer獲取到一個服務實例,重構URI的過程。希望本篇文章能夠讓你知道Ribbon是如何工作的。
Ⅳ 百度AI人臉注冊源碼詳解
1、起點 AppConfig.client.addUser(image, imageType, groupId, userId, options) 調用人臉注冊api,從addUser進入
2、那就要看一下_request請求是怎麼發送的,點進去看
3、至此調用基本結束,接下來看裡面用到的幾個方法:
Ⅳ Dubbo2.7.8服務啟動過程源碼分析
可以從 git clone,導入intellij idea,子模塊bbo-demo可以調試。
也可以新建Maven工程,自寫demo。 推薦(要動手) 。
本文以新建為例,並 推薦 引入nacos做為注冊中心(可選其他注冊中心)。
新建介面:
新建介面實現類AnnotationProviderServiceImpl:
新建啟動類AnnotationProviderBootstrap.
bbo-provider.properties配置文件如下:
整體目錄結構:
上述工程是基於annotation方式創建的,所以啟動類為
設置斷點,並啟動。
AnnotatedBeanDefinitionReader的190-192行:
回到84行:
AbstractApplicationContext的524行:
上述是初始化各種Processor,會做兩部分操作,可實時查看控制台輸出日誌。
在的50行設置斷點。
回到546行:
來到DubboBootstrap的893行start方法, 核心 所在:
initialize();主要是一些初始化操作。
重點關注exportServices
在exportServices中:
ServiceConfig被翻譯成:
<bbo:service beanName="ServiceBean:com.zp.bbo.api.HelloService" />
sc.export()過程:
見ServiceConfig183行:
init serviceMetadata之後真正執行doExport動作,最終來到ServiceConfig304行:doExportUrls
這里做了幾件事:
1.registerProvider:將HelloService放入 providers 和providersWithoutGroup。
2.獲取注冊中心地址:registryURLs
3.doExportUrlsFor1Protocol
4.exportLocal
這里根據不同的Protocol啟動不同的Server。可調試進入DubboProtocol的export方法。
5.在RegistryProtocol中注冊到注冊中心
調試至FailbackRegistry240行:
進入NacosRegistry152行,進行注冊。
進入NacosNamingService139行:registerInstance
最終NamingProxy168行registerService完成注冊。
6.exported() 分發ServiceConfigExportedEvent事件
那麼到此,bbo的服務提供者注冊過程分析就已經全部完成。
接下來將進行bbo其他源碼分析,包括負載均衡,路由等等。
Ⅵ Eureka源碼淺讀---自我保護機制
Eureka源碼採用1.7.2版本
本人小白,此文為本人閱讀源碼筆記,如果您讀到本文,您需要自己甄別是否正確,文中的說明只代表本人理解,不一定是正確的!!!
自我保護機制設計的初衷是防止服務注冊服務因為本地網路故障,長時間未接受到心跳請求,造成錯誤的移除大量服務實例,其實調用服務還是可用的
自我保護機制是和自動故障移除聯系在一起的,針對的移除實例也是自動故障移除
在服務故障移除的方法中有這樣一個判斷,當返回false時候,直接返回,不進行故障實例的摘除
進入該方法
關於獲取上一分鍾心跳總數,Eureka Server內部採用的是定時線程進行統計,使用兩個AtomicLong進行保存當前和上一分鍾的心跳總數
該方法初始化了運行了定時調度的線程進行統計,默認執行間隔為1min,執行流程:
那麼當前的心跳總數是怎麼計算的呢,直接看心跳的renew()方法,是否嵌入了計數器累計操作
如上所示,當接收到心跳時,當前心跳計數器進行了遞增操作
而getNumOfRenewsInLastMin()獲取上一分鍾心跳總數就是獲取lastBucket數量,再找下該定時任務啟動的入口
和自動故障移除的定時同時啟動的,那麼lastBucket代表了上一分鍾的心跳總數
接下來,我們需要看看期望每分鍾最小心跳總數的由來:
numberOfRenewsPerMinThreshold最開始的初始化計算是在Eureka Server初始化計算的,使用當前Server拉取到的服務實例總數 * 0.85
在openForTraffic()方法中使用初始化拉取的服務實例總數作為基數標准進行計算,(int) (this.expectedNumberOfRenewsPerMin * serverConfig.getRenewalPercentThreshold()) -> count * 2 * 0.85,
集群模式下,count為其他節點中已注冊的服務實例總數,單節點就為0
下面我們看看在注冊中心接收到注冊,下線等請求執行時,維護numberOfRenewsPerMinThreshold
注冊,當前實例數量+2,下線,當前實例數量-2,然後再次*0.85,計算期望每分鍾最小心跳數
在Eureka Server中有專門的定時任務進行更新numberOfRenewsPerMinThreshold,默認每15min執行一次
主要流程如下:
注意,自動服務故障移除沒有進行numberOfRenewsPerMinThreshold的更新
<font color= 'blue'>服務故障實例的摘除需要判斷當前是否處於自我保護模式,而自我保護模式的默認是開啟(isSelfPreservationModeEnabled),需要判斷上一分鍾的心跳總數是否大於期望每分鍾最小心跳數,如果在15分鍾內,累計丟失了15%以上的節點心跳,那麼Eureka Server就會認為當前所處的網路環境異常,從而處於自動保護模式,故障實例將不會移除,再等待15min後,進行expectedNumberOfRenewsPerMin的基於當前服務實例的重新計算後,自我保護模式才會關閉!</font>
自我保護服務開啟模擬:
Ⅶ Nacos注冊中心之概要設計
在之前的文章中分析了Nacos配置中心,配置中心的核心是配置的創建、讀取、推送。
注冊中心的核心比配置中心多一個 服務探活 模塊,他倆的相似度非常高,甚至阿里內部的注冊中心就叫 ConfigServer 。
Nacos注冊中心打算分成幾個模塊來分析,本文重點在於 概要設計 ,基於2.0.0版本。
用Nacos的源碼來搭建源碼閱讀和調試環境,可參考 《Nacos配置中心模塊詳解》 Nacos調試環境搭建 部分。
其中 JVM參數可以指定只啟動Naming模塊,也可以不指定,默認全都啟動。
example模塊下將NamingExample復制一份進行測試。
客戶端視角的服務發現模型(注意:服務端視角的模型定義與客戶端視角有區別)包含以下幾點內容:
他們的關系如下
除了上述的三層模型外,Nacos注冊中心和配置中心有著一樣的namespace設計,與client綁定,可隔離環境,租戶。
Nacos 2.0 為ephemeral不同的實例提供了兩套流程:
本文從總體上分析了Nacos 2.0的模型設計、介面設計以及交互流程,讀完後對Nacos的服務發現有一個整體上的認識。
後續篇幅會從細節入手,如bbo Nacos擴展、一致性協議、探活、CMDB擴展等逐一進行分析。
Ⅷ Dubbo——Registry服務注冊
注冊中心(Registry)在微服務架構中的作用舉足輕重,有了它,服務提供者(Provider) 和消費者(Consumer) 就能感知彼此。
Registry 只是 Consumer 和 Provider 感知彼此狀態變化的一種便捷途徑而已,它們彼此的實際通訊交互過程是直接進行的,對於 Registry 來說是透明無感的。Provider 狀態發生變化了,會由 Registry 主動推送訂閱了該 Provider 的所有 Consumer,這保證了 Consumer 感知 Provider 狀態變化的及時性,也將和具體業務需求邏輯交互解耦,提升了系統的穩定性。
Dubbo 中存在很多概念,但有些理解起來就特別費勁,如本文的 Registry,翻譯過來的意思是「注冊中心」,但它其實是應用本地的注冊中心客戶端,真正的「注冊中心」服務是其他獨立部署的進程,或進程組成的集群,比如 ZooKeeper 集群。本地的 Registry 通過和 ZooKeeper 等進行實時的信息同步,維持這些內容的一致性,從而實現了注冊中心這個特性。另外,就 Registry 而言,Consumer 和 Provider 只是個用戶視角的概念,它們被抽象為了一條 URL 。
RegistryFactory 就是產生一個注冊中心的工程,它有個自適應的方法getRegistry,那麼我們知道bbo會通過javassist動態產生一個RegistryFactory$Adaptive類,並且getRegistry方法的內部實現大致是如下:
它通過傳入的URL的protocol協議欄位排判斷是什麼類型注冊中心。例如,url的protocol的協議是zookeeper,那麼就會根據SPI的ExtensionLoader.getExtensionLoader(RegistryFactory.class).getExtension("zookeeper")得到一個產生ZooKeeper注冊中心的工廠,也就是ZookeeperRegistryFactory,而ZookeeperRegistryFactory這個類的getRegistry就是返回一個Zookeeper注冊中心。
可以看出其語義,一個注冊中心Registry是一個節點(extends Node),並且它具有注冊服務(extends RegistryService)的功能。
bbo支持如下這些注冊中心zookeeper、consul、etcd3、eureka、nacas、redis、sofa,那麼就會產生相應如下的Registry:ZookeeperRegistry、ConsulRegistry、EtcdRegistry、NacosRegistry、RedisRegistry、SofaRegistry。類圖如下:
所以我們知道,這些注冊中心都是繼承FailbackRegistry,這個FailbackRegistry其意思就是說,如果一個服務注冊到當前某個注冊中心注冊失敗後,可會在後台產生一個daemon線程,定時的把注冊失敗服務重新注冊,並且有一定的重試限制。
在上面的類圖中我們並沒有發現有個名為EurekaRegistry這樣的類,因為實現了另一個介面ServiceDiscovery方式,類名為EurekaServiceDiscovery來進行服務發現。
bbo的協議是通過名為org.apache.bbo.rpc.Protocol來進行抽象的,那麼注冊協議也是一樣的,是通過org.apache.bbo.registry.integration.RegistryProtocol來表達的,繼承org.apache.bbo.rpc.Protocol。RegistryPrtocol是擴展點Protocol的具體實現,會一次調用其setter方法來注入其需要的屬性,RegistryPrtocol其中有個屬性就是RegistryFactory,那麼就要為它注入一個具體的RegistryFactory,那麼這個具體的RegistryFactory工廠就是上面的RegistryFactory$Adaptive。因為注入的屬性對象會從SpringExtensionFactory和SpiExtensionFactory工廠中查詢,剛好RegistryFactory也是一個擴展點,所以會在SpiExtensionFactory找出,並且SpiExtensionFactory工廠的實現如下:
所以知道是返回一個自適應的擴展點,即RegistryFactory$Adaptive。
Protocol協議具有導出服務export()功能,和引用服務refer()功能。在RegistryProtocol中,在這個2個方法內就有對服務注冊到注冊中心的操作。
在服務導出中,首先要有一個認知,做bbo服務暴露的時候,我們有2中方式,一種是通過註解的方式:
@DubboService、@Service(非spring的)。或者通過xml的方式<bbo:service />。
不管採用哪一種方式,最終需要暴露的服務首先會包裝成一個ServiceBean的對象。這個ServiceBean 持有具體需要服務注冊的對象ref。ServiceBean的類圖如下:
服務導出也是是一個繁瑣的過程,本文只討論其服務導出引入與注冊中心交互。
DubboBootstrap是一個bbo框架啟動的幫助類,他有一個start()方法,在該方法的內部就會調用exportServices()用於導出服務,和調用referServices()進行引用服務。
一般使用bbo框架的都會引入Spring框架,Spring框架有一個事件監聽機制,bbo正是監聽Spring的上下文刷新事件ContextRefreshedEvent,來啟動Dubbo服務的。這個服務監聽類就是。
registry方法定位到FailbackRegistry,主要作用當服務注冊失敗後,可以在後端線程重試。
接下來分析AbstractRegistry 的作用和FailbackRegistry的重試機制,並且詳細剖析ZookeeperRegistry。
首先,直接引出這個類的作用,該類主要把服務提供者信息緩存本地文件上,文件目錄是:當前用戶目錄下的/.bbo/bbo-registry- {hos}-${port}.cache。
在解讀源碼前,先閱讀下AbstractRegistry類的成員變數,從成員變數中可以看到這個類是怎麼完成數據的本地化存儲的。
上面的注釋已經非常的清晰了,這里就不在描述,需要關注的是notify()這個函數,所以當每個服務注冊和訂閱時,首次創建注冊中心都會進行notify操作。具體來看下notify方法。
從上面可以知道,把消費端的訂閱的服務信息存入了file文件中,doSaveProperties就是文件操作,不進行分析。再一次強調下,消費端訂閱時,會訂閱某個具體服務下3個節點(providers,configurations,routers)。
接著,FailbackRegistry繼承自AbstractRegistry。
其構造函數如下,可以得知除了調用AbstractRegistry構造方法外,並且創建一個HashedWheelTimer類型的定時器。
並且FailbackRegistry 成員記錄一組注冊失敗和訂閱失敗的集合,然後通過retryTimer定式掃描這些失敗集合,重新發起訂閱和注冊。
下面是失敗集合:
參考:
https://www.cnblogs.com/liferecord/p/13462175.html
https://www.cnblogs.com/liferecord/p/13497411.html
https://www.cnblogs.com/Cubemen/p/12294377.html
https://blog.csdn.net/cold___play/article/details/107007130
https://www.jianshu.com/p/75931e545b36
Ⅸ 如何更好地學習bbo源代碼
1、Dubbo與Spring的整合 Dubbo在使用上可以做到非常簡單,不管是Provider還是Consumer都可以通過Spring的配置文件進行配置,配置完之後,就可以像使用 spring bean一樣進行服務暴露和調用了,完全看不到bbo api的存在。這是因為bbo使用了spring提供的可擴展Schema自定義配置支持。在spring配置文件中,可以像、這樣進行配置。 META-INF下的spring.handlers文件中指定了bbo的xml解析類:DubboNamespaceHandler。像前面的被解 析成ServiceConfig,被解析成ReferenceConfig等等。 2、jdk spi擴展 由於Dubbo是開源框架,必須要提供很多的可擴展點。Dubbo是通過擴展jdk spi機制來實現可擴展的。具體來說,就是在META-INF目錄下,放置文件名為介面全稱,文件中為key、value鍵值對,value為具體實現類 的全類名,key為標志值。由於bbo使用了url匯流排的設計,即很多參數通過URL對象來傳遞,在實際中,具體要用到哪個值,可以通過url中的參 數值來指定。 Dubbo對spi的擴展是通過ExtensionLoader來實現的,查看ExtensionLoader的源碼,可以看到Dubbo對jdk spi做了三個方面的擴展:
(1)jdk spi僅僅通過介面類名獲取所有實現,而ExtensionLoader則通過介面類名和key值獲取一個實現;
(2)Adaptive實現,就是生成一個代理類,這樣就可以根據實際調用時的一些參數動態決定要調用的類了。
(3)自動包裝實現,這種實現的類一般是自動激活的,常用於包裝類,比如Protocol的兩個實現類:ProtocolFilterWrapper、ProtocolListenerWrapper。 3、url匯流排設計 Dubbo為了使得各層解耦,採用了url匯流排的設計。我們通常的設計會把層與層之間的交互參數做成Model,這樣層與層之間溝通成本比較大,擴展起來也比較麻煩。因此,Dubbo把各層之間的通信都採用url的形式。比如,注冊中心啟動時,參數的url為: registry://0.0.0.0:9090?codec=registry&transporter=netty 這就表示當前是注冊中心,綁定到所有ip,埠是9090,解析器類型是registry,使用的底層網路通信框架是netty。
二、Dubbo啟動過程
Dubbo分為注冊中心、服務提供者(provider)、服務消費者(consumer)三個部分。 1、注冊中心啟動過程 注冊中心的啟動過程,主要看兩個類:RegistrySynchronizer、RegistryReceiver,兩個類的初始化方法都是start。 RegistrySynchronizer的start方法:
(1)把所有配置信息load到內存;
(2)把當前注冊中心信息保存到資料庫;
(3)啟動5個定時器。 5個定時器的功能是: (1)AutoRedirectTask,自動重定向定時器。默認1小時運行1次。如果當前注冊中心的連接數高於平均值的1.2倍,則將多出來的連接數重定向到其他注冊中心上,以達到注冊中心集群的連接數均衡。 (2)DirtyCheckTask,臟數據檢查定時器。作用是:分別檢查緩存provider、資料庫provider、緩存consumer、資料庫 consumer的數據,清除臟數據;清理不存活的provider和consumer數據;對於緩存中的存在的provider或consumer而數 據庫不存在,重新注冊和訂閱。 (3)ChangedClearTask,changes變更表的定時清理任務。作用是讀取changes表,清除過期數據。 (4)AlivedCheckTask,注冊中心存活狀態定時檢查,會定時更新registries表的expire欄位,用以判斷注冊中心的存活狀態。如果有新的注冊中心,發送同步消息,將當前所有注冊中心的地址通知到所有客戶端。 (5)ChangedCheckTask,變更檢查定時器。檢查changes表的變更,檢查類型包括:參數覆蓋變更、路由變更、服務消費者變更、權重變
Ⅹ Kitex 源碼解析 —— 將服務注冊進入注冊中心的細節
Kitex為 位元組跳動 內部的 Golang 微服務 RPC 框架,具有 高性能 、 強可擴展 的特點,在位元組內部已廣泛使用。如果對微服務性能有要求,又希望定製擴展融入自己的治理體系,Kitex 會是一個不錯的選擇。
這次我們可以從 官方示例 中的 easy_note 這個demo 開始分析,因為它基本展示了 Kitex 的基本使用方法。
下面圖例為官方在 demo 中展示的架構圖,通過簡單的分析可得, note , user 通過 注冊中心 (Etcd) 進行注冊 , api 通過 注冊中心 來發現 note , user 兩個 rpc 服務, 並進行業務處理。
從 kitex-examples/hello 這個最簡單示例分析,從 cloudwego/kitex 的快速上手可知,這里用了最簡單的 直鏈 來鏈接 server 和 client。而這次的 easy_note 中使用了 注冊中心 來作為 服務之間的 橋梁 (middleware), 為什麼不使用之前的方式而是使用了注冊中心?
我們搜索一下注冊中心的作用,可知: 服務注冊中心的主要作用就是「服務的注冊」和「服務的發現」
我們將服務交給注冊中心管理,雖然可以避免處理復雜的手動管理,我們也許需要還要考慮更多問題,例如:
這次目標之一就是來解析解析服務是如何在服務啟動時進行 注冊 ( Register ) 這個操作的, 這次我們從 easy_note/cmd/user 這個服務開始分析, 因為它是被 注冊 進入 Etcd 的服務之一。
我們從其中的 main.go 開始下手,以下的內容是經過簡化後的文件,是實現配置服務,啟動服務的文件
由注釋可知 WithRegister() 為配置 注冊信息 的函數 ,那為什麼直接就看這個函數呢?
一是因為主要配置注冊的主要邏輯在其中,二是因為 With... 的函數結構都是大同小異,十分相似的。
再然後我們進入 kitex/server/option.go ,先看看 di.Push(fmt.Sprintf("WithRegistry(%T)", r)) 這一行,
這個 *util.Slice 是什麼 ?進去看看?
進入 kitex/pkg/utils/slice.go , 我發現它很簡短。但是它好眼熟,它好像是一個非常常見的數據結構 —— Stack (棧) !
在這個文件之下有它的 slice__test.go 文件 ,看到這里的朋友可以去試驗一下是否這個 Slice 和我的想法是否一致,大家看文章是要思考的嘛!最好可以動動手!
我們再進入 o.Registry = r 這一行,可以得知 Options 用於初始化 server, Option 用於配置 Options (我覺得這種命名方式很巧妙,我感覺基本達到了 見名知意 的作用),裡面東西很多,我們今天只看 Register 部分
到了這里我們可以暫停思考一下,到達這一步是怎麼個過程呢?是通過 main.go/user.NewServer() 的方法進來的。
那 NewServer() 的作用是什麼?是用於配置初始化伺服器的 可選參數 ,
配置完了參數什麼時候生效呢 ( Register 是什麼時候發生的呢) ?其實配置的實現就在 main.go NewServer() 的下一句, Run() !
進入Run方法的實現,可以得知 register 是發生在 server 啟動成功後 的,停止也是會向 Etcd 進行注銷操作的 (大家可以在同文件的 Stop() 中查看)
至此 服務完成了向 Etcd 的注冊,我忽略了許多其他細節,這些細節也很有意思,希望大家可以自己試著探索
這次文章其實向大家分析了如何配置服務,以及向注冊中心進行注冊的方法和時機。
雖然省略了許多細節,但是通過這篇文章可以學到什麼呢?