ZKクラスタリーダー選挙コード読み取り

序文

ZooKeeperのはザブの合意を達成するために、独自のスタンバイモデル、すなわちリーダーと学習者(オブザーバー+フォロワー)を持っている、いくつかのケースがあります選挙のリーダーである必要が

  • ケース1:クラスタの起動プロセスのリーダーを選出する必要があります
  • シナリオ2:クラスタが正常に起動した後、障害が原因のリーダーはリーダー選出されるために必要な、ハングアップ
  • シナリオ3:フォロワークラスタの数が半分試験では十分ではない、リーダーは新しいリーダーを選出、自分自身をハングアップします
  • シナリオ4:クラスタが正しく動作している、新規追加フォロワー

このブログ、読書源の追跡のこれらの4つの側面

プログラムのエントリ

QuorumPeer.java各ノードは、その中に、サーバクラスタに対応するstart()現在のノードは、次のソースコードの起動動作を完了するために、プロセス。

    // todo 进入了 QuorumPeer(意为仲裁人数)类中,可以把这个类理解成集群中的某一个点
    @Override
    public synchronized void start() {
        // todo 从磁盘中加载数据到内存中
        loadDataBase();

        // todo 启动上下文的这个工厂,他是个线程类, 接受客户端的请求
        cnxnFactory.start();

        // todo 开启leader的选举工作
        startLeaderElection();

        // todo 确定服务器的角色, 启动的就是当前类的run方法在900行
        super.start();
    }

最初のloadDataBase();目的は、メモリにクラスタからデータを復元することです

第二は、cnxnFactory.start();現在のノードにより送信されたクライアント(Javaコード又はコンソール)からの接続要求を受け入れることができるです

第三は、startLeaderElection();選挙のリーダーになって実際には、彼はヘルパークラスのシリーズを初期化され、リーダーの選挙への援助は、選挙で実際にはありません

現在のクラスがquorumPeerスレッドクラス自体でZKThreadは、継承super.start();そのrunメソッドを開始することで、彼のRunメソッドでwhileループがあり、手続きの段階から始まり、すべてのノードのデフォルト値であるLookingので、枝に入りますが、この中には本物のリーダー選挙を分けるだろう

概要

プログラムの入り口の導入から、だろうこの記事で見ることができます見に焦点を当ててstartLeaderElection();行われていたものか、そして中にlookingどのように選挙分岐リーダー

ケース1:クラスタを起動する過程で、新しいリーダーの選出

startLeaderElection();ソースコードは以下の方法で、彼は主に2つのことを行います

  • このタイプのQuorumPeer.java変数メンテナンス(volatile private Vote currentVote;)の初期化
  • createElectionAlgorithm()リーダー選挙法の作成

    実際には、今までは、有効期限が切れていないアルゴリズムを残して、つまり、fastLeaderElection

   // TODO 开启投票选举Leader的工作
    synchronized public void startLeaderElection() {
        try {
            // todo 创建了一个封装了投票结果对象   包含myid 最大的zxid 第几轮Leader
            // todo 先投票给自己
            // todo 跟进它的构造函数
            currentVote = new Vote(myid, getLastLoggedZxid(), getCurrentEpoch());
        } catch(IOException e) {
            RuntimeException re = new RuntimeException(e.getMessage());
            re.setStackTrace(e.getStackTrace());
            throw re;
        }
        for (QuorumServer p : getView().values()) {
            if (p.id == myid) {
                myQuorumAddr = p.addr;
                break;
            }
        }
        if (myQuorumAddr == null) {
            throw new RuntimeException("My id " + myid + " not in the peer list");
        }
        if (electionType == 0) {
            try {
                udpSocket = new DatagramSocket(myQuorumAddr.getPort());
                responder = new ResponderThread();
                responder.start();
            } catch (SocketException e) {
                throw new RuntimeException(e);
            }
        }
        // todo  创建一个领导者选举的算法,这个算法还剩下一个唯一的实现 快速选举
        this.electionAlg = createElectionAlgorithm(electionType);
    }

フォローアップを続行しcreateElectionAlgorithm(electionType)、この方法では、以下の3つのことを行います

  • 作成QuorumCnxnManager
  • 作りますListenner
  • 作りますFastLeaderElection
