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