服務注冊與發現

隨著業務的發展,用戶量日益上升,單一的系統越來越復雜,越來越龐大,單純的提升服務器性能始終有頂天的一天,我們可以通過分布式技術,例如:服務器集群,水平業務劃分,應用分解,系統分流,微服務架構等方式來解決系統性能問題和復雜業務問題。

第一篇:架構演進

在分布式體系下服務注冊與發現將會以核心組件而存在,也將是接下來討論的話題。

Lazy服務注冊與發現(一)

由于應用的分解,微服務的引入,服務越來越多,業務系統與服務系統之間的調用,都需要有效管理。

在服務化的早期,服務不是很多,服務的注冊與發現并不是什么新鮮的名詞,Nginx+內部域名服務器方式,甚至Nginx+host文件配置方式也能完成服務的注冊與發現。

架構圖如下:

(架構一)


各組件角色如下:

Nginx通過多域名配置實現生產者服務路由,通過upstream對生產者提供負載均衡,通過checkhealth對生產者提供健康檢查。

在內部域名服務器/本地host文件配置服務域名的方式,實現域名與服務的綁定。

生產者提供服務給消費者訪問,并通過Nginx來進行請求分發。在服務化的早期,服務較少,訪問量較小,解決了服務的注冊與發現與負載均衡等問題。隨著業務的發展,用戶量日益上升,服務也越來越多,該架構的問題暴露出來:

1)最明顯的問題是所有的請求都需要nginx來轉發,同時隨著訪問量的提升,會成為一個性能瓶頸。

2)隨著內部服務的越來越多,服務上線nginx的配置,內部域名的配置也越來越多,不利于快速部署。

3)一旦內部網絡調整,nginx upstream也需要做對應的配置調整。

Lazy服務注冊與發現(二)

由于所有的請求都需要nginx來轉發,同時隨著訪問量的提升,會成為一個性能瓶頸,為了解決這個瓶頸,引入下面這個架構。

(架構二)


這個架構在nginx的上層加入了LVS,LVS基于第四層的IP轉發,一定限度提高了并發能力,但是所有請求都通過LVS來轉發。

同時nginx分組,服務分組,雖然nginx不再是瓶頸的所在,但是這樣帶來的代價太高,配置越來越多,工作量越來越大,對系統壓力需要有預見性,有效nginx分組,服務分組部署。

Lazy服務注冊與發現(三)

由于所有的請求都需要LVS來轉發,同時隨著訪問量的提升,會成為一個性能瓶頸,為了解決這個瓶頸,引入下面這個架構。

(架構三)


在這個架構基礎上需要做兩件事情:

對系統壓力需要有預見性,有效nginx分組,服務分組部署。

消費端需要編程實現分組選擇,可以是輪訓,random等實現,我們每一個消費者同時承擔了的負載均衡的職責。

ZK服務注冊與發現 (一)

通過架構三解決了nginx的瓶緊,但是服務上下線需要在nginx,域名服務器做相應的配置,一旦服務的IP端口發生變化,都需要在nginx上做相應的配置,為了解決這個問題引入下面這個架構。

(架構四)


服務在啟動的時候就將endpoint注冊到Zookeeper對服務進行統一管理。

服務節點增加Endpoint不需要做任何配置,ZK將以Watch機制通知消費者。

消費者本地緩存了提供者服務列表,不需要轉發,直接發起服務調用。

缺點:

需要通過zookeeper API來實現服務注冊與發現,負載均衡,以及容錯,為了解決nginx的瓶緊架構三也是需要通過編程的方式實現負載均衡。

ZK服務注冊與發現 (二)

Zookeeper數據模型結構是一個樹狀層次結構。每個節點叫做Znode,節點可以擁有子節點,同時允許將少量數據存儲在該節點下,客戶端可以通過NodeCacheListener監聽節點的數據變更,PathChildrenCacheListener監聽子節點變更來實時獲取Znode的變更(Wather機制)。

以下是點融成都服務注冊結構,見下圖,接下來的講解也將以這個結構為基礎:

ZK服務注冊與發現 (三)

1./com/dianrong/cfg/1.0.0/rpcservice: 命名空間,用來跟其他用途區分。

