服務端有3種運行方式:leader,follower,observer。leader是領導者,一個ZooKeeper集羣同一時刻最多隻能有一個leader。follower是跟隨者,能夠有多個跟隨者。Observer是觀察者,也能夠有多個觀察者。網絡
集羣剛開始的時候沒有leader,這時全部的follower會發起選舉過程,選舉出惟一一個leader。只有選舉出了leader以後集羣才能正式工做,被選舉出的機器的角色則從follower轉換成leader,其他的機器則將本身的leader地址更改成新選舉出的leader地址。observer不參與選舉過程。session
正常的集羣對外提供統一服務接口,無論leader、follower、仍是observer均可以提供對外服務,對於客戶端來講他們是沒有區別的。併發
服務端功能能夠分紅兩大類:選舉相關的和服務相關的,咱們先看服務相關的。框架
服務相關的專門用於響應ZooKeeper客戶端的請求,類名稱爲ZooKeeperServer。它又分紅3個子類,名稱爲:LeaderZooKeeperServer,FollowerZooKeeperServer,ObserverZooKeeperServer。顧名思義它們分別在Leader,Follower,Observer時使用。分佈式
當LeaderZooKeeperServer收到客戶端寫事務請求時,先將請求交給Leader類來發起整個集羣的寫事務流程,這時候Leader類給全部Follower發送PROPOSAL,要求Follower將寫請求持久化到本地磁盤以防丟失,Follower持久化後回覆ACK信息給Leader,當Leader收集到超過半數的ACK回覆時,給Follower發送COMMIT包提交事務執行,最終Leader和Follower都會執行該事務。固然最後是由LeaderZooKeeperServer返回結果給客戶端的。性能
當FollowerZooKeeperServer收到客戶端寫事務請求時,會調用Follower類發送REQUEST消息給Leader。Leader再給全部Follower發送PROPOSAL發起寫事務流程,後面的流程和Leader模式下相似。this
對於寫操做來講,都是要交給leader而後再開始後續流程的。而對於讀操做來講,因爲集羣提供了分佈式一致性存儲,就由本地服務端提供服務,而不用區別服務端的角色。線程
一、怎麼啓動code
服務端的啓動是從QuorumPeerMain開始的,它又幹了什麼事呢?server
QuorumPeerMain讀取配置信息而後啓動QuorumPeer,配置信息由QuorumPeerConfig類負責讀取。而後在ZooKeeperServerMain方法中啓動ZooKeeperServer並等待關閉事件,ZooKeeperServer又啓動ServerCnxn負責和客戶端的網絡通訊。
二、ZooKeeperServer
ZooKeeperServer是服務端基類,它使用NettyServerCnxn做爲底層Socket通訊機制,NettyServerCnxn收到客戶端請求以後會自動調用ZooKeeperServer的prorcessPacket方法來執行客戶端命令,併發送響應包給客戶端。
ZooKeeper服務端定義了3種子類:LeaderZooKeeperServer、FollowerZooKeeperServer和ObserverZooKeeperServer。它們分別用於Leader,Follower和Observer。
ZooKeeper客戶端鏈接服務端時,可能鏈接的是Leader,也可能鏈接的是Follower或者Observer。鏈接到不一樣類型的服務端它的命令請求流程是不太同樣的,特別是寫事務請求。
若是鏈接的是Leader,則Leader會將寫請求生成Proposal包廣播到Follower,通知Follower有寫事務要執行,這時Follower先將寫事務請求保存到磁盤,而後給Leader發送ACK確認。Leader在超過半數確認的狀況下才會真正開始執行寫命令,寫命令完成後Leader再給Follower發送COMMIT命令,通知Follower寫操做已完成,這時Follower也會接着調用CommitProcessor而且最後調用FinalRequestProcessor執行一樣的寫命令,所有完成後Leader和Follower仍然保持相同的ZooKeeper事務,保持集羣數據一致性。
讀操做則不須要這麼複雜,客戶端無論鏈接的是Leader仍是Follower,它發送的讀命令都會直接執行並返回結果給客戶端,所以讀操做的併發性能要高不少。
ZooKeeper客戶端和服務端之間數據交互流程示意圖以下:
它們之間採用Netty框架做爲底層Socket鏈接,NettyServerFactory是服務端鏈接管理池,爲每一個客戶端建立一個NettyServer,NettyServer接收到客戶端命令請求以後會調用ZooKeeperServer的processPacket方法處理該客戶端請求。
2.1 processPacket
當ZooKeeperServer的底層NettyServerCnxn接收到客戶端的Request時,該方法被NettyServerCnzx的主接收線程調用。ZooKeeperServer而後開始處理Request。
該方法的主要代碼:
public void processPacket(ServerCnxn cnxn, ByteBuffer incomingBuffer) throws IOException { // We have the request, now process and setup for next InputStream bais = new ByteBufferInputStream(incomingBuffer); BinaryInputArchive bia = BinaryInputArchive.getArchive(bais); RequestHeader h = new RequestHeader(); h.deserialize(bia, "header"); Request si = new Request(cnxn, cnxn.getSessionId(), h.getXid(), h.getType(), incomingBuffer, cnxn.getAuthInfo()); si.setOwner(ServerCnxn.me); setLocalSessionFlag(si); submitRequest(si); }
最後一行的submitRequest方法調用firstProcessor開始處理Request,firstProcessor是處理請求包的入口方法,在Leader/Follower等不一樣的模式下其firstProcessor是不一樣的。主要代碼以下:
public void submitRequest(Request si) { if (firstProcessor == null) { touch(si.cnxn); boolean validpacket = Request.isValid(si.type); if (validpacket) { firstProcessor.processRequest(si); if (si.cnxn != null) { incInProcess(); } } } }
2.2 processTxn
處理客戶端的寫操做請求,經過ZKDatabase類實現具體邏輯。
該方法由FinalRequestProcessor調用,客戶端讀操做不在這裏實現,而是放在FinalRequestProcessor中直接代碼實現。固然讀操做最終也是經過ZkDatabase的相關方法實現具體邏輯的。
處理寫操做請求,寫操做涉及到集羣狀態同步,和讀操做有很大的區別,寫操做提供了統一的入口方法processTxn來處理。
該方法的主要代碼以下:
private ProcessTxnResult processTxn(Request request,TxnHeader hdr, Record txn) { ProcessTxnResult rc; int opCode = request != null ? request.type : hdr.getType(); long sessionId = request != null ? request.sessionId : hdr.getClientId(); if (hdr != null) { rc = getZKDatabase().processTxn(hdr, txn); } else { rc = new ProcessTxnResult(); } if (opCode == OpCode.createSession) { if (hdr != null && txn instanceof CreateSessionTxn) { CreateSessionTxn cst = (CreateSessionTxn) txn; sessionTracker.addGlobalSession(sessionId, cst.getTimeOut()); } else if (request != null && request.isLocalSession()) { request.request.rewind(); int timeout = request.request.getInt(); request.request.rewind(); sessionTracker.addSession(request.sessionId, timeout); } else { LOG.warn("*****>>>>> Got " + txn.getClass() + " " + txn.toString()); } } else if (opCode == OpCode.closeSession) { sessionTracker.removeSession(sessionId); } return rc; }
2.3 NettyServerCnxn
NettyServerCnxn是ZooKeeper客戶端和服務端底層通訊的接口,採用Netty組件做爲Socket接口。客戶端提交的命令會經過NettyServerCnxn到達服務端,而後觸發NettyServerCnxn中的receiveMessage方法,該方法再調用ZooKeeperServer的processPacket方法處理接收到的請求包。其中代碼片斷以下:
public void receiveMessage(ChannelBuffer message) { if (initialized) { zks.processPacket(this, bb); if (zks.shouldThrottle(outstandingCount.incrementAndGet())) { disableRecvNoWait(); } } else { zks.processConnectRequest(this, bb); initialized = true; } }
當客戶端第一次鏈接到服務端時,會調用ZooKeeperServer的processConnectRequest方法處理;其餘狀況下會調用ZooKeeperServer的processPacket方法處理客戶端請求。具體的處理邏輯可參見ZooKeeperServer及其子類(LeaderZooKeeperServer、FollowerZooKeeperServer、ObserverZooKeeperServer)的相關代碼。