原文鏈接
- 瞭解zookeeper及zookeeper的設計猜想
- Zookeeper集羣角色
- 深入分析ZAB協議
- 從源碼層面分析leader選舉的實現過程
- 關於zookeeper的數據存儲
回顧內容
- zookeeper集羣安裝(myid/zoo.cfg)
- zookeeper的數據模型(znode)
- 節點的特性
(持久化、臨時節點、有序節點、同級節點必須唯一、臨時節點不能存在子節點)
- 節點的status信息
- 簡單地瞭解了watcher機制
- Zookeeper的應用場景
Zookeeper的由來
分佈式架構下面臨的問題
我們有一個 orderservice 服務
我們對同一個應用去做拷貝,相當於是部署了三個一模一樣的應用實例,組成一個集羣。每個模塊中運行一個 Task 任務。
因為我們是一個集羣,所以我們每一個節點都有權限去執行這個任務。
我們每一個服務都會有一個配置文件,application.properties,配置文件中會配置一些數據庫的信息,連接信息,服務地址信息等等,我們可能有些東西是需要動態變更的,這是一個背景,然後這些數據需要進行調整的話,因為現在是一個集羣,所以現在必須在同一時刻,對這些數據進行一個更新,但是如果你是保存在配置文件裏邊的話,你怎麼保證各個節點的數據保持一致。
我們現在一個server執行一個任務,假如説 ,這個任務在server1上執行,怎麼控制這個任務只在server1上執行。假如説我們有一個定時任務解析文件去入庫,我們怎麼只讓其中一台機子去解析這個文件,其他機子不需要解析。因為如果三台機子都做這個任務的話,那麼意味着數據要做三次。
如果你有一個手段讓一個任務只在一個server上執行,如果其中一個服務掛掉了以後,其他節點怎麼知道它掛掉了,去重新接替任務。
我們如果説存在一個共享資源,集羣對每一個節點都是公平的,所以存在某一個時刻,很多個服務去訪問同一個文件。在訪問的時候怎麼去保證互斥性,就像多線程裏邊,多個線程同時訪問同一塊資源,如果保證線程資源的安全性。
集中式到分佈式架構發展帶來的問題:
- 各個數據節點的數據一致
- 怎麼保證任務只在一個節點上執行。
- 如果某個節點掛了,其他節點如果發現並接替執行任務。
- 存在共享資源的場景。互斥性、安全性
總結一下,就是缺少一個分佈式的協調機制。
分佈式協調做得比較好的一個是
- 谷歌的chubby
- Apache的zookeeper
谷歌的chubby是一個分佈式鎖的東西,
解決分佈式鎖、master選舉相關的服務
雅虎公司內部很多模塊,它需要依賴一個系統,去對裏邊的分佈式系統去做一個協調,然而Google的chubby不開源。 所以雅虎基於chubby的思想開發了zookeeper,捐獻給Apache,所以現在我們下載的zookeeper是從Apache上下載下來的。
這就是我們為什麼要用zookeeper機制的原因 。
Zookeeper,是一個文件結構的樹形結構的數據存儲,我們基於zookeeper本身的數據結構和它的節點特性,我們可以利用它去實現一個,互斥的訪問
那麼我們就需要先去註冊一個結點,因為zookeeper節點是一個樹形結構
我們把zookeeper中最小的那個節點有優先權,第一個服務去執行,後邊的兩個就不讓它去執行。我們利用zookeeper起到對我們相關服務節點的協調控制。
如果我們的zookeeper作為我們整個系統的服務協調中心的話,那麼zookeeper就會成為一個瓶頸,而zookeeper目標是提供一個高性能,高可用的,有嚴格訪問順序的控制能力(表示寫操作的順序)分佈式協調服務。
初衷
基於zookeeper的初衷,我們要解決zookeeper的性能問題。
還有它本身的可用性,我們不能讓它本身成為一個單點。如果它是一個單點,如果它掛了,那麼相應的節點無法執行相應的操作。
Zookeeper的設計猜想:
- 防止單點故障
集羣方案(leader、follower)、還能分擔請求
- 每個節點的數據是一致的(必須要有leader)
Leader、master; redis-cluster
- Leader掛了怎麼辦?數據如何恢復
選舉機制?數據如何恢復
- 如何去保證數據一致性?(分佈式事務)
一開始單體架構中,CAP一致性是由一個事務去統一控制和管理的,現在一個請求,分到了不同的服務器,但是我還需要保證在整個集羣是一致的。
每個節點只能保證內部的一致性,我們要保證整體的一致性,zookeeper就引入了改進版的2PC協議去完成2階提交
2PC 二階段提交協議
Two Phase Commitment Protocol
當一個事務涉及多個節點去提交的時候,為了保證事務處理的ACI的特性的話,在2PC中引入了一個概念叫協調者,通過一個協調者來控制各個節點事務的執行邏輯。
如果參與者1與參與者2中的數據需要都執行成功,才算一次完整的成功。
在單體架構中直接通過transactional去直接控制事務的一致性。
在分佈式系統中引入一個協調者,協調者先對每一個參與這個事務的參與者提交一次請求,每個參與者收到請求後,會給一個迴應,是不是能夠執行這個事務,給個“是”,表示可以執行這次事務,如果每一個參與者都回應一個是,那麼就表示這個請求是可以被執行成功的。 這時候協調者就對每一個參與者進行一次commit,然後提交完成以後給一個ack的二階提交的響應。
假如某一個參與者第一次迴應了一個“否”,就意味着整體要全部失敗,事務要保證原子性,要麼全部成功,要麼全部失敗。那麼對所有的節點發起的是一個rollback操作。這就是一個所謂的二階段提交的概念。
而zookeeper裏邊是基於二階提交的方式去做數據同步的
結論:
- 為什麼用ZAB實現選舉(基於proposal思想的,zookeeper裏邊的協議,解決數據一致性的問題)
- 為什麼要做集羣
- 為什麼通過2PC做數據一致性
Zookeeper的集羣
我們現在有一個客户端,對zookeeper集羣做出了一個處理
Zookeeper對外有一個集羣,客户端進行連接這個集羣時,會隨機連接某一個節點。
如果當前的請求是讀請求,我們的請求可以落在任意一個節點去讀取數據。
如果是寫請求,那麼這個請求會轉發給leader去處理
我們説過了,zookeeper是基於一個2PC的方式進行的一個事務的提交
如果我們的一個寫請求到了一個follower節點上,它會轉發到leader節點上,然後leader節點會發起一個提議,會把事務發給集羣中的每一個節點,這時候的follower節點要給leader節點一個ack,這個ack表示當前的這個節點是不是能夠執行這個事務,leader一旦發現過半的請求是同意的,我會提交這個事務,然後就返回response。
這就是改進版的2PC的一個事務。然後提交事務以後,數據會同步給observer。
Leader是整個zookeeper集羣的核心,起到了主導zookeeper集羣的一個作用,比如説我們一個事務請求的一個調度和處理,保證我們集羣中事務處理的一個順序性。
Follower角色主要是用來處理客户端的非事務請求,以及轉發事務請求給leader服務器,參與整個事務的投票過程,叫proposal,我們這條數據要保存到zookeeper集羣裏邊,必須要有過半的節點同意。
Observer是一個觀察者的角色,相當於我能夠了解集羣中相關節點的相關變化並且對狀態進行同步, observer不參與事務請求的投票。
如果我們想要集羣的性能更高,我們肯定想要引入更多的節點,節點越來越多,性能提升了,投票的時候會變慢,不影響整體寫性能的情況下,引入observer,提升整體的性能。
所有節點的數量必須是2N+1,我們必須保證有2N+1個節點,叫基礎節點,zookeeper集羣的工作機制,是必須有半數以上節點能夠正常地工作,並且能夠參與到投票機制,如果投票不能過半的話,我們的投票是沒有結果的。
ZAB協議
(基於Proposal協議衍生出來的一種算法)
ZAB協議是zookeeper裏邊專門設計的針對崩潰恢復的原子廣播協議,
主要用來實現數據一致性
Zookeeper是一種類似於主備的形式,leader掛了,follower上能夠選舉出一個leader來,代替上一個leader上的服務,通過ZAB協議保證各個節點上數據的一致性,為了保證主備數據的一致性,就需要實現ZAB協議。
- 崩潰恢復
- 原子廣播
當我們第一次啓動集羣的時候或者leader集羣崩潰的時候,這時候ZAB協議就會產生作用,ZAB協議會進入恢復模式,它會從宕掉的集羣裏邊重新去選舉一個leader,並且當新的leader選舉出來以後,當集羣中過半的機器和新選舉的leader完成了數據同步以後,整個集羣就進入了一個正常運行的狀態,就進入了一個原子廣播的階段。
消息廣播:(事務提交)
改進版本的2PC
首先當leader收到一個事務請求的時候會生成一個zxid(64位的自增ID)
- 它會對每一個請求分配一個zxid,
通過zxid的大小可以實現數據的因果有序
Leader對每一個follower都會準備一個IFIO隊列,這個是通過TCP協議來實現的,
- 它會把帶有zxid的消息作為一個proposal(提案),分發到集羣中的每一個follower節點
- 每一個follower節點收到請求以後,它會把propose這個事務寫入到磁盤,返回ack給leader
- Leader收到合法數量的請求ack,過半的請求數,同意以後,再發起commit請求
整個事務的過程就是一個消息廣播的過程。
Leader的投票過程和事務的投票過程中不需要observer,但是observer必須保證他的數據和leader的數據是保持一致的
崩潰恢復(對數據層來説)
- 當leader失去了過半的follower節點的聯繫
- 當leader服務掛了
整個集羣就會進入崩潰恢復階段,因為我們必須保證leader掛了之後整個集羣還可用。
對於數據恢復來説:
- 已經被處理的消息不能丟失
當leader收到合法數量的follower的ack以後,就會向各個follower廣播消息(commit命令),同時本地自己也會commit這條事務消息。如果follower節點收到commit命令之前,Leader掛了。會導致部分節點收到commit,部分節點沒有收到commit。ZAB協議需要保證已經處理的消息不能丟失。
- 被丟棄的消息不能再次出現
當leader請求收到事務請求,並且還未發起事務投票之前,leader掛了。
下一個leaser,要跳過這個請求。
我們的leader掛掉以後,我們不僅需要重新選舉leader,還要恢復我們的數據。
ZAB的設計思想
- zxid是最大的? 保證已經提交的proposal一定會被提交!!不會出現數據丟失。
- epoch的概念,每產生一個新的leader,那麼新的leader的epoch會+1
zxid是64位的數據,
低32位表示消息計數器(自增的),高32位(epoch編號)
如果我們新的選舉出來的leader,就意味着它的epoch比老的epoch高,它的每一個事務id 都會帶上epoch編號,和消息計數器。這樣設計以後,當我們重新選舉leader以後,消息會重新從0開始,而epoch是老的加上一,好處是:老的服務器重新啓動以後,它不會再成為leader,就是在這一輪裏邊不會在成為leader,並且老的leader,變成follower,加入到集羣以後,它的zxid一定會小於新的zxid,那麼新的leader,會把他所有沒有提交的事務都會清除。
epoch可以理解為年號,皇帝的年號。
實操
[root@Darian1 bin]# ls /tmp/zookeeper/
myid version-2 zookeeper_server.pid
[root@Darian1 bin]# ls /tmp/zookeeper/version-2/
acceptedEpoch currentEpoch log.1 log.100000001 log.200000001
[root@Darian1 bin]# vim /tmp/zookeeper/version-2/currentEpoch
2
~
# ls /tmp/zookeeper/
# ls /tmp/zookeeper/version-2/
acceptedEpoch cuttentEpoch log.1 log.1000000001 log.3 log.a00000001
# vim /tmp/zookeeper/version-2/currentEpoch
可以看到epoch
:q!
Epoch
我們去關閉leader
[root@Darian1 bin]# sh zkServer.sh stop
重新看到epoch
可以看到加了一
Epoch加一,可以保證把以前沒有提交的proposal丟棄掉,
[root@Darian1 bin]# ls /tmp/zookeeper/version-2
查看日誌內容
[root@Darian1 bin]# java -cp :/zookeeper/zookeeper-3.4.10/lib/slf4j-api-1.6.1.jar:/zookeeper/zookeeper-3.4.10/zookeeper-3.4.10.jar org.apache.zookeeper.server.LogFormatter /tmp/zookeeper/version-2/log.1
路徑:java –cp :/slf4j.jar:/zookeeper.jar org.apache.zookeeper.server.LogFormatter /tmp/zookeeper/version-2/log.1
server.1=192.168.136.128:2888:3888
server.2=192.168.136.129:2888:3888
server.3=192.168.136.130:2888:3888
理解ZAB協議
假設leader中有三個事務請求
| zxid | |
|---|---|
| P1 | 01 |
| P2 | 02 |
| P3 | 03 |
它會把這個事務分發給每一個節點去做提交
假設
| Follower1 | 收到了 p1 01 |
| Follower2 | 收到了 p1 01 |
其他請求還沒有發起,
這時候leader掛了,
這時候我們假設follower1變成了leader
這時候新的leader發起了一個新的事務請求,
[p2] -10
(代表epoch 0 代表消息數)
這個請求同步到follower上 叫 [p2] -10
數據恢復
整個集羣裏邊所有的參與者follower節點都要確定事務日誌裏邊的Propress已經被過半的節點提交過了。
Leader會為每一個follower節點準備一個FIFO的隊列,把各個follower節點沒有被提交的請求,以事務的方式逐步發給其他節點,但是,如果事務是失效的,過期的,它會被丟棄掉。
Leader選舉
兩種情況會做leader選舉,
一個是集羣啓動,一個是崩潰恢復
Fast Leader 基於fast leader做選舉的
Zxid最大會設置為leader【事務id,事務id越大,那麼表示數據越新】
64位, 000000000000000001(epoch) 0000000000000000000010
Myid(服務器id,sid)【myid越大,在leader選舉機制中權重越大】
epoch【邏輯時鐘】【每一輪投票,epoch都會遞增】
選舉狀態(LOOKING)
- LEADING
- FOLLOWING
- OBSERVING
理論
啓動的時候初始化,每一個節點會選舉自己作為一個leader,把當前節點的這些信息發佈給集羣中的每一個節點。
(myid, zxid, epoch)
每一個節點會收到這些信息,然後去做投票。投票過程中會比較對應的數據記錄結果。
| 1 | 檢查zxid | zxid大的節點直接為leader |
| 2 | myid | myid比較大的會作為leader |
| 3 | 投票完了以後 | 會去統計票數,根據投票結果,確定leader選舉的結果。 |
| 4 | 統計投票 | 每次投票後,服務器都會統計投票信息,判斷是否已經有過半機器接收到相同的投票信息,此時便認為已經選出了Leader |
如果運行過程中的選舉的話,leader掛掉了,它可以重新去選舉leader,第一個它會去變更狀態,把剩下的所有節點都變成一個LOOKING狀態去重新做一個監控,監控其他的節點去重新做一個選舉。 接下來步驟跟初始化的都一樣了………
zookeeper 的代碼入口搜索 QuorumPeerMain
FastLeaderElection 類中 的 lookForLeader投票方法的實現機制
投票流程
- 判斷epoch
- Zxid
- 再判斷myid
高性能和高可用的集羣
熱備的集羣
來源於: https://javaguide.net
公眾號:不止極客