protected Election createElectionAlgorithm(int electionAlgorithm){
    Election le=null;
            
    //TODO: use a factory rather than a switch
    switch (electionAlgorithm) {
    case 0:
        le = new LeaderElection(this);
        break;
    case 1:
        le = new AuthFastLeaderElection(this);
        break;
    case 2:
        le = new AuthFastLeaderElection(this, true);
        break;
    case 3:
        // todo 创建CnxnManager 上下文的管理器
        qcm = createCnxnManager();
        QuorumCnxManager.Listener listener = qcm.listener;
        if(listener != null){

            // todo 在这里将listener 开启
            listener.start();
            // todo  实例化领导者选举的算法
            le = new FastLeaderElection(this, qcm);
        } else {
            LOG.error("Null listener when initializing cnx manager");
        }
        break;

選挙の環境を準備します

QuorumManager

https://img2018.cnblogs.com/blog/1496926/201910/1496926-20191004181514119-1851057732.png

図は、クラス図のQuorumCnxManagerで、見て、それがある場合を除き、6つの内部クラスを持っているMessageスレッドクラスとりわけが個別に実行されています

極めて重要な役割を持っているこのクラスは、それがクラスタ内のすべてのノードで補助クラスを共有され、そして最終的に何をすべきか役割ということ?私たちはお互いに投票しなければならないとして、選挙のリーダーは、投票のうち、解像度なので、ちょうど言う推測し、そのクラスタではありませんよ各ポイントは、あなたがいることを、任意の2つの間の接続を確立する必要がありQuorumCnxManager、それは、クラスタ内の様々なポイントでの通信を維持する責任があります

これは2つのキュー、以下のソースを維持する、最初のキューに格納されているConcurrentHashMapノードでは、のMyIDキー(又はSERVERID)であり、それは投票サーバーキューに送信された値が他を格納することは理解されるであろう

第二は、MSGを介して送信するために、他のサーバーのキューを受信します

// todo key=serverId(myid)   value = 保存着当前服务器向其他服务器发送消息的队列
final ConcurrentHashMap<Long, ArrayBlockingQueue<ByteBuffer>> queueSendMap;

// todo 接收到的所有数据都在这个队列中
public final ArrayBlockingQueue<Message> recvQueue;

https://img2018.cnblogs.com/blog/1496926/201910/1496926-20191004181513574-985979791.png

上の写真は手描きでQuorumCnxManager.javaシステム図、その内部を見ることができる3つのスレッドクラスの最も直感的、かつスレッドクラスのrun()メソッドの3とは全く何もしていたこと?

RUN()SendWorker、次いで、それをキュー対応SIDに従って抽出し、現在のノードがキューにデータを送信することがわかります


    public void run() {
            threadCnt.incrementAndGet();
            try {
                ArrayBlockingQueue<ByteBuffer> bq = queueSendMap.get(sid);
                if (bq == null || isSendQueueEmpty(bq)) {
                   ByteBuffer b = lastMessageSent.get(sid);
                   if (b != null) {
                       LOG.debug("Attempting to send lastMessage to sid=" + sid);
                       send(b);
                   }
                }
            } catch (IOException e) {
                LOG.error("Failed to send last message. Shutting down thread.", e);
                this.finish();
            }
            
            try {
                while (running && !shutdown && sock != null) {

                    ByteBuffer b = null;
                    try {
                        // todo 取出任务所在的队列
                        ArrayBlockingQueue<ByteBuffer> bq = queueSendMap.get(sid);

                        if (bq != null) {
                            // todo 将bq,添加进sendQueue
                            b = pollSendQueue(bq, 1000, TimeUnit.MILLISECONDS);
                        } else {
                            LOG.error("No queue of incoming messages for " +
                                      "server " + sid);
                            break;
                        }

                        if(b != null){
                            lastMessageSent.put(sid, b);
                            // todo
                            send(b);
                        }
                    } catch (InterruptedException e) {
                        LOG.warn("Interrupted while waiting for message on queue",
                                e);
                    }
                }

RecvWorkerのrunメソッドの中に、次にMSG、受信MSG、recvQueueキュー

        public void run() {
            threadCnt.incrementAndGet();
            try {
                while (running && !shutdown && sock != null) {
                    /**
                     * Reads the first int to determine the length of the
                     * message
                     */
                    int length = din.readInt();
                    if (length <= 0 || length > PACKETMAXSIZE) {
                        throw new IOException(
                                "Received packet with invalid packet: "
                                        + length);
                    }
                    /**
                     * Allocates a new ByteBuffer to receive the message
                     */
                    // todo 从数组中把数据读取到数组中
                    byte[] msgArray = new byte[length];
                    din.readFully(msgArray, 0, length);
                    // todo 将数组包装成ByteBuf
                    ByteBuffer message = ByteBuffer.wrap(msgArray);
                    // todo 添加到RecvQueue中
                    addToRecvQueue(new Message(message.duplicate(), sid));
                }

https://img2018.cnblogs.com/blog/1496926/201910/1496926-20191004181512961-831337433.png]

RUN Listenner() それは互いの間の接続を確立するために、我々は構成ファイルで構成された通信鍵クラスタを使用(3888以上)ポートを使用します

また、クラスタ内の種々の点の間の従来の通信ソケット通信を使用することを見出しました

        InetSocketAddress addr;
            while((!shutdown) && (numRetries < 3)){
                try {
                    // todo 创建serversocket
                    ss = new ServerSocket();
                    ss.setReuseAddress(true);
                    if (listenOnAllIPs) {
                        int port = view.get(QuorumCnxManager.this.mySid)
                            .electionAddr.getPort();
                        //todo 它取出来的地址就是address就是我们在配置文件中配置集群时添加进去的 port 3888...
                        addr = new InetSocketAddress(port);
                    } else {
                        addr = view.get(QuorumCnxManager.this.mySid)
                            .electionAddr;
                    }
                    LOG.info("My election bind port: " + addr.toString());
                    setName(view.get(QuorumCnxManager.this.mySid)
                            .electionAddr.toString());
                    // todo 绑定端口
                    ss.bind(addr);
                    while (!shutdown) {
                        // todo 阻塞接受其他的服务器发起连接
                        Socket client = ss.accept();
                        setSockOpts(client);
                        LOG.info("Received connection request "
                                + client.getRemoteSocketAddress());
                       // todo  如果启用了仲裁SASL身份验证,则异步接收和处理连接请求
                        // todo  这是必需的,因为sasl服务器身份验证过程可能需要几秒钟才能完成,这可能会延迟下一个对等连接请求。
                        if (quorumSaslAuthEnabled) {
                            // todo 异步接受一个连接
                            receiveConnectionAsync(client);
                        } else {
                            // todo 跟进这个方法
                            receiveConnection(client);
                        }
                        numRetries = 0;
                    }

バックにソースコードをフォローアップしていき、再インターセプト源が完了以下、プロセス作成を開始リスナー後、Listennerはマークが同時に任意の2つの間の通信能力を確立するために、クラスタ内の各ノードとの間に有する開始スレッドクラスListenner、上記のコードでの実行()メソッドQuorumPeer.javacreateElectionAlgorithm()QuorumCnxManager

FastLeaderElection

Listennerを開始した後、オブジェクトのリーダー選挙アルゴリズムをインスタンス化し始めましたnew FastLeaderElection(this, qcm)

    ...
     break;
        case 3:
            // todo 创建CnxnManager 上下文的管理器
            qcm = createCnxnManager();
   QuorumCnxManager.Listener listener = qcm.listener;
            if(listener != null){
                // todo 在这里将listener 开启
                listener.start();
                // todo  实例化领导者选举的算法
                le = new FastLeaderElection(this, qcm);
            } else {
                LOG.error("Null listener when initializing cnx manager");
            }

以下はFasterElectionクラス図は、

https://img2018.cnblogs.com/blog/1496926/201910/1496926-20191004181512482-258189385.png

直接視覚的にその3つの内部クラスを参照してください

  • (2つの雌ねじクラスを持っている)メサジェ
    • WorkerRecriver
      • 責任
    • WorkerSender
  • 通知
    • 新しいノードの状態は、その後、ポーリングの解像度を見ているときに、他のノードが使用される受信した後に通常、開始Notification、独自の信頼できるリーダーを伝えるために
  • ToSend
    • 他のノードから互いに、またはメッセージを送信します。メッセージが通知であってもよいし、通知がACKを受信することができます

対応しQuorumCnxManager、キューのメンテナンスの2種類FasterElection1、以下の2つのキューを維持するために、同様の注意を払ってsendqueue他がありますrecvqueue

LinkedBlockingQueue<ToSend> sendqueue;
LinkedBlockingQueue<Notification> recvqueue;

具体的にどのように再生するには?下記に示すように、

https://img2018.cnblogs.com/blog/1496926/201910/1496926-20191004181511814-142904818.png

ノードは、外部投票の処理を開始されたときに入金されFasterElectionsendqueue、そして次に後投票、NIO、逆の過程を経て送出する他のノードが受信することにより受信し、その後にこのキューMSGはであり続けるだろう雌ねじクラス保存するためにとらQuorumCnxManagersendWorkerQuorumCnxManagerrecvWorkerQuorumCnxManagerrecvQueue中FasterElectionworkerRecviverFasterElectionrecvqueue中

見ることができるコードを追跡することによって、二つの内部スレッドのメッセージが開きする方法として、デーモンスレッドであります

Messenger(QuorumCnxManager manager) {
    // todo WorderSender 作为一条新的线程
    this.ws = new WorkerSender(manager);

    Thread t = new Thread(this.ws,
            "WorkerSender[myid=" + self.getId() + "]");
    t.setDaemon(true);
    t.start();

    //todo------------------------------------
    // todo WorkerReceiver  作为一条新的线程
    this.wr = new WorkerReceiver(manager);

    t = new Thread(this.wr,
            "WorkerReceiver[myid=" + self.getId() + "]");
    t.setDaemon(true);
    t.start();
}

概要

実際には、選挙のリーダーのための準備があることを意味し、完成されたコードは、ここを参照してください、この方法は、環境リーダーシップの選挙への準備ができて、それが地図上にありますquorumPeer.javastart()startLeaderElection();


選挙の本当の始まり

ここを見てquorumPeer.java、クラスが起動し、このスレッドでの一部run()傍受方法を、私たちはその懸念しているlookForLeader()方法

while (running) {
switch (getPeerState()) {
    /**
     * todo 四种可能的状态, 经过了leader选举之后, 不同的服务器就有不同的角色
     * todo 也就是说,不同的服务器会会走动下面不同的分支中
     * LOOKING 正在进行领导者选举
     * Observing
     * Following
     * Leading
     */
case LOOKING:
    // todo 当为Looking状态时,会进入领导者选举的阶段
    LOG.info("LOOKING");

    if (Boolean.getBoolean("readonlymode.enabled")) {
        LOG.info("Attempting to start ReadOnlyZooKeeperServer");

        // Create read-only server but don't start it immediately
        // todo 创建了一个 只读的server但是不着急立即启动它
        final ReadOnlyZooKeeperServer roZk = new ReadOnlyZooKeeperServer(
                logFactory, this,
                new ZooKeeperServer.BasicDataTreeBuilder(),
                this.zkDb);

        // Instead of starting roZk immediately, wait some grace(优雅) period(期间) before we decide we're partitioned.
        // todo 为了立即启动roZK ,在我们决定分区之前先等一会
        // Thread is used here because otherwise it would require changes in each of election strategy classes which is
        // unnecessary code coupling.
        //todo  这里新开启一条线程,避免每一个选举策略类上有不同的改变 而造成的代码的耦合
        Thread roZkMgr = new Thread() {
            public void run() {
                try {
                    // lower-bound grace period to 2 secs
                    sleep(Math.max(2000, tickTime));
                    if (ServerState.LOOKING.equals(getPeerState())) {
                        // todo 启动上面那个只读的Server
                        roZk.startup();
                    }
                } catch (InterruptedException e) {
                    LOG.info("Interrupted while attempting to start ReadOnlyZooKeeperServer, not started");
                } catch (Exception e) {
                    LOG.error("FAILED to start ReadOnlyZooKeeperServer", e);
                }
            }
        };
        try {
            roZkMgr.start();
            setBCVote(null);
            // todo 上面的代码都不关系,直接看它的 lookForLeader()方法
            // todo 直接点进去,进入的是接口,我们看它的实现类
            setCurrentVote(makeLEStrategy().lookForLeader());
        } catch (Exception e) {
            LOG.warn("Unexpected exception",e);
            setPeerState(ServerState.LOOKING);
        } finally {
            // If the thread is in the the grace period, interrupt
            // to come out of waiting.
            roZkMgr.interrupt();
            roZk.shutdown();
        }

ここでlookForLeader()、ソースコードの解釈は
、このアプローチは実際にはかなり長いですが、私たちはウェブ上で見つけることができるので、この方法では、それはあなたがこの方法からこまごまとしたのリーダーのための選挙を総括、本当に重要である真実を伝えるために

最初のポイント:すべての投票は彼らの最初の投票、それはっきりキャストしますnew Vote(myid, getLastLoggedZxid(), getCurrentEpoch());一緒に自分のMYID、最大zxid、およびパッケージの最初のセッションは、しかし、1つの詳細がある、それは自分自身で鋳造された、または存在しますこれは、この投票の独自の情報は、ソケットを介して他のノードに送信しています

で他の人の投票を受け入れるThreadクラスに追加投票するあなた自身のための投票は、このルートを行くのではなく、チケットに直接追加することを選択していない場合、キューキューQuorumManagerrecvWorkerrecvQueuerecvQueue

次のコードの行があり、HashMap<Long, Vote> recvset = new HashMap<Long, Vote>();このマップは、各ノードは、メールボックスを維持しますが、そのチケットのために、自分の票を保存することができる、または他の誰かの投票を自分のチケットを、または他の誰かが他の誰かのチケットに投票する小さなメールボックスとして理解することができますまたは自国民の投票のための投票は、投票のこの統計的なメールボックスの数は、特定のノードが、ソースとして、リーダーとなるメールボックス内の情報を使用できるかどうかを決定することができます

    // todo 根据别人的投票,以及自己的投票判断,本轮得到投票的集群能不能成为leader
    if (termPredicate(recvset,
            new Vote(proposedLeader, proposedZxid,
                    logicalclock.get(), proposedEpoch))) {
        // todo 到这里说明接收到投票的机器已经是准leader了

        // Verify if there is any change in the proposed leader
        // todo 校验一下, 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) {
                // todo 判断自己是不是leader, 如果是,更改自己的状态未leading , 否则根据配置文件确定状态是 Observer 还是Follower
                // todo leader选举出来后, QuorumPeer中的run方法中的while再循环,不同角色的服务器就会进入到 不同的分支
                self.setPeerState((proposedLeader == self.getId()) ?
                        ServerState.LEADING : learningState());
        
                Vote endVote = new Vote(proposedLeader,
                        proposedZxid,
                        logicalclock.get(),
                        proposedEpoch);
                leaveInstance(endVote);
                return endVote;
            }
        }

ではtermPredicate()、次の論理機能、self.getQuorumVerifier().containsQuorum(set);その実装は、テスト中のメカニズムの半分は、より多くのより、実際には、以下の結論は、ノードがクラスタ内のノードの半分以上で票を持っている、とするとき、それは彼らの状況を修正するということです大手、彼らのニーズに応じて他のノードは状態になりますfollowing或者observing

    public boolean containsQuorum(Set<Long> set){
        return (set.size() > half);
    }

投票回数マーキング、クロックを維持logicalclock彼は変数の型をAutomicLongされ、彼はそれが何であるかを使用していましたか?ロジックは次のコードとして見ることができる、つまり、とき彼らのクロック受信現在のクロック時間よりも、投票に、彼は他の理由で投票用紙を逃したので、自身のクロックを更新、または再キャスト彼らの投票裁判官のほかに可能性があることを示す、共感クロックは、この投票は意味がないことを示し、彼らの現在のクロックよりも少ない票を受け取った場合直接無視捨てます

   if (n.electionEpoch > logicalclock.get()) {
                                // todo 将自己的时钟调整为更新的时间
                                logicalclock.set(n.electionEpoch);
                                // todo 清空自己的投票箱
                                recvset.clear();

クラスのチケットパッケージに封入ノードどんな情報?解析することにより、だから、自分自身のために投票したり、他の誰かのために投票すると判断された内容に応じて?情報Zxid、MYID、エポックが多い場合は大きな優先リーダーになるエポック一般的な同じエポックが再び同じzxidあればそれほど大きなzxidの優先順位は、リーダーになるだろう、優先順位は大MYIDの指導者になりました

リーダーとしての自分よりも他のより適切にノードを確認し、再投票となり、選挙がより適しているノード

完全なソースコード

// todo 当前进入的是FastLeaderElection.java的实现类
public Vote lookForLeader() throws InterruptedException {
try {
    // todo 创建用来选举Leader的Bean
    self.jmxLeaderElectionBean = new LeaderElectionBean();

    MBeanRegistry.getInstance().register(
            self.jmxLeaderElectionBean, self.jmxLocalPeerBean);
} catch (Exception e) {
    LOG.warn("Failed to register with JMX", e);
    self.jmxLeaderElectionBean = null;
}
if (self.start_fle == 0) {
    self.start_fle = Time.currentElapsedTime();
}
try {
    // todo 每台服务器独有的投票箱 , 存放其他服务器投过来的票的map
    // todo long类型的key (sid)标记谁给当前的server投的票   Vote类型的value 投的票
    HashMap<Long, Vote> recvset = new HashMap<Long, Vote>();

    HashMap<Long, Vote> outofelection = new HashMap<Long, Vote>();

    int notTimeout = finalizeWait;

    synchronized (this) {
        //todo Automic 类型的时钟
        logicalclock.incrementAndGet();
        //todo 一开始启动时,入参位置的值都取自己的,相当于投票给自己
        updateProposal(getInitId(), getInitLastLoggedZxid(), getPeerEpoch());
    }

    LOG.info("New election. My id =  " + self.getId() +
            ", proposed zxid=0x" + Long.toHexString(proposedZxid));
    // todo 发送出去,投票自己
    sendNotifications();

    /*
     * Loop in which we exchange notifications until we find a leader
     */
    // todo 如果自己一直处于LOOKING的状态,一直循环
    while ((self.getPeerState() == ServerState.LOOKING) && (!stop)) {
        /*
         * Remove next notification from queue, times out after 2 times
         * the termination time
         */
        //todo  尝试获取其他服务器的投票的信息

        // todo 从接受消息的队列中取出一个msg(这个队列中的数据就是它投票给自己的票)
        // todo 在QuorumCxnManager.java中 发送的投票的逻辑中,如果是发送给自己的,就直接加到recvQueue,而不经过socket
        // todo 所以它在这里是取出了自己的投票
        Notification n = recvqueue.poll(notTimeout, TimeUnit.MILLISECONDS);

        /*
         * Sends more notifications if haven't received enough.
         * Otherwise processes new notification.
         */
        // todo 第一轮投票这里不为空
        if (n == null) {
            // todo 第二轮就没有投票了,为null, 进入这个分支
            // todo 进行判断 ,如果集群中有三台服务器,现在仅仅启动一台服务器,还剩下两台服务器没启动
            // todo 那就会有3票, 其中1票直接放到 recvQueue , 另外两票需要发送给其他两台机器的逻辑就在这里判断
            // todo 验证是通不过的,因为queueSendMap中的两条队列都不为空
            if (manager.haveDelivered()) {
                sendNotifications();
            } else {
                // todo 进入这个逻辑
                manager.connectAll();
            }

            /*
             * Exponential backoff
             */
            int tmpTimeOut = notTimeout * 2;
            notTimeout = (tmpTimeOut < maxNotificationInterval ?
                    tmpTimeOut : maxNotificationInterval);
            LOG.info("Notification time out: " + notTimeout);
        } else if (validVoter(n.sid) && validVoter(n.leader)) {
            // todo 收到了其他服务器的投票信息后,来到下面的分支中处理
            /*
             * Only proceed if the vote comes from a replica in the
             * voting view for a replica in the voting view.
             * todo 仅当投票来自投票视图中的副本时,才能继续进行投票。
             */
            switch (n.state) {
                case LOOKING:
                    // todo 表示获取到投票的服务器的状态也是looking

                    // If notification > current, replace and send messages out
                    // todo 对比接收到的头片的 epoch和当前时钟先后

                    // todo 接收到的投票 > 当前服务器的时钟
                    // todo 表示当前server在投票过程中可能以为故障比其他机器少投了几次,需要重新投票
                    if (n.electionEpoch > logicalclock.get()) {
                        // todo 将自己的时钟调整为更新的时间
                        logicalclock.set(n.electionEpoch);
                        // todo 清空自己的投票箱
                        recvset.clear();
                        // todo 用别人的信息和自己的信息对比,选出一个更适合当leader的,如果还是自己适合,不作为, 对方适合,修改投票,投 对方
                        if (totalOrderPredicate(n.leader, n.zxid, n.peerEpoch,
                                getInitId(), getInitLastLoggedZxid(), getPeerEpoch())) {
                            updateProposal(n.leader, n.zxid, n.peerEpoch);
                        } else {
                            updateProposal(getInitId(),
                                    getInitLastLoggedZxid(),
                                    getPeerEpoch());
                        }
                        sendNotifications();

                        // todo 接收到的投票 < 当前服务器的时钟
                        // todo 说明这个投票已经不能再用了
                    } else if (n.electionEpoch < logicalclock.get()) {
                        if (LOG.isDebugEnabled()) {
                            LOG.debug("Notification election epoch is smaller than logicalclock. n.electionEpoch = 0x"
                                    + Long.toHexString(n.electionEpoch)
                                    + ", logicalclock=0x" + Long.toHexString(logicalclock.get()));
                        }
                        break;
                        // todo 别人的投票时钟和我的时钟是相同的
                        // todo 满足 totalOrderPredicate 后,会更改当前的投票,重新投票
                        /**
                         *   在 totalOrderPredicate 比较两者之间谁更满足条件
                         *   ((newEpoch > curEpoch) ||
                         *   ((newEpoch == curEpoch) &&
                         *   ((newZxid > curZxid) ||
                         *   ((newZxid == curZxid) &&
                         *   (newId > curId)))));
                         */
                        // todo 返回true说明 对方更适合当leader
                    } else if (totalOrderPredicate(n.leader, n.zxid, n.peerEpoch,
                            proposedLeader, proposedZxid, proposedEpoch)) {
                        updateProposal(n.leader, n.zxid, n.peerEpoch);
                        sendNotifications();
                    }

                    if (LOG.isDebugEnabled()) {
                        LOG.debug("Adding vote: from=" + n.sid +
                                ", proposed leader=" + n.leader +
                                ", proposed zxid=0x" + Long.toHexString(n.zxid) +
                                ", proposed election epoch=0x" + Long.toHexString(n.electionEpoch));
                    }
                    // todo 将自己的投票存放到投票箱子中
                    recvset.put(n.sid, new Vote(n.leader, n.zxid, n.electionEpoch, n.peerEpoch));

                    // todo 根据别人的投票,以及自己的投票判断,本轮得到投票的集群能不能成为leader
                    if (termPredicate(recvset,
                            new Vote(proposedLeader, proposedZxid,
                                    logicalclock.get(), proposedEpoch))) {
                        // todo 到这里说明接收到投票的机器已经是准leader了

                        // Verify if there is any change in the proposed leader
                        // todo 校验一下, leader有没有变动
                        while ((n = recvqueue.poll(finalizeWait,
                                TimeUnit.MILLISECONDS)) != null) {
                            if (totalOrderPredicate(n.leader, n.zxid, n.peerEpoch,
                                    proposedLeader, proposedZxid, proposedEpoch)) {
                                recvqueue.put(n);
                                break;
                            }
                        }

                        /*
                         * This predicate is true once we don't read any new
                         * relevant message from the reception queue
                         */
                        if (n == null) {
                            // todo 判断自己是不是leader, 如果是,更改自己的状态未leading , 否则根据配置文件确定状态是 Observer 还是Follower
                            // todo leader选举出来后, QuorumPeer中的run方法中的while再循环,不同角色的服务器就会进入到 不同的分支
                            self.setPeerState((proposedLeader == self.getId()) ?
                                    ServerState.LEADING : learningState());

                            Vote endVote = new Vote(proposedLeader,
                                    proposedZxid,
                                    logicalclock.get(),
                                    proposedEpoch);
                            leaveInstance(endVote);
                            return endVote;
                        }
                    }
                    break;
                case OBSERVING:
                    // todo 禁止Observer参加投票
                    LOG.debug("Notification from observer: " + n.sid);
                    break;
                case FOLLOWING:
                case LEADING:
                    /*
                     * Consider all notifications from the same epoch
                     * together.
                     */
                    if (n.electionEpoch == logicalclock.get()) {
                        recvset.put(n.sid, new Vote(n.leader,
                                n.zxid,
                                n.electionEpoch,
                                n.peerEpoch));

                        if (ooePredicate(recvset, outofelection, n)) {
                            self.setPeerState((n.leader == self.getId()) ?
                                    ServerState.LEADING : learningState());

                            Vote endVote = new Vote(n.leader,
                                    n.zxid,
                                    n.electionEpoch,
                                    n.peerEpoch);
                            leaveInstance(endVote);
                            return endVote;
                        }
                    }

                    /*
                     * Before joining an established ensemble, verify
                     * a majority is following the same leader.
                     */
                    outofelection.put(n.sid, new Vote(n.version,
                            n.leader,
                            n.zxid,
                            n.electionEpoch,
                            n.peerEpoch,
                            n.state));

                    if (ooePredicate(outofelection, outofelection, n)) {
                        synchronized (this) {
                            logicalclock.set(n.electionEpoch);
                            self.setPeerState((n.leader == self.getId()) ?
                                    ServerState.LEADING : learningState());
                        }
                        Vote endVote = new Vote(n.leader,
                                n.zxid,
                                n.electionEpoch,
                                n.peerEpoch);
                        leaveInstance(endVote);
                        return endVote;
                    }
                    break;
                default:
                    LOG.warn("Notification state unrecognized: {} (n.state), {} (n.sid)",
                            n.state, n.sid);
                    break;
            }
        } else {
            if (!validVoter(n.leader)) {
                LOG.warn("Ignoring notification for non-cluster member sid {} from sid {}", n.leader, n.sid);
            }
            if (!validVoter(n.sid)) {
                LOG.warn("Ignoring notification for sid {} from non-quorum member sid {}", n.leader, n.sid);
            }
        }
    }
    return null;

各ノードの上記判断はに戻るために、異なる役割を選出することができた後、循環の時間、もはや入力しないコードブロックを、彼らの異なる役割に従ってその職務を遂行するために、異なる初期化スタートを実行QuorumPeer.javarun()case LOOKING:

シナリオ2:クラスタが正常に起動した後、リーダーがあるため、新しいリーダーを選出する障害のハングアップ

例二選挙のリーダーは、クラスタが正常に起動した後、リーダーは新しいリーダーを選出する障害によるハングアップ

それが何であるかのロジックのこの部分は?

リーダーがハングアップしますが、サーバーの役割はまだフォロワーの実行になりますが、QuorumPeer.javarun()それは実行時にループ方式は、一方で、無限のfollower.followLeader();方法のリーダーを見つけることができない、異常になり、最終的な実装finallyコード・ブロック・ロジックは、あなたがそれを見ることができます見ていると、そのステータスを変更した後、再選リーダー

   break;
        case FOLLOWING:
            // todo server 当选follow角色
            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;

シナリオ3:フォロワークラスタの数が半分試験では十分ではない、リーダーは自分自身をハングアップし、新しい指導者を選出します

ケース3:台湾フォロワーのチェック機構の半分以上を満たすことができない残りのフォロワーは、したがって、リーダー再選されます際にハングアップし、その後、クラスタ2フォロワー、台湾のリーダーを仮定

戻る出典:リーダーたびに入力しcase LEADING:実行しますleader.lead();

 case LEADING:
            // todo 服务器成功当选成leader
            LOG.info("LEADING");
            try {
                setLeader(makeLeader(logFactory));
                // todo 跟进lead
                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;

しかし、leader.lead();各実行で以下の判断を行います、テストが自分自身をハングアップしたように、直接半分、指導者に会っていないときに、クラスタ内のすべてのノードの最終ステータスに変更されることが明らかであるLOOKING再選挙


              if (!tickSkip && !self.getQuorumVerifier().containsQuorum(syncedSet)) {
                //if (!tickSkip && syncedCount < self.quorumPeers.size() / 2) {
                    // Lost quorum, shutdown
                    shutdown("Not sufficient followers synced, only synced with sids: [ "
                            + getSidSetString(syncedSet) + " ]");
                    // make sure the order is the same!
                    // the leader goes to looking
                    return;
              } 

シナリオ4:クラスタが正しく動作している、新規追加フォロワー

彼女は同じ選挙のリーダー、自分のために同じ意志最初の投票をしようと、その状態の開始時に入ってくる新しい追加が探しているフォロワーが、安定したクラスタのためである
と判定された各オレンジのクラスタダウン、この場合には、我々が入ります新しいノードの現在のアドオンがするように、この方法の次のブランチを直接リーダーを認識しFastLeaderElection.javalookForLeader()

case OBSERVING:
        // todo 禁止Observer参加投票
        LOG.debug("Notification from observer: " + n.sid);
        break;
    case FOLLOWING:
    case LEADING:
        /*
            * Consider all notifications from the same epoch
            * together.
            */
        if (n.electionEpoch == logicalclock.get()) {
            recvset.put(n.sid, new Vote(n.leader,
                    n.zxid,
                    n.electionEpoch,
                    n.peerEpoch));

            if (ooePredicate(recvset, outofelection, n)) {
                self.setPeerState((n.leader == self.getId()) ?
                        ServerState.LEADING : learningState());

                Vote endVote = new Vote(n.leader,
                        n.zxid,
                        n.electionEpoch,
                        n.peerEpoch);
                leaveInstance(endVote);
                return endVote;
            }
        }

エラーがある場合、それは、あなたのサポートのための歓迎のポイントを助けている場合、指摘してください

おすすめ

転載: www.cnblogs.com/ZhuChangwu/p/11622763.html