Zookeeper: 沒有對提案ack的follower 如何了?

  1. 只有Leader支持寫,只能由Leader發起提案proposal , 所以Zookeeper的寫能力是無法擴展的。
  2. leader只要接收到過半follower對提案的 ACK,就發起 COMMIT

那些沒有對提案ACK(也就是提案處理失敗)的follower 怎麼處理了?

首先,follower 正常情況下肯定能對提案進行ACK

如果無法處理提案的follower,會重新進入LOOKING,進行下一輪的選主 + 恢復階段的數據同步(主要是這個來保證數據一致性)

下文以3.4.9版本來分析(3.4.8的實現裏SyncRequestProcessor是會退出程序System.exit)

當follower在處理提案proposal 時發生異常,有2種情況:

  •   SyncRequestProcessor.run() 異常了。

 這裏會設置 ZooKeeperServer.state = ERROR,  那 Follower.followLeader()在末尾處的while循環邏輯判定不再滿足,會break;在QuorumPeer.run()邏輯裏,會驅動它進入LOOKING

// SyncRequestProcessor.run()
...   } catch (Throwable t) {
            handleException(this.getName(), t); // kServer.setState(State.ERROR)
            running = false;
        }
        LOG.info("SyncRequestProcessor exited!");
  •  SyncRequestProcessor執行完成後SendAckRequestProcessor需要ACK回leader,  異常了 。

直接關閉Socket連接,Follower.followLeader()也因異常跳出執行,重新進入LOOKING

    public void processRequest(Request si) {
        if(si.type != OpCode.sync){
            QuorumPacket qp = new QuorumPacket(Leader.ACK, si.hdr.getZxid(), null,
                null);
            try {
                learner.writePacket(qp, false);
            } catch (IOException e) {
                LOG.warn("Closing connection to leader, exception during packet send", e);
                try {
                    if (!learner.sock.isClosed()) {
                        learner.sock.close(); // 異常直接關閉sokcet了
                    }
                } catch (IOException e1) {
                    // Nothing to do, we are shutting things down, so an exception here is irrelevant
                    LOG.debug("Ignoring error closing the connection", e1);
                }
            }
        }
    }

一個請求的全過程

  1. 客戶端org.apache.zookeeper.ZooKeeper 在實例化時創建了 ClientCnxnSocketNIO;
  2. 服務端org.apache.zookeeper.server.ZooKeeperServerMain在啓動時默認創建的是NIOServerCnxnFactory,對應的是NIOServerCnxn 。
     可以通過指定 "zookeeper.serverCnxnFactory" 來選擇使用Netty版本: NettyServerCnxn。

NIOServerCnxnFactory.run() 的邏輯中,在發生連接事件SelectionKey.OP_ACCEPT時,

  1. 維護了相同 InetAddress下保持的ClientCnxn計數,判定相同訪問ip的連接不能超過配置值maxClientCnxns(默認60)。
  2.  創建了專屬這個ClientCnxn的NIOServerCnxn
  1.   ClientCnxnSocketNIO.doIO : 請求從客戶端發出
  2.   NIOServerCnxn.doIO :從SocketChannel讀入數據到緩衝區
  3.  ZooKeeperServer.processPacket : 進入到ZooKeeperServer處理
  4.  ZooKeeperServer.submitRequest: 找到 firstProcessor 來鏈式處理

不同的角色對於ZooKeeperServer有不同的實現。

ZooKeeperServer

在 Zookeeper 的 Leader & Follower 在選主和恢復階段的流程簡析 裏有提及 :在選主後的數據同步過程完成後會恢復對外服務。

這裏會執行ZooKeeperServer.startup() -> .setupRequestProcessors()來初始化Server端的處理鏈Processor。

// LeaderZooKeeperServer
    protected void setupRequestProcessors() {
        RequestProcessor finalProcessor = new FinalRequestProcessor(this);
        RequestProcessor toBeAppliedProcessor = new Leader.ToBeAppliedRequestProcessor(
                finalProcessor, getLeader().toBeApplied);
        commitProcessor = new CommitProcessor(toBeAppliedProcessor,
                Long.toString(getServerId()), false,
                getZooKeeperServerListener());
        commitProcessor.start();
        ProposalRequestProcessor proposalProcessor = new ProposalRequestProcessor(this,
                commitProcessor);
        proposalProcessor.initialize();
        firstProcessor = new PrepRequestProcessor(this, proposalProcessor);
        ((PrepRequestProcessor)firstProcessor).start();
    }