2./com/dianrong/cfg/1.0.0/rpcservice下的所有子目錄由兩部分組成,

“應用名稱” + “-” + ?“分組名稱”例如:ProductService-SG1,ProductService-SG2, 對應Nginx注冊中心Nginx-SG1, Nginx-SG2

3. 服務分組下的所有子節點為臨時節點,key為“PROVIDER”+ IP(去符號.) ?+ “-” + 端口,Value為endpoint信息。

例如:PROVIDER1921681010-8080 ? = http://192.168.10.10:8080

第二篇:代碼分析

項目說明

有了上面的理論我們接下來針對基于ZK的服務與發現的代碼分析,代碼已經提交到git

GIT地址:

https://code.dianrong.com/projects/PLAT/repos/platform/compare/commits?sourceBranch=refs%2Fheads%2FEVER-81-zk&targetBranch=refs%2Fheads%2Fmaster

說明:

1. 該組件建立在Curator基礎之上,Curator是Netflix開源的一套ZooKeeper客戶端框架封裝ZooKeeper client與ZooKeeper server之間的連接處理。

2. Curator提供如下機制,保證我們不需要關注網絡通信,而把主要精力放在業務邏輯的處理。

重試機制:提供可插拔的重試機制, 它將給捕獲所有可恢復的異常配置一個重試策略, 并且內部也提供了幾種標準的重試策略

連接狀態監控: Curator初始化之后會一直的對zk連接進行監聽, 一旦發現連接狀態發生變化, 將作出相應的處理

ZK客戶端實例管理:Curator對zk客戶端到server集群連接進行管理. 并在需要的情況, 透明重建zk實例, 保證與zk集群的可靠連接

基于ZK的服務與發現UML類圖:


目標

1. 統一配置中心

數據實時性,一旦zk節點發生變化,實時通知本地hash同步刷新。

2. 服務注冊

服務啟動完成,服務IP,端口以臨時節點的形式注冊到zk,在網絡正常的情況下,一直存在。

3. 服務發現

服務啟動完成,將服務注冊信息刷新到本地hash。

4. 服務上下線

服務注冊到zk將實時通知服務發現方,更新本地hash,服務下線也將實時通知服務發現方,更新本地hash。

5. 負載均衡

服務發現方獲取服務緩存在本地hash,通過random,robin等負載均衡算法完成服務選擇性調用。

6. 網絡中斷容災

針對注冊方網絡中斷,服務下線,網絡恢復,服務上線,并通知服務發現方更新本地Hash;

針對發現方網絡中斷,通過本地hash負載均衡,網絡恢復重刷hash,負載均衡重新分配。

7. Zookeeper宕機容災

針對注冊方Zookeeper宕機,服務下線,嘗試重連, Zookeeper 啟動重連成功,服務上線,并通知服務發現方更新本地hash,針對發現方Zookeeper宕機,通過本地hash負載均衡,嘗試重連, ?Zookeeper 啟動重連成功,重刷hash,負載均衡重新分配。

基本配置

PathConfig.java

包含服務注冊的命名空間和統一配置的命名空間的配置。

SgConfig.java

包含服務名和分組名的配置

ZookeeperConfig.java

包含zookeeper地址,會話超時時間,連接超時時間,命名空間以及認證信息的配置

ZK操作框架

IzookeeperManager.java

定義了一套zookeeper操作規范(類似JDBC操作數據數據庫規范),有待繼續完善。

ZookeeperManager.java

針對IzookeeperManager接口規范的實現(類似Mysql驅動對Mysql操作的實現)

ZookeeperManagerPool.java

針對ZookeeperManager實例的緩存,不同配置緩存不同ZookeeperManager實例,避免zookeeper連接創建的開銷,同時可以根據zookeeper水平分組擴展zookeeper

實例。

統一配置,服務注冊與發現

AbstractZookeeperFeature.java

內部兩個接口定義:

IConfigService 提供針對統一配置接口的定義, IManagementService提供服務注冊與發現接口的定義。

ConfigService.java

統一配置的實現。

ManagementService.java

服務注冊與發現的實現。

負載均衡

LbStrategy.java

負載均衡策略接口定義,目前實現了兩種負載均衡算法,Random負載均衡和Robin負載均衡。

