理解zookeeper選舉機(jī)制
zookeeper集群
配置多個(gè)實(shí)例共同構(gòu)成一個(gè)集群對(duì)外提供服務(wù)以達(dá)到水平擴(kuò)展的目的,每個(gè)服務(wù)器上的數(shù)據(jù)是相同的,每一個(gè)服務(wù)器均可以對(duì)外提供讀和寫的服務(wù),這點(diǎn)和redis是相同的,即對(duì)客戶端來(lái)講每個(gè)服務(wù)器都是平等的。
這篇主要分析leader的選擇機(jī)制,zookeeper提供了三種方式:
- LeaderElection
- AuthFastLeaderElection
- FastLeaderElection
默認(rèn)的算法是FastLeaderElection,所以這篇主要分析它的選舉機(jī)制。
選擇機(jī)制中的概念
服務(wù)器ID
比如有三臺(tái)服務(wù)器,編號(hào)分別是1,2,3。
編號(hào)越大在選擇算法中的權(quán)重越大。
數(shù)據(jù)ID
服務(wù)器中存放的最大數(shù)據(jù)ID.
值越大說(shuō)明數(shù)據(jù)越新,在選舉算法中數(shù)據(jù)越新權(quán)重越大。
邏輯時(shí)鐘
或者叫投票的次數(shù),同一輪投票過(guò)程中的邏輯時(shí)鐘值是相同的。每投完一次票這個(gè)數(shù)據(jù)就會(huì)增加,然后與接收到的其它服務(wù)器返回的投票信息中的數(shù)值相比,根據(jù)不同的值做出不同的判斷。
選舉狀態(tài)
- LOOKING,競(jìng)選狀態(tài)。
- FOLLOWING,隨從狀態(tài),同步leader狀態(tài),參與投票。
- OBSERVING,觀察狀態(tài),同步leader狀態(tài),不參與投票。
- LEADING,領(lǐng)導(dǎo)者狀態(tài)。
選舉消息內(nèi)容
在投票完成后,需要將投票信息發(fā)送給集群中的所有服務(wù)器,它包含如下內(nèi)容。
- 服務(wù)器ID
- 數(shù)據(jù)ID
- 邏輯時(shí)鐘
- 選舉狀態(tài)
選舉流程圖
因?yàn)槊總€(gè)服務(wù)器都是獨(dú)立的,在啟動(dòng)時(shí)均從初始狀態(tài)開(kāi)始參與選舉,下面是簡(jiǎn)易流程圖。
選舉狀態(tài)圖
描述Leader選擇過(guò)程中的狀態(tài)變化,這是假設(shè)全部實(shí)例中均沒(méi)有數(shù)據(jù),假設(shè)服務(wù)器啟動(dòng)順序分別為:A,B,C。
源碼分析
QuorumPeer
主要看這個(gè)類,只有LOOKING狀態(tài)才會(huì)去執(zhí)行選舉算法。每個(gè)服務(wù)器在啟動(dòng)時(shí)都會(huì)選擇自己做為領(lǐng)導(dǎo),然后將投票信息發(fā)送出去,循環(huán)一直到選舉出領(lǐng)導(dǎo)為止。
public void run() { //....... try { while (running) { switch (getPeerState()) { case LOOKING: if (Boolean.getBoolean("readonlymode.enabled")) { //... try { //投票給自己... setCurrentVote(makeLEStrategy().lookForLeader()); } catch (Exception e) { //... } finally { //... } } else { try { //... setCurrentVote(makeLEStrategy().lookForLeader()); } catch (Exception e) { //... } } break; case OBSERVING: //... break; case FOLLOWING: //... break; case LEADING: //... break; } } } finally { //... } }
FastLeaderElection
它是zookeeper默認(rèn)提供的選舉算法,核心方法如下:具體的可以與本文上面的流程圖對(duì)照。
public Vote lookForLeader() throws InterruptedException { //... try { HashMap<Long, Vote> recvset = new HashMap<Long, Vote>(); HashMap<Long, Vote> outofelection = new HashMap<Long, Vote>(); int notTimeout = finalizeWait; synchronized(this){ //給自己投票 logicalclock.incrementAndGet(); updateProposal(getInitId(), getInitLastLoggedZxid(), getPeerEpoch()); } //將投票信息發(fā)送給集群中的每個(gè)服務(wù)器 sendNotifications(); //循環(huán),如果是競(jìng)選狀態(tài)一直到選舉出結(jié)果 while ((self.getPeerState() == ServerState.LOOKING) && (!stop)){ Notification n = recvqueue.poll(notTimeout, TimeUnit.MILLISECONDS); //沒(méi)有收到投票信息 if(n == null){ if(manager.haveDelivered()){ sendNotifications(); } else { manager.connectAll(); } //... } //收到投票信息 else if (self.getCurrentAndNextConfigVoters().contains(n.sid)) { switch (n.state) { case LOOKING: // 判斷投票是否過(guò)時(shí),如果過(guò)時(shí)就清除之前已經(jīng)接收到的信息 if (n.electionEpoch > logicalclock.get()) { logicalclock.set(n.electionEpoch); recvset.clear(); //更新投票信息 if(totalOrderPredicate(n.leader, n.zxid, n.peerEpoch, getInitId(), getInitLastLoggedZxid(), getPeerEpoch())) { updateProposal(n.leader, n.zxid, n.peerEpoch); } else { updateProposal(getInitId(), getInitLastLoggedZxid(), getPeerEpoch()); } //發(fā)送投票信息 sendNotifications(); } else if (n.electionEpoch < logicalclock.get()) { //忽略 break; } else if (totalOrderPredicate(n.leader, n.zxid, n.peerEpoch, proposedLeader, proposedZxid, proposedEpoch)) { //更新投票信息 updateProposal(n.leader, n.zxid, n.peerEpoch); sendNotifications(); } recvset.put(n.sid, new Vote(n.leader, n.zxid, n.electionEpoch, n.peerEpoch)); //判斷是否投票結(jié)束 if (termPredicate(recvset, new Vote(proposedLeader, proposedZxid, logicalclock.get(), proposedEpoch))) { // Verify if there is any change in the proposed leader while((n = recvqueue.poll(finalizeWait, TimeUnit.MILLISECONDS)) != null){ if(totalOrderPredicate(n.leader, n.zxid, n.peerEpoch, proposedLeader, proposedZxid, proposedEpoch)){ recvqueue.put(n); break; } } if (n == null) { self.setPeerState((proposedLeader == self.getId()) ? ServerState.LEADING: learningState()); Vote endVote = new Vote(proposedLeader, proposedZxid, proposedEpoch); leaveInstance(endVote); return endVote; } } break; case OBSERVING: //忽略 break; case FOLLOWING: case LEADING: //如果是同一輪投票 if(n.electionEpoch == logicalclock.get()){ recvset.put(n.sid, new Vote(n.leader, n.zxid, n.electionEpoch, n.peerEpoch)); //判斷是否投票結(jié)束 if(termPredicate(recvset, new Vote(n.leader, n.zxid, n.electionEpoch, n.peerEpoch, n.state)) && checkLeader(outofelection, n.leader, n.electionEpoch)) { self.setPeerState((n.leader == self.getId()) ? ServerState.LEADING: learningState()); Vote endVote = new Vote(n.leader, n.zxid, n.peerEpoch); leaveInstance(endVote); return endVote; } } //記錄投票已經(jīng)完成 outofelection.put(n.sid, new Vote(n.leader, IGNOREVALUE, IGNOREVALUE, n.peerEpoch, n.state)); if (termPredicate(outofelection, new Vote(n.leader, IGNOREVALUE, IGNOREVALUE, n.peerEpoch, n.state)) && checkLeader(outofelection, n.leader, IGNOREVALUE)) { synchronized(this){ logicalclock.set(n.electionEpoch); self.setPeerState((n.leader == self.getId()) ? ServerState.LEADING: learningState()); } Vote endVote = new Vote(n.leader, n.zxid, n.peerEpoch); leaveInstance(endVote); return endVote; } break; default: //忽略 break; } } else { LOG.warn("Ignoring notification from non-cluster member " + n.sid); } } return null; } finally { //... } }
判斷是否已經(jīng)勝出
默認(rèn)是采用投票數(shù)大于半數(shù)則勝出的邏輯。
選舉流程簡(jiǎn)述
目前有5臺(tái)服務(wù)器,每臺(tái)服務(wù)器均沒(méi)有數(shù)據(jù),它們的編號(hào)分別是1,2,3,4,5,按編號(hào)依次啟動(dòng),它們的選擇舉過(guò)程如下:
- 服務(wù)器1啟動(dòng),給自己投票,然后發(fā)投票信息,由于其它機(jī)器還沒(méi)有啟動(dòng)所以它收不到反饋信息,服務(wù)器1的狀態(tài)一直屬于Looking。
- 服務(wù)器2啟動(dòng),給自己投票,同時(shí)與之前啟動(dòng)的服務(wù)器1交換結(jié)果,由于服務(wù)器2的編號(hào)大所以服務(wù)器2勝出,但此時(shí)投票數(shù)沒(méi)有大于半數(shù),所以兩個(gè)服務(wù)器的狀態(tài)依然是LOOKING。
- 服務(wù)器3啟動(dòng),給自己投票,同時(shí)與之前啟動(dòng)的服務(wù)器1,2交換信息,由于服務(wù)器3的編號(hào)最大所以服務(wù)器3勝出,此時(shí)投票數(shù)正好大于半數(shù),所以服務(wù)器3成為領(lǐng)導(dǎo)者,服務(wù)器1,2成為小弟。
- 服務(wù)器4啟動(dòng),給自己投票,同時(shí)與之前啟動(dòng)的服務(wù)器1,2,3交換信息,盡管服務(wù)器4的編號(hào)大,但之前服務(wù)器3已經(jīng)勝出,所以服務(wù)器4只能成為小弟。
- 服務(wù)器5啟動(dòng),后面的邏輯同服務(wù)器4成為小弟。
以上就是本文的全部?jī)?nèi)容,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作能帶來(lái)一定的幫助,同時(shí)也希望多多支持腳本之家!
相關(guān)文章
JDBC簡(jiǎn)介_(kāi)動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
什么是JDBC?這篇文章就為大家詳細(xì)介紹了Java語(yǔ)言中用來(lái)規(guī)范客戶端程序如何來(lái)訪問(wèn)數(shù)據(jù)庫(kù)的應(yīng)用程序接口,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-07-07SWT(JFace)體驗(yàn)之Slider,Scale
SWT(JFace)體驗(yàn)之Slider,Scale實(shí)現(xiàn)代碼。2009-06-06maven如何打包動(dòng)態(tài)環(huán)境變量(包括啟動(dòng)腳本)
這篇文章主要介紹了maven如何打包動(dòng)態(tài)環(huán)境變量(包括啟動(dòng)腳本)問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-04-04IDEA提示:Boolean method ‘xxx‘ is always&nb
這篇文章主要介紹了IDEA提示:Boolean method ‘xxx‘ is always inverted問(wèn)題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-08-08Java計(jì)時(shí)新姿勢(shì)StopWatch的使用方法詳解
這篇文章主要給大家介紹了關(guān)于Java計(jì)時(shí)新姿勢(shì)StopWatch的相關(guān)資料,以及java 中使用StopWatch來(lái)計(jì)算時(shí)間差的方法,對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2022-01-01SpringBoot3整合pageHelper實(shí)現(xiàn)分頁(yè)功能
PageHelper是一個(gè)開(kāi)源的Java分頁(yè)插件,它提供了方便的分頁(yè)查詢功能,適用于大多數(shù)基于Java的持久層框架(如MyBatis、Hibernate等),本文給大家介紹了springboot3整合pageHelper實(shí)現(xiàn)分頁(yè)功能的方法,需要的朋友可以參考下2024-08-08JAVA使用動(dòng)態(tài)代理對(duì)象進(jìn)行敏感字過(guò)濾代碼實(shí)例
這篇文章主要介紹了JAVA使用動(dòng)態(tài)代理對(duì)象進(jìn)行敏感字過(guò)濾代碼實(shí)例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-09-09