【ZStack】2.ZStack的伸縮性秘密武器:無狀態服務

每個ZStack服務都是無狀態的,讓服務高可用以及橫向拓展(scale out)可以很簡單,只需要啟動剩余的服務實例,然后進行負載均衡即可。此外,ZStack將所有的服務打包到名為管理節點(management node)的單個進程,它讓部署和管理變得超級簡單。

動機

ZStack的伸縮性秘密武器——異步架構(ZStack's Scalability Secrets Part 1: Asynchronous Architecture) 一文中, 我們已經詳細解釋了異步架構,它讓單個ZStack管理節點能勝任大多數的云端工作負載。然而,當用戶希望建立高可用的生產環境,或者處理超級大的并發工作負載的時候,一個管理節點是不夠的。解決方案是,構建一個分布式的系統,這樣工作負載可以延展到每一個單一管理節點。這種增加新節點來拓展整個系統的容量的方式稱為 橫向拓展(scale out).

問題

設計一個分布式的系統并不容易。一個分布式的系統,特別是一個有狀態的系統,必須處理一致性,可用性,以及分區容忍性(請查看 CAP理論(CAP theorem)),所有這些都很復雜。相反,一個無狀態的分布式系統,在某種程度上擺脫了這種復雜性。首先,因為在節點之間無需狀態共享,系統自然保持了一致性;其次,由于節點之間是類似的,當系統遇到一個分區問題通常也是OK的。鑒于此,一個分布式的系統,通常更傾向于保持無狀態而不是有狀態。但是,設計一個無狀態的分布式系統也是很困難的,同時,常常比設計有狀態的分布式系統更加困難。提升了消息總線(message bus)和數據庫優勢的ZStack,構建了一個包含了無狀態服務的無狀態分布式系統。

由于無狀態服務是保證整個系統無狀態的根基,在討論它是什么之前,讓我們先了解下什么是“狀態”。在ZStack里面,資源,如主機,虛擬機,鏡像,以及用戶,都是由單個服務管理的;當系統中存在多余一個服務實例的時候,資源會被劃分為不同的實例。例如,假如有10,000個虛擬機和兩個虛擬機服務實例,理想的情況下,每個實例將會管理5000個虛擬機:

由于存在兩個服務實例,在向虛擬機發送請求之前,請求者必須知道哪一個實例正在管理虛擬機;否則,它將無法知道將請求發往何處。像 ”哪個服務實例正在管理什么資源“ 的認知,正是我們正在談論的狀態。如果服務是有狀態的,狀態也就顯現在服務之中。請求者需要在某個地方咨詢這些狀態。當服務實例的數目發生變化的時候,服務需要交換狀態,例如,當一個新的服務實例加入,或者當前的服務實例脫離的時候。

狀態交換是讓人擔憂的,它很容易導致錯誤,常常會限制系統的可拓展性。為了讓系統更可靠,同時更易于橫向拓展,理想的方式是,通過彼此分隔狀態來讓服務保持無狀態(查看 服務無狀態原則(Service Statelessness Principle)。 有了無狀態的服務,請求者不再需要詢問何處發送請求;當新的服務實例加入,或者舊的服務實例脫離的時候,服務也不再需要交換狀態。

注意:在接下來的內容中,為了簡單起見,術語“服務”和“服務實例”交換著使用。

服務和管理節點

服務,通過中央消息總線(central message bus)--RabbitMQ,來彼此通訊,它們是ZStack中的“第一等公民”。

不像通常的微服務架構,其每個服務都在單獨的進程或單獨的機器上運行,ZStack將所有的服務打包到一個名為管理節點的單一進程。對于這個號稱 進程中的微服務(in-process microservices)架構,我們有充分的理由,你可以參看進程中的微服務架構(The In-Process Microservices Architecture)。

一個管理節點是一個完整功能的ZStack軟件。由于包含了無狀態服務,管理節點沒有共享狀態,但是有心跳記錄,以及一致性哈希算法環(consistent hashing ring)--接下來我們將詳細介紹。 心跳用來監控管理節點的“健康”(譯者注:即此管理節點是否存活,是否正常運轉),只要一個管理節點在給定的間隔內停止更新心跳,其它的管理節點將會驅除它,同時開始接管它所管理的資源。

無狀態服務

實現無狀態服務的核心技術,特別是對于ZStack的業務邏輯,就是一致性哈希算法(consistent hashing algorithm)。在啟動的時候,每個管理節點都會被分配一個 版本4UUID(version 4 UUID)(管理節點UUID),它會和服務名一起,在消息總線上注冊一個服務隊列。例如,管理節點可能注冊如下所示的服務隊列:

zstack.message.ansible.3694776ab31a45709259254a018913ca
zstack.message.api.portal       
zstack.message.applianceVm.3694776ab31a45709259254a018913ca     
zstack.message.cloudbus.3694776ab31a45709259254a018913ca        
zstack.message.cluster.3694776ab31a45709259254a018913ca
zstack.message.configuration.3694776ab31a45709259254a018913ca   
zstack.message.console.3694776ab31a45709259254a018913ca
zstack.message.eip.3694776ab31a45709259254a018913ca     
zstack.message.globalConfig.3694776ab31a45709259254a018913ca    
zstack.message.host.3694776ab31a45709259254a018913ca    
zstack.message.host.allocator.3694776ab31a45709259254a018913ca  
zstack.message.identity.3694776ab31a45709259254a018913ca        
zstack.message.image.3694776ab31a45709259254a018913ca   
zstack.message.managementNode.3694776ab31a45709259254a018913ca  
zstack.message.network.l2.3694776ab31a45709259254a018913ca      
zstack.message.network.l2.vlan.3694776ab31a45709259254a018913ca
zstack.message.network.l3.3694776ab31a45709259254a018913ca      
zstack.message.network.service.3694776ab31a45709259254a018913ca
zstack.message.portForwarding.3694776ab31a45709259254a018913ca  
zstack.message.query.3694776ab31a45709259254a018913ca   
zstack.message.securityGroup.3694776ab31a45709259254a018913ca   
zstack.message.snapshot.volume.3694776ab31a45709259254a018913ca
zstack.message.storage.backup.3694776ab31a45709259254a018913ca

說明:你應該注意到,所有隊列都以同樣的UUID結尾,那是管理節點的UUID。

資源,如主機,容量,虛擬機,也是通過UUID來標識的。消息,常常和資源相關聯,是在服務間傳遞的。在發送消息之前,發送者必須選擇基于資源的UUID的接收者服務,這時,一致性哈希算法就開始登場了。

一致性哈希(Consistent hashing)是一種特別的哈希,當哈希表調整大小的時候,就會用到一致性哈希,其中只有一部分鍵(key)需要重新映射。關于一致性哈希的更多內容,更詳細的請參閱 這里。在ZStack之中,管理節點由一致性哈希環組成,如下所示:

每個管理節點都維護一份一致性哈希環的拷貝,這個環包含了系統中所有管理節點的UUID。當管理節點加入或者脫離的時候,生命周期事件(lifecycle event)就會通過消息總線廣播到其它節點,這樣使得這些節點擴展或者收縮環,以呈現當前系統的狀態。當發送消息的時候,發送者服務將使用資源的UUID,通過哈希的方式得出目標管理節點的UUID。例如,發送VM的UUID為932763162d054c04adaab6ab498c9139的StartVmInstanceMsg,偽代碼如下:

msg = new StartVmInstanceMsg(); destinationManagementNodeUUID = consistent_hashing_algorithm("932763162d054c04adaab6ab498c9139"); msg.setServiceId("vmInstance." + destinationManagementNodeUUID); cloudBus.send(msg)

如果有一個穩定的環,那么包含同樣資源UUID的消息就總是會路由到某個管理節點上同樣的服務,這就是ZStack無鎖架構的基礎(參閱 ZStack的伸縮性秘密(第三部分):無鎖架構(Stack's Scalability Secrets Part 3: Lock-free Architecture)。

6.png

當一致性哈希環收縮或釋放的時候,由于一致性哈希的特性,只有少數節點受到輕微影響。

由于一致性哈希環,發送者無需知道哪一個服務實例即將處理消息;取而代之的是,這將會被處理掉。服務無需維護和交換,關于它們正在管理什么資源的信息;它們所需要做的就是,處理即將到來的消息,因為環能夠保證消息找到正確的服務實例。這就是服務如何變得超級簡單和保持無狀態的。

除包含資源UUID的消息之外(如 StartVmInstanceMsg, DownloadImageMsg),也有一類無資源UUID的消息,通常是創建型的消息(如 CreateVolumeMsg)和非資源消息(如 AllocateHostMsg)--它們不會操控單獨的資源。考慮到這些消息可以發送到任意管理節點的服務,它們可能被故意發送到本地的管理節點,由于發送者和接收者在同樣的節點,當發送者發送消息的時候,接收者當然也是可達的。

對 API 消息(例如:APIStartVmInstanceMsg)來說,有一個特殊的處理,它們總是發送一個眾所周知的服務 ID api.portal 。在消息總線上,一個全局的隊列被叫做 zstack.message.api.portal ,它被所有的管理節點 API 服務所共享,消息服務 ID api.portal 將會自動對其中的一個API服務做負載均衡,這個服務還會路由轉發消息到正確的目的地,并使用了一致性哈希環(consistent hashing ring)。通過這種做法,ZStack 隱藏了來自 API 客戶端消息路由轉發的細節,并簡化了寫一個ZStack API 客戶端的工作。

msg = new APICreateVmInstanceMsg()
msg.setServiceId("api.portal")
cloudBus.send(msg)

摘要

在這篇文章中,我們證明了Zstack 構建伸縮性的分布式系統。因為管理節點共享的信息比較少,很容易建立一個大的集群,可能有幾十個甚至幾百個管理節點。然而實際上,在私有云方面,兩個管理節點可以有很好的擴展性;在公共云方面,管理員能根據工作量創建一個管理節點。依靠異步架構和無狀態的服務,Zstack能夠處理大量的并發任務,現有的IaaS軟件則不能處理。

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

推薦閱讀更多精彩內容