原文出處:http://rdc.taobao.com/blog/cs/?p=162算法
本文主要介紹zookeeper中zookeeper Server leader的選舉,zookeeper在選舉leader的時候採用了paxos算法(主要是fast paxos),這裏主要介紹其中兩種:LeaderElection 和FastLeaderElection.網絡
在zookeeper中,一個zookeeper集羣有多少個Server是固定,每一個Server用於選舉的IP和PORT都在配置文件中數據結構
每個Server都有一個數字編號,並且是惟一的,咱們根據配置文件中的配置來對每個Server進行編號,這一步在部署時須要人工去作,須要在存儲數據文件的目錄中建立一個文件叫myid的文件,並寫入本身的編號,這個編號在處理我提交的value相同頗有用spa
得到n/2 + 1個Server贊成(這裏意思是n/2 + 1個Server要贊成擁有zxid是全部Server最大的哪一個Server)線程
zookeeper中選舉主要是採用UDP,也一種實現是採用TCP,在這裏介紹的兩種實現採用的是UDPserver
LOOKING 初始化狀態對象
LEADING 領導者狀態blog
FOLLOWING 跟隨者狀態排序
zookeeper中每個Server都有一個ID,這個ID是不重複的,並且按大小排序,若是遇到這樣的狀況時,zookeeper就推薦ID最大的哪一個Server做爲Leader隊列
Leader定時向Fllower發ping消息,Fllower定時向Leader發ping消息,當發現Leader沒法ping通時,就改變本身的狀態(LOOKING),發起新的一輪選舉
zookeeer Server: zookeeper中一個Server,如下簡稱Server
zxid(zookeeper transtion id): zookeeper 事務id,他是選舉過程當中可否成爲leader的關鍵因素,它決定當前Server要將本身這一票投給誰(也就是我在選舉過程當中的value,這只是其中一個,還有id)
myid/id(zookeeper server id): zookeeper server id ,他也是可否成爲leader的一個因素
epoch/logicalclock:他主要用於描述leader是否已經改變,每個Server中啓動都會有一個epoch,初始值爲0,當 開始新的一次選舉時epoch加1,選舉完成時 epoch加1。
tag/sequencer:消息編號
xid:隨機生成的一個數字,跟epoch功能相同
Client Proposer Acceptor Learner | | | | | | | X-------->| | | | | | Request | X--------->|->|->| | | Prepare(N)//向全部Server提議 | |<---------X--X--X | | Promise(N,{Va,Vb,Vc})//向提議人回覆是否接受提議(若是不接受回到上一步) | X--------->|->|->| | | Accept!(N,Vn)//向全部人發送接受提議消息 | |<---------X--X--X------>|->| Accepted(N,Vn)//向提議人回覆本身已經接受提議) |<---------------------------------X--X Response | | | | | | |
沒有衝突的選舉過程
Client Leader Acceptor Learner | | | | | | | | | X--------->|->|->|->| | | Any(N,I,Recovery) | | | | | | | | X------------------->|->|->|->| | | Accept!(N,I,W)//向全部Server提議,全部Server收到消息後,接受提議 | |<---------X--X--X--X------>|->| Accepted(N,I,W)//向提議人發送接受提議的消息 |<------------------------------------X--X Response(W) | | | | | | | |
LeaderElection是Fast paxos最簡單的一種實現,每一個Server啓動之後都詢問其它的Server它要投票給誰,收到全部Server回覆之後,就計算出zxid最大的哪 個Server,並將這個Server相關信息設置成下一次要投票的Server
每一個Server都有一個response線程和選舉線程,咱們先看一下每一個線程是作一些什麼事情
它主要功能是被動的接受對方法的請求,並根據當前本身的狀態做出相應的回覆,每次回覆都有本身的Id,以及xid,咱們根據他的狀態來看一看他都回復了哪些內容
LOOKING狀態:
本身要推薦的Server相關信息(id,zxid)
LEADING狀態
myid,上一次推薦的Server的id
FLLOWING狀態:
當前Leader的id,以及上一次處理的事務ID(zxid)
選舉線程由當前Server發起選舉的線程擔任,他主要的功能對投票結果進行統計,並選出推薦的Server。選舉線程首先向全部Server發起 一次詢問(包括本身),被詢問方,根據本身當前的狀態做相應的回覆,選舉線程收到回覆後,驗證是不是本身發起的詢問(驗證 xid是否一致),而後獲取對方的id(myid),並存儲到當前詢問對象列表中,最後獲取對方提議的leader相關信息(id,zxid),並將這些 信息存儲到當次選舉的投票記錄表中,當向全部Server都詢問完之後,對統計結果進行篩選並進行統計,計算出當次詢問後獲勝的是哪個 Server,並將當前zxid最大的Server設置爲當前Server要推薦的Server(有多是本身,也有能夠是其它的Server,根據投票 結果而定,可是每個Server在第一次投票時都會投本身),若是此時獲勝的Server得到n/2 + 1的Server票數, 設置當前推薦的leader爲獲勝的Server,將根據獲勝的Server相關信息設置本身的狀態。每個Server都重複以上流程,直到選出 leader
瞭解每一個線程的功能之後,咱們來看一看選舉過程
當一個Server啓動時它都會發起一次選舉,此時由選舉線程發起相關流程,那麼每一個Server都會得到當前zxid最大的哪一個Server是 誰,若是當次最大的Server沒有得到n/2+1個票數,那麼下一次投票時,他將向zxid最大的Server投票,重複以上流程,最後必定能選舉出一 個Leader
只要保證n/2+1個Server存活就沒有任何問題,若是少於n/2+1個Server存活就沒辦法選出Leader
當選舉出Leader之後,此時每一個Server應該是什麼狀態(FLLOWING)都已經肯定,此時因爲Leader已經死亡咱們就無論它,其它 的Fllower按正常的流程繼續下去,當完成這個流程之後,全部的Fllower都會向Leader發送Ping消息,若是沒法ping通,就改變本身 的狀態爲(FLLOWING ==> LOOKING),發起新的一輪選舉
這個過程的處理跟選舉過程當中Leader死亡處理方式同樣,這裏就再也不描述
fastLeaderElection是標準的fast paxos的實現,它首先向全部Server提議本身要成爲leader,當其它Server收到提議之後,解決epoch和zxid的衝突,並接受對方的提議,而後向對方發送接受提議完成的消息
本地消息結構:
static public class Notification {
long leader; //所推薦的Server id
long zxid; //所推薦的Server的zxid(zookeeper transtion id)
long epoch; //描述leader是否變化(每個Server啓動時都有一個logicalclock,初始值爲0)
QuorumPeer.ServerState state; //發送者當前的狀態
InetSocketAddress addr; //發送者的ip地址
}
網絡消息結構:
static public class ToSend {
int type; //消息類型
long leader; //Server id
long zxid; //Server的zxid
long epoch; //Server的epoch
QuorumPeer.ServerState state; //Server的state
long tag; //消息編號
InetSocketAddress addr;
}
每一個Server都一個接收線程池(3個線程)和一個發送線程池 (3個線程),在沒有發起選舉時,這兩個線程池處於阻塞狀態,直到有消息到來時才解除阻塞並處理消息,同時每一個Server都有一個選舉線程(能夠發起 選舉的線程擔任);咱們先看一下每一個線程所作的事情,以下:
被動接收消息端(接收線程池)的處理:
notification: 首先檢測當前Server上所被推薦的zxid,epoch是否合法(currentServer.epoch <= currentMsg.epoch && (currentMsg.zxid > currentServer.zxid || (currentMsg.zxid == currentServer.zxid && currentMsg.id > currentServer.id))) 若是不合法就用消息中的zxid,epoch,id更新當前Server所被推薦的值,此時將收到的消息轉換成Notification消息放入接收隊列 中,將向對方發送ack消息
ack: 將消息編號放入ack隊列中,檢測對方的狀態是不是LOOKING狀態,若是不是說明此時已經有Leader已經被選出來,將接收到的消息轉發成Notification消息放入接收對隊列
主動發送消息端(發送線程池)的處理:
notification: 將要發送的消息由Notification消息轉換成ToSend消息,而後發送對方,並等待對方的回覆,若是在等待結束沒有收到對方法回覆,重作三次, 若是重作次仍是沒有收到對方的回覆時檢測當前的選舉(epoch)是否已經改變,若是沒有改變,將消息再次放入發送隊列中,一直重複直到有Leader選 出或者收到對方回覆爲止
ack: 主要將本身相關信息發送給對方
主動發起選舉端(選舉線程)的處理:
首先本身的epoch 加1,而後生成notification消息,並將消息放入發送隊列中,系統中配置有幾個Server就生成幾條消息,保證每一個Server都能收到此消 息,若是當前Server的狀態是LOOKING就一直循環檢查接收隊列是否有消息,若是有消息,根據消息中對方的狀態進行相應的處理。
LOOKING狀態:
首先檢測消息中epoch是否合法,是否比當前Server的大,若是比較當前Server的epoch大時,更新epoch,檢測是消息中的 zxid,id是否比當前推薦的Server大,若是是更新相關值,並新生成notification消息放入發關隊列,清空投票統計表; 若是消息小的epoch則什麼也不作; 若是相同檢測消息中zxid,id是否合法,若是消息中的zxid,id大,那麼更新當前Server相關信息,並新生成notification消息放 入發送隊列,將收到的消息的IP和投票結果放入統計表中,並計算統計結果,根據結果設置本身相應的狀態
LEADING狀態:
將收到的消息的IP和投票結果放入統計表中(這裏的統計表是獨立的),並計算統計結果,根據結果設置本身相應的狀態
FOLLOWING狀態:
將收到的消息的IP和投票結果放入統計表中(這裏的統計表是獨立的),並計算統計結果,根據結果設置本身相應的狀態
瞭解每一個線程的功能之後,咱們來看一看選舉過程,選舉過程跟第一程同樣
當一個Server啓動時它都會發起一次選舉,此時由選舉線程發起相關流程,經過將本身的zxid和epoch告訴其它Server,最後每一個 Server都會得zxid值最大的哪一個Server的相關信息,而且在下一次投票時就投zxid值最大的哪一個Server,重複以上流程,最後必定能選 舉出一個Leader
只要保證n/2+1個Server存活就沒有任何問題,若是少於n/2+1個Server存活就沒辦法選出Leader
當選舉出Leader之後,此時每一個Server應該是什麼狀態 (FLLOWING)都已經肯定,此時因爲Leader已經死亡咱們就無論它,其它的Fllower按正常的流程繼續下去,當完成這個流程之後,全部的 Fllower都會向Leader發送Ping消息,若是沒法ping通,就改變本身的狀態爲(FLLOWING ==> LOOKING),發起新的一輪選舉
這個過程的處理跟選舉過 程中Leader死亡處理方式同樣,這裏就再也不描述