把Zookeeper說清楚
一、ZooKeeper的由來
ZooKeeper最早起源于雅虎研究院的一個研究小組。當時,研究人員發現,在雅虎內部很多大型系統基本都需要依賴一個類似的系統來進行分布式協調,但是這些系統往往都存在分布式單點問題。
什么是分布式單點問題呢?通常分布式系統采用主從模式,一個主機連接多個處理節點,主節點負責分發任務,而子節點負責處理業務,當主節點發生故障時,會導致整個系統發故障,我們把這種故障叫做單點故障,也就是分布式單點問題。
所以,雅虎的開發人員就試圖開發一個通用的無單點問題的分布式協調框架,以便讓開發人員將精力集中在處理業務邏輯上。
關于“ZooKeeper”這個項目的名字,其實也有一段趣聞。在立項初期,考慮到之前內部很多項目都是使用動物的名字來命名的(例如著名的Pig項目),雅虎的工程師希望給這個項目也取一個動物的名字。(列舉hadoop生態中的項目動物圖標)
時任研究院的首席科學家Raghu Ramakrishnan開玩笑地說:“在這樣下去,我們這兒就變成動物園了!”
此話一出,大家紛紛表示就叫動物園管理員吧,因為各個以動物命名的分布式組件放在一起,雅虎的整個分布式系統看上去就像一個大型的動物園了。
而Zookeeper 正好要用來進行分布式環境的協調,于是,Zookeeper 的名字也就由此誕生了。
二、Zookeeper是什么?
ZooKeeper是一個開放源碼的分布式應用程序協調服務,由Yahoo公司開發,目前廣泛應用于互聯網領域。zookeeper管理的是分布式系統里的服務器,作為一個管理員,他要做的就是協調分布式系統中的多個服務器,使得系統可以正常工作。
三、Zookeeper提供了什么?
zookeeper提供了三個東西:文件系統、通知機制、集群管理機制,這個看似很簡單,但卻可以解決分布式系統中的許多問題。
四、Zookeeper可以做什么?
1、命名服務
命名服務就是幫助我們對資源進行命名的服務,命名的主要目的是能夠更好的定位,比如在茫茫人海中,一叫你的名字就能立刻找到你,就是這個意思。
剛才提到zookeeper提供的第一個東西就是文件系統,指的就是zookeeper提供了這樣一種樹形結構的文件系統,zookeeper可以在自己的文件系統中創建以路徑為名稱的節點,用于存儲服務器地址等信息。阿里巴巴開發的分布式服務框架DUBBO就是用zookeeper來作為其命名服務,維護全局的服務器列表,實現方式就是服務器啟動時,在ZK的某個路徑下創建一個代表自己的節點。
2、配置管理
接下來說一下配置管理。每個程序一般都會需要一些配置信息,如果程序分散部署在多臺機器上,要逐個改變配置就變得很困難。現在把這些配置全部放到zookeeper上去,保存在 Zookeeper 的某個目錄節點中,然后所有相關應用程序對這個目錄節點進行監聽,一旦配置信息發生變化,每個應用程序就會收到 Zookeeper 的通知,然后就從 Zookeeper 獲取新的配置信息并應用到系統中,這樣就省去了手動去逐個修改配置信息的麻煩。其實這個過程中最關鍵的一點就是用到了zookeeper的watcher機制,watcher就是zookeeper的發布/訂閱機制,客戶端可以向zookeeper服務器注冊watcher,訂閱自己感興趣的節點,當相應的節點發生變化時,zookeeper服務器就會向客戶端發布通知。
這種發布/訂閱機制不僅可以用于配置管理,還可以用于實現許多其他功能,比如熱切換。
這是熱切換的一種場景,系統中有兩臺數據庫服務器和一臺接入服務器,其中一臺數據庫服務器是備用的,系統運行過程中,接入服務器需要向數據庫查詢一些數據,熱切換就是指,當主數據庫出現故障時,接入服務器可以自動發現故障并切換到備用數據庫服務器進行數據查詢。使用zookeeper實現熱切換的流程就是主數據庫啟動時先向zookeeper服務器發起注冊,這樣zookeeper服務器就會創建一個代表主數據庫的節點,之后主數據庫和zookeeper之間會建立鏈路監控,也就是每隔指定時間,數據庫會向zookeeper服務器發送一條消息表示自己還在正常運行。而接入服務器則需要向zookeeper服務器訂閱主數據庫的信息。當zookeeper沒有在指定時間收到主數據庫發來的鏈路監控消息時,就會認為主數據庫出現故障,并向接入服務器發布通知,接入服務器就會切換到備用數據庫進行數據查詢,這樣就實現了熱切換。
3、分布式鎖
分布式鎖是控制分布式系統之間同步訪問共享資源的一種方式。在分布式系統中,雖然有多個服務器提供服務,但系統數據只有一套,也就是多個服務器要使用同一套數據,這時就存在兩個問題,一個是某服務器在讀數據時,另一個服務器修改了數據,導致讀到的數據不是最新的數據,另一個問題是某服務器在修改數據的時候另一個服務器讀這塊數據,這也會導致讀到的數據不是最新的,而分布式鎖就是用來解決這兩種問題的一種機制。針對兩種問題,分布式鎖也分為共享鎖和排它鎖兩類。共享鎖又稱讀鎖,簡稱S鎖,共享鎖就是在事務T要讀取數據A時,對A加上S鎖,這時其他事務也可以讀數據A,但是就不能修改數據A了,這樣就避免了讀數據的同時數據被修改。排它鎖,也就是寫鎖,是在事務T要修改數據A時,對A加上X鎖,這時就只有事務T可以讀取或修改數據A,而其他事務無法在讀寫數據A,這樣就避免了修改數據時數據被別人讀取。
那么zookeeper是怎么實現分布式鎖的呢?其實得益于zookeeper的樹形數據結構以及節點的一些特性,在zookeeper中實現分布式鎖非常簡單,而且有多種方法,這里我就就介紹一種比較通俗易懂的實現方式。
首先,想要獲取鎖的人要在特定目錄下創建一個臨時節點,并標明創建的是讀鎖還是寫鎖,接下來就需要獲取并監聽這個目錄下所有的子節點,因為這個目錄下的所有節點都是要獲取鎖的人,那么既然這么多人都想獲取鎖,自己到底能不能獲取到呢,這就需要判斷自己在子節點中的順序,之后,如果需要的是讀鎖,那么就看目錄下有沒有比自己序號小的寫節點,如果沒有,說明之前的鎖都是讀鎖,那么自己就也可以讀,這樣就成功獲得了讀鎖而如果需要的是寫鎖,那就需要看自己是不是目錄下序號最小的節點了,如果不是,就要等前面的節點都釋放掉了自己才能獲得寫鎖。注意在讀取或修改完數據之后,要釋放自己申請的鎖,不然別人就一直無法正常訪問鎖住的數據了,而釋放的方式也很簡單,就是刪除自己創建的這個節點,這樣排在自己后面的節點就成為序號最小的節點就可以獲取鎖了。
對于分布式鎖還存在一個問題就是死鎖,死鎖就是指節點申請到鎖以后出了故障崩潰了無法主動釋放鎖,導致這塊數據被永久的鎖住了,對于這個問題zookeeper也可以輕松解決,我們看申請鎖的第一步就是創建一個臨時節點,為什么創建的是臨時節點呢,就是因為臨時節點在出現故障之后會自動刪除,這樣也就不會導致死鎖的發生。
4、隊列管理
在分布式系統中還有一個非常重要的概念就是消息隊列,消息隊列可以解決應用解耦、異步消息、流量削鋒等問題,實現系統的高性能、高可用。我們先看下面這兩個圖來理解一下什么是消息隊列。
首先傳統的用戶請求都是請求者直接發給服務器,服務器處理完成之后在將結果返回給請求者,這樣會導致一個問題就是請求者在發出請求后會阻塞住,在收到服務器回復之前什么都干不了,而如果引入了消息隊列,那么這個處理的流程就會變成請求者先把請求發給消息隊列,然后服務器在從消息隊列獲得需要處理的消息,處理完成之后在將結果返回給請求者,這種方式下,請求者只要把請求發給消息隊列就可以干其他事情了,這樣就實現了請求的異步處理,也解除了請求者和服務器之間的耦合。
而流量削鋒是什么意思呢,實際上就是在一些搶購的時候,搶購的時間一到,大量的請求會幾乎同時到達服務器,這很容易導致服務器崩潰,而使用消息隊列的話,所有的請求都先發到消息隊列,把消息隊列的長度設置為搶購商品的數量,超過隊列長度的請求直接返回失敗,而成功進入隊列的請求再由服務器慢慢處理,這樣就輕松化解了搶購時服務器短時間的巨大壓力。
在分布式系統中,同類的請求可以發送到同一個隊列中,系統中的多臺服務器都可以從隊列中獲取請求并進行處理,這樣實際上也就實現了將請求分配給多個服務器去處理,再配合適當的負載均衡算法,就可以充分的利用系統中的服務器集群了。
利用zookeeper的順序節點可以非常簡單的實現隊列。
生產者,也就是請求的發起者每產生一個請求,就會在指定目錄下創建一個節點,并將請求內容存儲在節點中,而消費者,也就是消息的處理者,從目錄下獲取序號最小的節點里存儲的請求內容,獲取請求后刪除該節點,避免請求被重復處理,這樣就實現了分布式系統的消息隊列。
5、集群管理
之前所講的不管是命名服務還是配置管理還是分布式鎖還有隊列管理,它們都是單個zookeeper服務器可以實現的功能,但由于zookeeper充當了分布式系統服務協調者的角色,因此一旦zookeeper服務器出現故障,整個系統就會癱瘓,另外,當系統中服務器數量較大時,單個zookeeper服務器要處理所有服務器的協調業務也會顯得力不從心,因此zookeeper本身也需要以集群的形式運行在分布式系統中,而其他服務器則可以通過任意一臺zookeeper服務器獲得協調服務。但這也對zookeeper提出了一個新的要求,就是zookeeper集群的所有zookeeper服務器要向外呈現一樣的視圖,也就是說不管連接了哪個zookeeper服務器,獲取到的信息都是一樣的。所以,zookeeper需要提供一種可靠的集群管理機制。
在zookeeper集群中,每個zookeeper的根目錄下,都有一個group member節點,每個加入集群的zookeeper服務器都要在該目錄下創建節點,這樣group member節點就存儲了集群中所有zookeeper服務器的信息。
接下來要做的就是保證每個zookeeper服務器,他的數據都是一致的,為了做到這一點,我們需要保證集群中只有一個zookeeper服務器有寫的權利,為什么呢,我們舉一個簡單的例子:
這個系統中有兩臺zookeeper服務器和兩個數據庫,兩個數據庫分別注冊到兩個zookeeper服務器上,zookeeper會在DB目錄下依次創建節點,如果說DB1先注冊,DB2后注冊,那么ZK1會先在DB目錄下創建DB1節點并通知ZK2,這樣DB2再向ZK2注冊時,ZK2就會在DB目錄下創建DB2節點,這樣不會有任何沖突,但是如果DB1和DB2恰好同時發起了注冊,或者注冊的時間差很小,那么ZK1與ZK2就會同時在DB目錄下創建DB1節點并通知對方,這樣就會產生沖突導致ZK1和ZK2無法同步,這就是集群中每個zookeeper服務器都有寫權限的后果。而如果只有ZK1有寫權限,當DB2向ZK2發起注冊時,ZK2會告知ZK1,由ZK1寫入再同步系統中所有ZK,這樣就避免了沖突的問題。所以說集群中就需要一個leader來執行所有的寫操作,這樣才能保證集群中各個zookeeper節點的一致性。但是這又產生了一個新的問題,就是怎么選取這個leader。
在Zookeeper集群中,leader選舉是基于paxos算法實現的,paxos算法是一種基于消息傳遞的一致性算法,在分布式領域應用的非常廣泛,這里我就簡單的概括一下zookeeper選舉leader的幾個步驟。首先是第一輪投票,所有ZK節點第一次投票都把會票投給自己,也就是都推選自己當leader,因為這個時候每個節點都不知道其他節點的信息,也不知道應該投給誰,所以就先投自己一票,投的這個票上呢有兩個重要信息,一個是sid,另一個是zxid,sid就是serverid,也就是這個ZK節點在集群中的序號,每個ZK節點的sid都是唯一的,不可能重復,而zxid是一個遞增的標識事務的id,用來保證事務的順序一致性,也就說zxid越大,對應的事務就越新,因此在處理第一輪投票的時候,首先要看的就是誰的zxid最大,因為zxid最大的ZK節點提出了最新的事務,也就是說他的狀態是最新的,所以集群中所有節點都應該與這個節點同步,如果zxid相同,比如在系統剛啟動時所有ZK節點都還沒有產生事務,zxid都還是0,這時就選擇sid最大的ZK節點,之前我們說過sid是不會重復的,因此一定存在一個sid最大的ZK節點,經過對第一輪投票的處理后,所有ZK節點都會按照這個規則進行第二輪投票,直到某個ZK節點獲得超過半數的投票,該節點就成功的當選了集群的leader,這里還要強調的一點就是一定要獲得超過半數的投票才能當選leader,比如在系統啟動的過程中,不同的ZK節點不是同時啟動的,假設集群由5個ZK節點,有兩個先啟動了,這兩個節點會先統一意見,比如都投給ZK2,但這時ZK2還不能成為leader,因為集群有5個ZK節點,必須在某個節點獲得3票以上時才能產生leader,這是選舉leader時需要注意的一點。
在leader選舉出來之后,集群中的寫操作就可以由leader來負責完成了,leader在收到寫請求之后,使用了一種消息廣播機制來實現所有節點的同步,首先leader接收到寫請求會產生一個proposal,leader會將這個proposal發送給集群中的其他follower節點,注意這里是通過消息隊列來發送的,leader節點和每個follower節點之間都存在一個消息隊列,關于消息隊列我剛剛講過,他可以實現異步解耦,如果這里沒有消息隊列,leader每發一條proposal給一個節點,都要等到回復才能給下一個節點發,這樣延時會非常大,而使用消息隊列的話,leader只需把proposal發給所有的消息隊列,再由follower從隊列獲取proposal并處理就可以了,follower收到proposal之后,會把數據先復制下來,并回復ACK,在leader收到半數個ACK之后,就會向所有follower節點發送commit提交,也就是告知本次寫操作生效了,其實這就類似是一種投票決議的過程,超過半數的人同意了proposal,這個proposal就可以生效了。這個就是zookeeper數據同步的流程,我們可以發現,不管是選舉leader,還是數據同步,都需要獲得半數以上節點的支持,這其實正是zookeeper集群的一個特性,就是只有集群中超過一半節點能夠正常工作,這個集群才可以使用,如果說集群中半數以上ZK節點掛掉了,這個集群就不可用了。
五、總結
1、Zookeeper適合讀請求較多的系統
因為zookeeper集群里多個ZK節點可以分擔系統讀的壓力,但是每次寫請求都要經過一個比較復雜的同步過程,因此zookeeper集群對寫請求的處理能力實際還不如單服務器強,所以說對于寫請求占很大比例的系統,zookeeper并不適用。
2、Zookeeper不能保證可用性
解釋這一點之前我要先介紹一下分布式系統的CAP原則,CAP原則指的是在一個分布式系統中,Consistency(一致性)、 Availability(可用性)、Partition tolerance(分區容錯性),三者不可兼得,在分布式系統中最多只能滿足其中的兩項。其中,一致性是指集群所有節點對外呈現的數據是一致的,也就是同一時刻不會出現從兩個不同的節點讀取數據結果會不同的情況,這一點zookeeper是滿足的,因為之前講過zookeeper有一套非常可靠的數據同步機制。分區容錯性是指如果分布式系統部署在多個網絡分區,其中部分網絡分區出現了問題,系統仍然可用,這點zookeeper也是滿足的,因為部分網絡分區出了問題只會導致這些分區的節點不可用,但不會影響整體系統。但是可用性zookeeper卻是無法保證的,這一方面是因為zookeeper集群必須保證半數以上節點正常,整個系統才能工作,如果半數以上節點崩潰了,系統就不可用了,另一方面可用性要求的是對所有的請求都能在較短時間內得到響應,比如搜索引擎類的系統,要求搜索一個關鍵字可以在0.3秒之內給出結果,才算是具有可用性,如果時間太長,那只能說是能用,不能算是可用,對zookeeper而言,問題有兩個,一個是所有的寫請求要完成都需要經過一個集群內所有節點同步的過程,如果同步失敗或時間太長,就違背了可用的原則,另一個問題是集群的leader出現問題后,要經過選舉和恢復同步兩個過程集群才能繼續正常工作,那么在這段時間系統就是不可用的,因此我們說zookeeper無法滿足可用性。對于某些服務,它對一致性要求不高,但一定要保證可用性,這時候zookeeper就不適用了。當然這并不是說zookeeper設計的不好,因為zookeeper設計的主要目的就是確保分布式集群的一致性,在某些場景,如果一致性無法滿足可能會導致系統更混亂,在這些場景放棄一部分可用性是值得的,畢竟CAP這三點是無法同時滿足的,所以在實際的分布式應用中,我們還是應該根據具體情況進行取舍,選擇最適合我們的系統架構和協調機制。
大數據
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。