天天看點

分布式系統解決之道:目錄、消息隊列、事務系統及其他

目錄服務(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來作為日志存儲系統,上面再搭建自己的統計程式。

日志服務是分布式運維的儀表盤、潛望鏡。如果沒有一個可靠的日志服務,整個系統的運作狀況可能會是失控的。是以無論你的分布式系統節點是多還是少,必須花費重要的精力和專門的開發時間,去建立一個對日志進行自動化統計分析的系統。

分布式系統解決之道:目錄、消息隊列、事務系統及其他

 原文釋出時間為:2016-11-30

本文來自雲栖社群合作夥伴dbaplus