ZooKeeper系列之(十二):服務端實現機制

服務端有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)的相關代碼。

相關文章
相關標籤/搜索