RandomStrategy.java

基于隨機負載均衡的實現。

RobinStrategy.java

基于輪循負載均衡的實現。

統一配置Listener

ConfigPathChildrenCacheListener.java

統一配置結點監聽, 針對CHILD_REMOVED,CHILD_ADDED,CHILD_UPDATED事件對本地hash實時更新。

服務注冊與發現Listener

ZookeeperStateListener.java

Zookeeper狀態監聽接口定義,定義需要關心的三種事件:

LOST-斷開連接達到一定時間

CONNECTED-第一次連接成功

RECONNECTED-重連成功觸發事件。

ServiceRegistStateListener.java

服務注冊狀態監聽實現:

1.一旦網絡丟包嚴重/ zk宕機/ zk重啟,客戶端將會與zk斷開,服務下線,網絡恢復將觸發reconnected連接,服務重新注冊。

2.一旦zk斷開服務下線,長時間連接不上觸發Lost事件,ServiceRegistStateListener將會嘗試不斷連接直到連上為止,服務重新注冊。

ServiceDiscoverStateListener.java

服務發現狀態監聽實現:

1.一旦網絡丟包嚴重/ zk宕機/ zk重啟,客戶端將會與zk斷開,網絡恢復將觸發reconnected連接,重新獲取服務列表,刷新本地hash。

2.一旦zk斷開服務下線,長時間連接不上觸發Lost事件,ServiceDiscoverStateListener將會嘗試不斷連接直到連上為止,以便刷新本地hash。

ServicePathChildrenCacheListener.java

服務發現結點監聽, 針對CHILD_REMOVED,CHILD_ADDED,CHILD_UPDATED事件消費者對本地hash實時更新,以便及時刷新服務上下線。

第三篇:單元測試

環境準備

1. 在zookeeper中準備結點 com/dianrong/cfg/1.0.0/rpcservice

create /com/dianrong/cfg/1.0.0/rpcservice ?“”

2. 模擬docker/JVM啟動參數設置

由于點融網是通過物理機IP加端口映射到docker實例IP加端口的方式對外提供服務,因此需要通過java -D配置宿主機的IP加端口以便應用程序獲取服務IP加端口用來進行服務注冊。

服務注冊

1. 在服務注冊之前通過zookeeper控制臺查看,期望的注冊節點目前為空。

2. 啟動服務注冊單元測試,sleep 10s。

3. 觀察zookeeper中注冊結點,期望的臨時節點已經存在。

服務發現

通過服務注冊以后然后服務發現,發現節點與預期注冊結點一致。

負載均衡

通過服務注冊以后然后負載均衡,負載均衡獲取節點與預期節點一致。

服務上下線

1. 準備兩套單元測試并模擬docker/JVM啟動參數設置。

2.啟動服務注冊,服務發現,負載均衡集成單元測試,測試控制臺按照預期打印出服務注冊信息,注冊了兩個服務節點通過負載均衡交叉出現。

3. 通過zookeeper控制臺查看成功注冊兩個節點。

4.關閉機器1單元測試,結點1在zookeeper離線,結點2在zookeeper在線。

負載均衡只有一個節點存在達到預期目的

網絡中斷

1. 啟動服務注冊,服務發現,負載均衡集成單元測試,測試控制臺按照預期打印出服務注冊信息。

2. 通過Linux iptables開啟防火墻,模擬網絡中斷。

服務掉線,通過zookeeper控制臺查看注冊節點消失。

服務發現方任然可以從本地Hash中獲取服務節點。

3. 關閉Iptables,服務上線。

服務上線,通過zookeeper控制臺查看注冊節點出現。

服務發現方獲取服務節點重刷本地Hash中。

Zookeeper宕機

1. 啟動服務注冊,服務發現,負載均衡集成單元測試,測試控制臺按照預期打印出服務注冊信息。

2.關閉zookeeper,單元測試出現連接拒絕錯誤,但是任然能按照預期獲取本地hash中的服務注冊信息。

3. 啟動zookeeper,單元測試打印出重連信息,并重刷本地服務hash。

本文作者:秦瑜 Chris.Qin(點融黑幫),來自點融BE Team, 2015年10月加入點融,多年大并發分布式互聯網架構經驗。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容