在zk服務器集羣啓動過程當中,經QuorumPeerMain中,不光會建立ZooKeeperServer對象,同時會生成QuorumPeer對象,表明了ZooKeeper集羣中的一臺機器。在整個機器運行期間,負責維護該機器的運行狀態,同時會根據狀況發起Leader選舉。下圖是 《從PAXOS到ZOOKEEPER分佈式一致性原理與實踐》的服務器啓動流程算法
QuorumPeer是一個獨立的線程,維護着zk機器的狀態。服務器
@Override public synchronized void start() { loadDataBase(); cnxnFactory.start(); startLeaderElection(); super.start(); }
本次主要介紹的是選舉相關的內容,至於其餘操做能夠看其餘博客。以後的行文都是從startLeaderElection中衍生出來的。網絡
SID:服務器ID,用來標示ZooKeeper集羣中的機器,每臺機器不能重複,和myid的值一直 ZXID:事務ID Vote: 選票,具體的數據結構後面有 Quorum:過半機器數 選舉輪次:logicalclock,zk服務器Leader選舉的輪次
服務器類型:數據結構
在zk中,引入了Leader、Follwer和Observer三種角色。zk集羣中的全部機器經過一個Leader選舉過程來選定一臺被稱爲Leader的機器,Leader服務器爲客戶端提供讀和寫服務。Follower和Observer都可以提供讀服務,惟一的區別在於,Observer機器不參與Leader選舉過程,也不參與寫操做的過半寫成功策略。所以,Observer存在的意義是:在不影響寫性能的狀況下提高集羣的讀性能。架構
服務器狀態:分佈式
+ LOOKING:Leader選舉階段 + FOLLOWING:Follower服務器和Leader保持同步狀態 + LEADING:Leader服務器做爲主進程領導狀態。 + OBSERVING:觀察者狀態,代表當前服務器是Observer,不參與投票
選舉的目的就是選擇出合適的Leader機器,由Leader機器決定事務性的Proposal處理過程,實現類兩階段提交協議(具體是ZAB協議)ide
QuorumPeer的職責就是不斷地檢測當前的zk機器的狀態,執行對應的邏輯,簡單來講,就是根據服務所處的不一樣狀態執行不一樣的邏輯。刪除了一部分邏輯後,代碼以下:性能
@Override public void run() { setName("QuorumPeer" + "[myid=" + getId() + "]" + cnxnFactory.getLocalAddress()); try { while (running) { switch (getPeerState()) { case LOOKING: LOG.info("LOOKING"); try { setBCVote(null); setCurrentVote(makeLEStrategy().lookForLeader()); } catch (Exception e) { LOG.warn("Unexpected exception", e); setPeerState(ServerState.LOOKING); } break; case OBSERVING: try { LOG.info("OBSERVING"); setObserver(makeObserver(logFactory)); observer.observeLeader(); } catch (Exception e) { LOG.warn("Unexpected exception",e ); } finally { observer.shutdown(); setObserver(null); setPeerState(ServerState.LOOKING); } break; case FOLLOWING: try { LOG.info("FOLLOWING"); setFollower(makeFollower(logFactory)); follower.followLeader(); } catch (Exception e) { LOG.warn("Unexpected exception",e); } finally { follower.shutdown(); setFollower(null); setPeerState(ServerState.LOOKING); } break; case LEADING: LOG.info("LEADING"); try { setLeader(makeLeader(logFactory)); leader.lead(); setLeader(null); } catch (Exception e) { LOG.warn("Unexpected exception",e); } finally { if (leader != null) { leader.shutdown("Forcing shutdown"); setLeader(null); } setPeerState(ServerState.LOOKING); } break; } } } finally { LOG.warn("QuorumPeer main thread exited"); } }
當機器處於LOOKING狀態時,QuorumPeer會進行選舉,可是具體的邏輯並非由QuorumPeer來負責的,總體的投票過程獨立出來了,從邏輯執行的角度看,整個過程設計到兩個主要的環節:this
而QuorumPeer中默認使用的選舉算法是FastLeaderElection,以後的分析也是基於FastLeaderElection而言的。線程
在集羣啓動的過程當中,QuorumPeer會根據配置實現不一樣的選舉策略 this.electionAlg = createElectionAlgorithm(electionType);
protected Election createElectionAlgorithm(int electionAlgorithm){ Election le=null; switch (electionAlgorithm) { case 3: QuorumCnxManager qcm = new QuorumCnxManager(this); QuorumCnxManager.Listener listener = qcm.listener; if(listener != null){ listener.start(); le = new FastLeaderElection(this, qcm); } else { LOG.error("Null listener when initializing cnx manager"); } break; default: assert false; } return le; }
若是ClientCnxn是zk客戶端中處理IO請求的管理器,QuorumCnxManager是zk集羣間負責選舉過程當中網絡IO的管理器,在每臺服務器啓動的時候,都會啓動一個QuorumCnxManager,用來維持各臺服務器之間的網絡通訊。
對於每一臺zk機器,都須要創建一個TCP的端口監聽,在QuorumCnxManager中交給Listener來處理,使用的是Socket的阻塞式IO(默認監聽的端口是3888,是在config文件裏面設置的)。在兩兩相互鏈接的過程當中,爲了不兩臺機器之間重複地建立TCP鏈接,zk制定了鏈接的規則:只容許SID打的服務器主動和其餘服務器創建鏈接。實現的方式也比較簡單,在receiveConnection中,服務器會對比與本身創建鏈接的服務器的SID,判斷是否接受請求,若是本身的SID更大,那麼會斷開鏈接,而後本身主動去和遠程服務器創建鏈接。這段邏輯是由Listener來作的,且Listener獨立線程,receivedConnection,創建鏈接後的示意圖:
QuorumCnxManager是鏈接的管家,具體的TCP鏈接交給了Listener,可是對於選票的管理,內部還維護了一系列的隊列:
new ConcurrentHashMap<Long, ArrayBlockingQueue
>();
按照SID分組,分別爲每臺機器分配一個單獨隊列,保證各臺機器之間的消息發放互不影響基本的通訊流程以下:
以上內容主要是創建各臺zk服務器之間的鏈接通訊過程,具體的選舉策略zk抽象成了
public interface Election { public Vote lookForLeader() throws InterruptedException; public void shutdown(); }
上面說過QuorumPeer檢測到當前服務器的狀態是LOOKING的時候,就會進行新一輪的選舉,經過 setCurrentVote(makeLEStrategy().lookForLeader());
也就是FastLeaderElection的lookForLeader來進行初始選擇,實現的方式也很簡單,主要的邏輯在FastLeaderElection.lookForLeader
中實現:
基本流程先說明一下:
根據上面的流程,能夠大概說明一下FasterLeaderElection肯定選票更優的策略:
以上就是zk的默認選票流程,按照ZAB協議的兩種狀態分析: