隨著業務的發展,用戶量日益上升,單一的系統越來越復雜,越來越龐大,單純的提升服務器性能始終有頂天的一天,我們可以通過分布式技術,例如:服務器集群,水平業務劃分,應用分解,系統分流,微服務架構等方式來解決系統性能問題和復雜業務問題。
第一篇:架構演進
在分布式體系下服務注冊與發現將會以核心組件而存在,也將是接下來討論的話題。
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月加入點融,多年大并發分布式互聯網架構經驗。