分布式係統解決之道:目錄、消息隊列、事務係統及其他
目錄服務(ZooKeeper)
分布式係統是一個由很多進程組成的整體,這個整體中每個成員部分,都會具備一些狀態,比如自己的負責模塊,自己的負載情況,對某些數據的掌握等等。而這些和其他進程相關的數據,在故障恢複、擴容縮容的時候變得非常重要。
簡單的分布式係統,可以通過靜態的配置文件,來記錄這些數據:進程之間的連接對應關係,它們的IP地址和端口等等。然而,一個自動化程度高的分布式係統,必然要求這些狀態數據都是動態保存的。這樣才能讓程序自己去做容災和負載均衡的工作。
一些程序員會專門自己編寫一個DIR服務(目錄服務),來記錄集群中進程的運行狀態。集群中進程會和這個DIR服務產生自動關聯,這樣在容災、擴容、負載均衡的時候,就可以自動根據這些DIR服務裏的數據,來調整請求的發送目地,從而達到繞開故障機器、或連接到新的服務器的操作。
然而,如果我們隻是用一個進程來充當這個工作,那麼這個進程就成為了這個集群的“單點”——意思就是,如果這個進程故障了,那麼整個集群可能都無法運行的。所以存放集群狀態的目錄服務,也需要是分布式的。幸好我們有ZooKeeper這個優秀的開源軟件,它正是一個分布式的目錄服務區。
ZooKeeper可以簡單啟動奇數個進程,來形成一個小的目錄服務集群。這個集群會提供給所有其他進程,進行讀寫其巨大的“配置樹”的能力。這些數據不僅僅會存放在一個Zookeeper進程中,而是會根據一套非常安全的算法,讓多個進程來承載。這讓Zookeeper成為一個優秀的分布式數據保存係統。
由於Zookeeper的數據存儲結構,是一個類似文件目錄的樹狀係統,所以我們常常會利用它的功能,把每個進程都綁定到其中一個“分枝”上,然後通過檢查這些“分支”,來進行服務器請求的轉發,就能簡單的解決請求路由(由誰去做)的問題。另外還可以在這些“分支”上標記進程的負載的狀態,這樣負載均衡也很容易做了。
目錄服務是分布式係統中最關鍵的組件之一。而ZooKeeper是一個很好的開源軟件,正好是用來完成這個任務。
消息隊列服務(ActiveMQ、ZeroMQ、Jgroups)
兩個進程間如果要跨機器通訊,我們幾乎都會用TCP/UDP這些協議。但是直接使用網絡API去編寫跨進程通訊,是一件非常麻煩的事情。除了要編寫大量的底層socket代碼外,我們還要處理諸如:如何找到要交互數據的進程,如何保障數據包的完整性不至於丟失,如果通訊的對方進程掛掉了,或者進程需要重啟應該怎樣等等這一係列問題。這些問題包含了容災擴容、負載均衡等一係列的需求。
為了解決分布式係統進程間通訊的問題,人們總結出了一個有效的模型,就是“消息隊列”模型。消息隊列模型,就是把進程間的交互,抽象成對一個個消息的處理,而對於這些消息,我們都有一些“隊列”,也就是管道,來對消息進行暫存。每個進程都可以訪問一個或者多個隊列,從裏麵讀取消息(消費)或寫入消息(生產)。由於有一個緩存的管道,我們可以放心地對進程狀態進行變化。當進程起來的時候,它會自動去消費消息就可以了。而消息本身的路由,也是由存放的隊列決定的,這樣就把複雜的路由問題,變成了如何管理靜態的隊列的問題。
一般的消息隊列服務,都是提供簡單的“投遞”和“收取”兩個接口,但是消息隊列本身的管理方式卻比較複雜,一般來說有兩種。一部分的消息隊列服務,提倡點對點的隊列管理方式:每對通信節點之間,都有一個單獨的消息隊列。這種做法的好處是不同來源的消息,可以互不影響,不會因為某個隊列的消息過多,擠占了其他隊列的消息緩存空間。而且處理消息的程序也可以自己來定義處理的優先級——先收取、多處理某個隊列,而少處理另外一些隊列。
但是這種點對點的消息隊列,會隨著集群的增長而增加大量的隊列,這對於內存占用和運維管理都是一個複雜的事情。因此更高級的消息隊列服務,開始可以讓不同的隊列共享內存空間,而消息隊列的地址信息、建立和刪除,都采用自動化的手段。——這些自動化往往需要依賴上文所述的“目錄服務”,來登記隊列的ID對應的物理IP和端口等信息。比如很多開發者使用ZooKeeper來充當消息隊列服務的中央節點;而類似Jgropus這類軟件,則自己維護一個集群狀態來存放各節點今昔。
另外一種消息隊列,則類似一個公共的郵箱。一個消息隊列服務就是一個進程,任何使用者都可以投遞或收取這個進程中的消息。這樣對於消息隊列的使用更簡便,運維管理也比較方便。不過這種用法下,任何一個消息從發出到處理,最少進過兩次進程間通信,其延遲是相對比較高的。並且由於沒有預定的投遞、收取約束,所以也比較容易出BUG。
不管使用那種消息隊列服務,在一個分布式服務器端係統中,進程間通訊都是必須要解決的問題,所以作為服務器端程序員,在編寫分布式係統代碼的時候,使用的最多的就是基於消息隊列驅動的代碼,這也直接導致了EJB3.0把“消息驅動的Bean”加入到規範之中。
事務係統
在分布式的係統中,事務是最難解決的技術問題之一。由於一個處理可能分布在不同的處理進程上,任何一個進程都可能出現故障,而這個故障問題則需要導致一次回滾。這種回滾大部分又涉及多個其他的進程。這是一個擴散性的多進程通訊問題。要在分布式係統上解決事務問題,必須具備兩個核心工具:一個是穩定的狀態存儲係統;另外一個是方便可靠的廣播係統。
事務中任何一步的狀態,都必須在整個集群中可見,並且還要有容災的能力。這個需求,一般還是由集群的“目錄服務”來承擔。如果我們的目錄服務足夠健壯,那麼我們可以把每步事務的處理狀態,都同步寫到目錄服務上去。Zookeeper再次在這個地方能發揮重要的作用。
如果事務發生了中斷,需要回滾,那麼這個過程會涉及到多個已經執行過的步驟。也許這個回滾隻需要在入口處回滾即可(加入那裏有保存回滾所需的數據),也可能需要在各個處理節點上回滾。如果是後者,那麼就需要集群中出現異常的節點,向其他所有相關的節點廣播一個“回滾!事務ID是XXXX”這樣的消息。這個廣播的底層一般會由消息隊列服務來承載,而類似Jgroups這樣的軟件,直接提供了廣播服務。
雖然現在我們在討論事務係統,但實際上分布式係統經常所需的“分布式鎖”功能,也是這個係統可以同時完成的。所謂的“分布式鎖”,也就是一種能讓各個節點先檢查後執行的限製條件。如果我們有高效而單子操作的目錄服務,那麼這個鎖狀態實際上就是一種“單步事務”的狀態記錄,而回滾操作則默認是“暫停操作,稍後再試”。這種“鎖”的方式,比事務的處理更簡單,因此可靠性更高,所以現在越來越多的開發人員,願意使用這種“鎖”服務,而不是去實現一個“事務係統”。
自動部署工具(Docker)
由於分布式係統最大的需求,是在運行時(有可能需要中斷服務)來進行服務容量的變更:擴容或者縮容。而在分布式係統中某些節點故障的時候,也需要新的節點來恢複工作。這些如果還是像老式的服務器管理方式,通過填表、申報、進機房、裝服務器、部署軟件……這一套做法,那效率肯定是不行。
在分布式係統的環境下,我們一般都是采用“池”的方式來管理服務。我們預先會申請一批機器,然後在某些機器上運行服務軟件,另外一些則作為備份。顯然我們這一批服務器不可能隻為某一個業務服務,而是會提供多個不同的業務承載。那些備份的服務器,則會成為多個業務的通用備份“池”。隨著業務需求的變化,一些服務器可能“退出”A服務而“加入”B服務。
這種頻繁的服務變化,依賴高度自動的軟件部署工具。我們的運維人員,應該掌握這開發人員提供的部署工具,而不是厚厚的手冊,來進行這類運維操作。一些比較有經驗的開發團隊,會統一所有的業務底層框架,以期大部分的部署、配置工具,都能用一套通用的係統來進行管理。而開源界,也有類似的嚐試,最廣為人知的莫過於RPM安裝包格式,然而RPM的打包方式還是太複雜,不太符合服務器端程序的部署需求。所以後來又出現了Chef為代表的,可編程的通用部署係統。
在虛擬機技術出現之後,PaaS平台為自動部署提供了強大的支持:如果我們是按某個PaaS平台的規範來編寫的應用,可以完全把程序丟給平台去部署,其承載量計算、部署規劃,都自動完成了。這方麵的佼佼者是Google的AppEngine:我們可以直接用Eclipse開發一個本地的Web應用,然後上傳到AppEngine裏麵,所有的部署就完成了!AppEngine會自動的根據對這個Web應用的訪問量,來進行擴容、縮容、故障恢複。
然而,真正有革命性的工具,是Docker的出現。雖然虛擬機、沙箱技術早就不是什麼新技術,但是真正使用這些技術來作為部署工具的時間卻不長。Linux高效的輕量級容器技術,提供了部署上巨大的便利性——我們可以在各種庫、各種協作軟件的環境下打包我們的應用程序,然後隨意的部署在任何一個Linux係統上。
為了管理大量的分布式服務器端進程,我們確實需要花很多功夫,其優化其部署管理的工作。統一服務器端進程的運行規範,是實現自動化部署管理的基本條件。我們可以根據“操作係統”作為規範,采用Docker技術;也可以根據“Web應用”作為規範,采用某些PaaS平台技術;或者自己定義一些更具體的規範,自己開發完整的分布式計算平台。
日誌服務(log4j)
服務器端的日誌,一直是一個既重要又容易被忽視的問題。很多團隊在剛開始的時候,僅僅把日誌視為開發調試、排除BUG的輔助工具。但是很快會發現,在服務運營起來之後,日誌幾乎是服務器端係統,在運行時可以用來了解程序情況的唯一有效手段。
盡管我們有各種profile工具,但是這些工具大部分都不適合在正式運營的服務上開啟,因為會嚴重降低其運行性能。所以我們更多的時候需要根據日誌來分析。盡管日誌從本質上,就是一行行的文本信息,但是由於其具有很大的靈活性,所以會很受開發和運維人員的重視。
日誌本身從概念上,是一個很模煳的東西。你可以隨便打開一個文件,然後寫入一些信息。但是現代的服務器係統,一般都會對日誌做一些標準化的需求規範:日誌必須是一行一行的,這樣比較方便日後的統計分析;每行日誌文本,都應該有一些統一的頭部,比如日期時間就是基本的需求;日誌的輸出應該是分等級的,比如fatal/error/warning/info/debug/trace等等,程序可以在運行時調整輸出的等級,以便可以節省日誌打印的消耗;日誌的頭部一般還需要一些類似用戶ID或者IP地址之類的頭信息,用於快速查找定位過濾某一批日誌記錄,或者有一些其他的用於過濾縮小日誌查看範圍的字段,這叫做染色功能;日誌文件還需要有“回滾”功能,也就是保持固定大小的多個文件,避免長期運行後,把硬盤寫滿。
由於有上述的各種需求,所以開源界提供了很多遊戲的日誌組件庫,比如大名鼎鼎的log4j,以及成員眾多的log4X家族庫,這些都是應用廣泛而飽受好評的工具。
不過對比日誌的打印功能,日誌的搜集和統計功能卻往往比較容易被忽視。作為分布式係統的程序員,肯定是希望能從一個集中節點,能搜集統計到整個集群日誌情況。而有一些日誌的統計結果,甚至希望能在很短時間內反複獲取,用來監控整個集群的健康情況。要做到這一點,就必須有一個分布式的文件係統,用來存放源源不斷到達的日誌(這些日誌往往通過UDP協議發送過來)。而在這個文件係統上,則需要有一個類似Map Reduce架構的統計係統,這樣才能對海量的日誌信息,進行快速的統計以及報警。有一些開發者會直接使用Hadoop係統,有一些則用Kafka來作為日誌存儲係統,上麵再搭建自己的統計程序。
日誌服務是分布式運維的儀表盤、潛望鏡。如果沒有一個可靠的日誌服務,整個係統的運行狀況可能會是失控的。所以無論你的分布式係統節點是多還是少,必須花費重要的精力和專門的開發時間,去建立一個對日誌進行自動化統計分析的係統。
本文來自雲棲社區合作夥伴DBAplus
最後更新:2017-05-11 14:01:28