// FollowerZooKeeperServer
    protected void setupRequestProcessors() {
        RequestProcessor finalProcessor = new FinalRequestProcessor(this);
        commitProcessor = new CommitProcessor(finalProcessor,
                Long.toString(getServerId()), true,
                getZooKeeperServerListener());
        commitProcessor.start();
        firstProcessor = new FollowerRequestProcessor(this, commitProcessor);
        ((FollowerRequestProcessor) firstProcessor).start();
        syncProcessor = new SyncRequestProcessor(this,
                new SendAckRequestProcessor((Learner)getFollower()));
        syncProcessor.start();
    }

FollowerZooKeeperServer.submitRequest 

如果外部請求是涉及到數據變動的,則需要將請求轉給Leader。

它的流程是 FollowerRequestProcessor -> CommitProcessor -> FinalRequestProcessor

FollowerRequestProcessor.run():

  1.  執行方法 CommitProcessor.processRequest: 將這個原始的Request請求緩存到CommitProcessor裏的“queuedRequests”隊列裏;隨後被CommitProcessor.run()處理成“nextPending” :表示下一個準備要commit的數據它是帶上了“ServerCnxn”信息的

  2. 判定如果是 create、delete、setData等涉及到數據更改的操作,將數據轉發給leader,指令是 REQUEST

Leader對於每個follower有一個 LearnerHandler.run()  循環處理邏輯。

在末尾處的邏輯裏, 會判定收到follower的 REQUEST請求時執行自己的LeaderZooKeeperServer.submitRequest  ,只是這Request裏沒有“NIOServerCnxn”信息, 而由Leader直接從客戶端收到的請求是有的!

對於Leader分發下的 PROPOSALCOMMIT指令的處理在: Follower.followLeader() -> .processPacket

  • PROPOSAL:     FollowerZooKeeperServer.logRequest  
    1. 依leader發送來的數據創建了一個Request對象,緩存在“pendingTxns”隊列裏, 標識待提交的提案。
    2. SyncRequestProcessor 將提案寫入到ZKDatabase的日誌文件FileTxnSnapLog裏 -> SendAckRequestProcessorACK 給leader.
  •  COMMIT
    1.  FollowerZooKeeperServer.commit: 判定“pendingTxns”隊列裏第一個待提交的Request.zxid 就是本次需要提交的zxid, 則執行CommitProcessor.commit將Request扔到CommitProcessor 的 “committedRequests”待提交隊列。 否則System.exit(12);直接程序退出!
    2.  CommitProcessor -> FinalRequestProcessor:  更新DateTree, 響應客戶端結果。

在 CommitProcessor.run() 的實現裏 :

當“nextPending” 和 "committedRequests" 裏的這個Request指代的是同一個客戶端發出的同一個數據時,“nextPending”的這個帶ServerCnxn內容的Request對象將被FinalRequestProcessor處理。

LeaderZooKeeperServer.submitRequest  

它的流程是: PrepRequestProcessor -> ProposalRequestProcessor -> CommitProcessor -> Leader.ToBeAppliedRequestProcessor -> FinalRequestProcessor

  • PrepRequestProcessor:根據不同的類型創建不同的請求對象,做一些前置校驗:ack、session、path等。
  • 接着是 ProposalRequestProcessor 執行 Leader.propose 發起提案;PROPOSAL 指令會寫到對應每個follower的LearnerHandler發送隊列裏, 異步線程會推送給follower。

LearnerHandler.run() 裏,在收到過半follower回的 ACK 後: Leader.processAck

  1.  發出 COMMIT指令給每一個follower的LearnerHandler
  2.  CommitProcessor -> ToBeAppliedRequestProcessor -> FinalRequestProcessor

FinalRequestProcessor.processRequest

  1. DataTree.processTxn將request的數據應用到ZKDatabase的DateTree
  2.   ZKDatabase.addCommittedProposal  將request添入committedLog中 , 更新的maxCommittedLog = request.zxid
  3.   如果請求信息裏有 ServerCnxn 內容 , 才response客戶端

所以這裏leader是不會直接返回響應follower轉發過來的客戶端請求的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章