http://www.techweb.com.cn/network/hardware/2015-12-25/2246973.shtml
背景
大多數系統都是從一個單一系統開始起步的,隨着公司業務的快速發展,這個單一系統變得越來越龐大,帶來幾個問題:
1. 隨着訪問量的不斷攀升,純粹通過提升機器的性能來已經不能解決問題,系統無法進行有效的水平擴展
2. 維護這個單一系統,變得越來越復雜
3. 同時,隨着業務場景的不同以及大研發的招兵買馬帶來了不同技術背景的工程師,在原有達達Python技術棧的基礎上,引入了Java技術棧。
如何來解決這些問題?業務服務化是個有效的手段來解決大規模系統的性能瓶頸和復雜性。通過系統拆分將原有的單一龐大系統拆分成小系統,它帶來了如下好處:
1. 原來系統的壓力得到很好的分流,有效地解決了原先系統的瓶頸,同時帶來了更好的擴展性
2. 獨立的代碼庫,更少的業務邏輯,系統的維護性得到極大的增強
同時,也帶來了一系列問題:
▪ 隨着系統服務的越來越多,如何來管理這些服務?
▪ 如何分發請求到提供同一服務的多台主機上(負載均衡如何來做)
▪ 如果提供服務的Endpoint發生變化,如何將這些信息通知服務的調用方?
最初的解決方案
Linkedin的創始人里德霍夫曼曾經說過:
成立一家初創公司就像把自己從懸崖上扔下來,在降落過程中去組裝一架飛機。
這對於初創公司達達也是一樣的,業務在以火箭般的速度發展着。技術在業務發展中作用就是保障業務的穩定運行,快速地“組裝一架飛機”。所以,在業務服務化的早期,我們采用了Nginx+本地hosts文件的方式進行內部服務的注冊與發現,架構圖如下:
各系統組件角色如下:
1. 服務消費者通過本地hosts中的服務提供者域名與Nginx的IP綁定信息來調用服務
2. Nginx用來對服務提供者提供的服務進行存活檢查和負載均衡
3. 服務提供者提供服務給服務消費者訪問,並通過Nginx來進行請求分發
這在內部系統比較少,訪問量比較小的情況下,解決了服務的注冊,發現與負載均衡等問題。但是,隨着內部服務越來愈多,訪問量越來越大的情況下,該架構的隱患逐漸暴露出來:
▪ 最明顯的問題是Nginx存在單點故障(SPOF),同時隨着訪問量的提升,會成為一個性能瓶頸
▪ 隨着內部服務的越來越多,不同的服務消費方需要配置不同的hosts,很容易在增加新的主機時忘記配置hosts導致服務不能調用問題,增加了運維負擔
▪ 服務的配置信息分散在各個主機hosts中,難以保持一致性,不便於服務的管理
▪ 服務主機的發布和下線需要手工的修改Nginx upstream配置,修改的配置需要上線,不利於服務的快速部署
如何來解決
在談如何來解決之前,現梳理一下服務注冊與發現的目標:
▪ 服務的注冊信息應該統一保存,方便於服務的管理
▪ 自動通過服務的名稱去發現服務,而不必了解這個服務提供的end-point到底是哪台主機
▪ 支持服務的負載均衡及fail-over
▪ 增加或移除某個服務的end-point時,對於服務的消費者來說是透明的
▪ 支持Python和Java
備選方案一: DNS
DNS作為服務注冊發現的一種方案,它比較簡單。只要在DNS服務上,配置一個DNS名稱與IP對應關系即可。定位一個服務只需要連接到DNS服務器上,隨機返回一個IP地址即可。由於存在DNS緩存,所以DNS服務器本身不會成為一個瓶頸。
這種基於Pull的方式不能及時獲取服務的狀態的更新(例如:服務的IP更新等)。如果服務的提供者出現故障,由於DNS緩存的存在,服務的調用方會仍然將請求轉發給出現故障的服務提供方;反之亦然。
備選方案二:Dubbo
Dubbo是阿里巴巴推出的分布式服務框架,致力於解決服務的注冊與發現,編排,治理。它的優點如下:
1. 功能全面,易於擴展
2. 支持各種串行化協議(JSON,Hession,java串行化等)
3. 支持各種RPC協議(HTTP,Java RMI,Dubbo自身的RPC協議等)
4. 支持多種負載均衡算法
5. 其他高級特性:服務編排,服務治理,服務監控等
缺點如下:
1. 只支持Java,對於Python沒有相應的支持
2. 雖然已經開源,但是沒有成熟的社區來運營和維護,未來升級可能是個麻煩
3. 重量級的解決方案帶來新的復雜性
備選方案三:Zookeeper
Zookeeper是什么?按照Apache官網的描述是:
ZooKeeper is a centralized service for maintaining configuration information, naming, providing distributed synchronization, and providing group services.
參照官網的定義,它能夠做:
1. 作為配置信息的存儲的中心服務器
2. 命名服務
3. 分布式的協調
4. Mater選舉等
在定義中特別提到了命名服務。在調研之后,Zookeeper作為服務注冊與發現的解決方案,它有如下優點:
1. 它提供的簡單API
2. 已有互聯網公司(例如:Pinterest,Airbnb)使用它來進行服務注冊與發現
3. 支持多語言的客戶端
4. 通過Watcher機制實現Push模型,服務注冊信息的變更能夠及時通知服務消費方
缺點是:
1. 引入新的Zookeeper組件,帶來新的復雜性和運維問題
2. 需自己通過它提供的API來實現服務注冊與發現邏輯(包含Python與Java版本)
我們對上述幾個方案的優缺點權衡之后,決定采用了基於Zookeeper實現自己的服務注冊與發現。
基於Zookeeper的服務注冊與發現架構
在此架構中有三類角色:服務提供者,服務注冊中心,服務消費者。
服務提供者
服務提供者作為服務的提供方將自身的服務信息注冊到服務注冊中心中。服務信息包含:
▪ 隸屬於哪個系統
▪ 服務的IP,端口
▪ 服務的請求URL
▪ 服務的權重等等
服務注冊中心
服務注冊中心主要提供所有服務注冊信息的中心存儲,同時負責將服務注冊信息的更新通知實時的Push給服務消費者(主要是通過Zookeeper的Watcher機制來實現的)。
服務消費者
服務消費者主要職責如下:
1. 服務消費者在啟動時從服務注冊中心獲取需要的服務注冊信息
2. 將服務注冊信息緩存在本地
3. 監聽服務注冊信息的變更,如接收到服務注冊中心的服務變更通知,則在本地緩存中更新服務的注冊信息
4. 根據本地緩存中的服務注冊信息構建服務調用請求,並根據負載均衡策略(隨機負載均衡,Round-Robin負載均衡等)來轉發請求
5. 對服務提供方的存活進行檢測,如果出現服務不可用的服務提供方,將從本地緩存中剔除
服務消費者只在自己初始化以及服務變更時會依賴服務注冊中心,在此階段的單點故障通過Zookeeper集群來進行保障。在整個服務調用過程中,服務消費者不依賴於任何第三方服務。
實現機制介紹
Zookeeper數據模型介紹
在整個服務注冊與發現的設計中,最重要是如何來存儲服務的注冊信息。
在設計基於Zookeeper的服務注冊結構之前,我們先來看一下Zookeeper的數據模型。Zookeeper的數據模型如下圖所示:
Zookeeper數據模型結構與Unix文件系統很類似,是一個樹狀層次結構。每個節點叫做Znode,節點可以擁有子節點,同時允許將少量數據存儲在該節點下。客戶端可以通過監聽節點的數據變更和子節點變更來實時獲取Znode的變更(Wather機制)。
服務注冊結構
服務注冊結構如上圖所示。
▪ /dada來標示公司名稱dada,同時能方便與其它應用的目錄區分開(例如:Kafka的brokers注冊信息放置在/brokers下)
▪ /dada/services將所有服務提供者都放置該目錄下
▪ /dada/services/category1目錄定義具體的服務提供者的id:category1,同時該Znode節點中允許存放該服務提供者的一些元數據信息,例如:名稱,服務提供者的Owner,上下文路徑(Java Web項目),健康檢查路徑等。該信息可以根據實際需要進行自由擴展。
▪ /dada/services/category1/helloworld節點定義了服務提供者category1下的一個服務:helloworld。其中helloworld為該服務的ID,同時允許將該服務的元數據信息存儲在該Znode下,例如圖中標示的:服務名稱,服務描述,服務路徑,服務的調用的schema,服務的調用的HTTP METHOD等。該信息可以根據實際需要進行自由擴展。
▪ /dada/services/category1/helloworld/providers節點定義了服務提供者的父節點。在這里其實可以將服務提供者的IP和端口直接放置在helloworld節點下,在這里單獨放一個節點,是為了將來可以將服務消費者的消息掛載在helloworld節點下,進行一些擴展,例如命名為:/dada/services/category1/helloworld/consumers。
▪ /dada/services/category__1/helloworld/providers/192.168.1.1:8080該節點定義了服務提供者的IP和端口,同時在節點中定義了該服務提供者的權重。
實現機制
由於目前服務注冊通過我們的服務注冊中心UI來進行注冊,這部分邏輯比較簡單,即通過UI界面來構造上述定義的服務注冊結構。
下面着重介紹一下我們的服務發現是如何工作的:
在上述類圖中,類ServiceDiscovery主要通過Zookeeper的API(Python/Java版本)來獲取服務信息,同時對服務注冊結構中的每個服務的providers節點增加Watcher,來監控節點變化。獲取的服務注冊信息保存在變量service_repos中。通過在初始化時設置LoadBalanceStrategy的實現(Round-Robin算法,Radmon算法)來實現服務提供者的負載均衡。主要方法:
1. init獲取Zookeeper的服務注冊信息,並緩存在service_repos
2. get_service_repos方法獲取實例變量service_repos
3. get_service_endpoint根據init構建好的service_repos,以及lb_strategy提供的負載均衡策略返回某個服務的URL地址
4. update_service_repos通過Zookeeper的Watcher機制來實時更新本地緩存service_repos
5. heartbeat_monitor是一個心跳檢測線程,用來進行服務提供者的健康存活檢測,如果出現問題,將該服務提供者從該服務的提供者列表中移除;反之,則加入到服務的提供者列表中
LoadBalanceStrategy定義了根據服務提供者的信息返回對應的服務Host和IP,即決定由那台主機+端口來提供服務。
RoundRobinStrategy和RandomStrategy分別實現了Round-Robin和隨機的負載均衡算法
未來展望
目前達達基於Zookeeper的服務注冊與發現的架構還處於初期,很多功能還未完善,例如:服務的路由功能,與部署平台的集成,服務的監控等等。
當然基於Zookeeper還能做其它許多事情,例如:實時動態配置系統。目前,我們已經基於Zookeeper實現了實時動態配置系統。如想了解,請繼續關注我們的blog。