Zookeeper客戶端Curator使用詳解-選舉(二)

轉:http://throwable.coding.me/2018/12/16/zookeeper-curator-usage

Leader選舉

在分布式計算中, leader elections是很重要的一個功能, 這個選舉過程是這樣子的: 指派一個進程作為組織者,將任務分發給各節點。 在任務開始前, 哪個節點都不知道誰是leader(領導者)或者coordinator(協調者). 當選舉算法開始執行后, 每個節點最終會得到一個唯一的節點作為任務leader. 除此之外, 選舉還經常會發生在leader意外宕機的情況下,新的leader要被選舉出來。

在zookeeper集群中,leader負責寫操作,然后通過Zab協議實現follower的同步,leader或者follower都可以處理讀操作。

Curator 有兩種leader選舉的recipe,分別是LeaderSelectorLeaderLatch

前者是所有存活的客戶端不間斷的輪流做Leader,大同社會。后者是一旦選舉出Leader,除非有客戶端掛掉重新觸發選舉,否則不會交出領導權。某黨?

LeaderLatch

LeaderLatch有兩個構造函數:

public LeaderLatch(CuratorFramework client, String latchPath)
public LeaderLatch(CuratorFramework client, String latchPath,  String id)

LeaderLatch的啟動:

leaderLatch.start( );

一旦啟動,LeaderLatch會和其它使用相同latch path的其它LeaderLatch交涉,然后其中一個最終會被選舉為leader,可以通過hasLeadership方法查看LeaderLatch實例是否leader:

leaderLatch.hasLeadership( ); //返回true說明當前實例是leader

類似JDK的CountDownLatch, LeaderLatch在請求成為leadership會block(阻塞),一旦不使用LeaderLatch了,必須調用close方法。 如果它是leader,會釋放leadership, 其它的參與者將會選舉一個leader。

public void await() throws InterruptedException,EOFException
/*Causes the current thread to wait until this instance acquires leadership
unless the thread is interrupted or closed.*/
public boolean await(long timeout,TimeUnit unit)throws InterruptedException

異常處理: LeaderLatch實例可以增加ConnectionStateListener來監聽網絡連接問題。 當 SUSPENDED 或 LOST 時, leader不再認為自己還是leader。當LOST后連接重連后RECONNECTED,LeaderLatch會刪除先前的ZNode然后重新創建一個。LeaderLatch用戶必須考慮導致leadership丟失的連接問題。 強烈推薦你使用ConnectionStateListener。

一個LeaderLatch的使用例子:

public class LeaderLatchDemo extends BaseConnectionInfo {
    protected static String PATH = "/francis/leader";
    private static final int CLIENT_QTY = 10;


    public static void main(String[] args) throws Exception {
        List<CuratorFramework> clients = Lists.newArrayList();
        List<LeaderLatch> examples = Lists.newArrayList();
        TestingServer server=new TestingServer();
        try {
            for (int i = 0; i < CLIENT_QTY; i++) {
                CuratorFramework client
                        = CuratorFrameworkFactory.newClient(server.getConnectString(), new ExponentialBackoffRetry(20000, 3));
                clients.add(client);
                LeaderLatch latch = new LeaderLatch(client, PATH, "Client #" + i);
                latch.addListener(new LeaderLatchListener() {

                    @Override
                    public void isLeader() {
                        // TODO Auto-generated method stub
                        System.out.println("I am Leader");
                    }

                    @Override
                    public void notLeader() {
                        // TODO Auto-generated method stub
                        System.out.println("I am not Leader");
                    }
                });
                examples.add(latch);
                client.start();
                latch.start();
            }
            Thread.sleep(10000);
            LeaderLatch currentLeader = null;
            for (LeaderLatch latch : examples) {
                if (latch.hasLeadership()) {
                    currentLeader = latch;
                }
            }
            System.out.println("current leader is " + currentLeader.getId());
            System.out.println("release the leader " + currentLeader.getId());
            currentLeader.close();

            Thread.sleep(5000);

            for (LeaderLatch latch : examples) {
                if (latch.hasLeadership()) {
                    currentLeader = latch;
                }
            }
            System.out.println("current leader is " + currentLeader.getId());
            System.out.println("release the leader " + currentLeader.getId());
        } finally {
            for (LeaderLatch latch : examples) {
                if (null != latch.getState())
                CloseableUtils.closeQuietly(latch);
            }
            for (CuratorFramework client : clients) {
                CloseableUtils.closeQuietly(client);
            }
        }
    }
}

可以添加test module的依賴方便進行測試,不需要啟動真實的zookeeper服務端:

<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-test</artifactId>
    <version>2.12.0</version>
</dependency>

首先我們創建了10個LeaderLatch,啟動后它們中的一個會被選舉為leader。 因為選舉會花費一些時間,start后并不能馬上就得到leader。
通過hasLeadership查看自己是否是leader, 如果是的話返回true。
可以通過.getLeader().getId()可以得到當前的leader的ID。
只能通過close釋放當前的領導權。
await是一個阻塞方法, 嘗試獲取leader地位,但是未必能上位。

LeaderSelector

LeaderSelector使用的時候主要涉及下面幾個類:

  • LeaderSelector
  • LeaderSelectorListener
  • LeaderSelectorListenerAdapter
  • CancelLeadershipException

核心類是LeaderSelector,它的構造函數如下:

public LeaderSelector(CuratorFramework client, String mutexPath,LeaderSelectorListener listener)
public LeaderSelector(CuratorFramework client, String mutexPath, ThreadFactory threadFactory, Executor executor, LeaderSelectorListener listener)

類似LeaderLatch,LeaderSelector必須start: leaderSelector.start(); 一旦啟動,當實例取得領導權時你的listener的takeLeadership()方法被調用。而takeLeadership()方法只有領導權被釋放時才返回。 當你不再使用LeaderSelector實例時,應該調用它的close方法。

異常處理 LeaderSelectorListener類繼承ConnectionStateListener。LeaderSelector必須小心連接狀態的改變。如果實例成為leader, 它應該響應SUSPENDED 或 LOST。 當 SUSPENDED 狀態出現時, 實例必須假定在重新連接成功之前它可能不再是leader了。 如果LOST狀態出現, 實例不再是leader, takeLeadership方法返回。

重要: 推薦處理方式是當收到SUSPENDED 或 LOST時拋出CancelLeadershipException異常.。這會導致LeaderSelector實例中斷并取消執行takeLeadership方法的異常.。這非常重要, 你必須考慮擴展LeaderSelectorListenerAdapter. LeaderSelectorListenerAdapter提供了推薦的處理邏輯。

下面的一個例子摘抄自官方:

public class LeaderSelectorAdapter extends LeaderSelectorListenerAdapter implements Closeable {
    private final String name;
    private final LeaderSelector leaderSelector;
    private final AtomicInteger leaderCount = new AtomicInteger();

    public LeaderSelectorAdapter(CuratorFramework client, String path, String name) {
        this.name = name;
        leaderSelector = new LeaderSelector(client, path, this);
        leaderSelector.autoRequeue();
    }

    public void start() throws IOException {
        leaderSelector.start();
    }

    @Override
    public void close() throws IOException {
        leaderSelector.close();
    }

    @Override
    public void takeLeadership(CuratorFramework client) throws Exception {
        final int waitSeconds = (int) (5 * Math.random()) + 1;
        System.out.println(name + " is now the leader. Waiting " + waitSeconds + " seconds...");
        System.out.println(name + " has been leader " + leaderCount.getAndIncrement() + " time(s) before.");
        try {
            Thread.sleep(TimeUnit.SECONDS.toMillis(waitSeconds));
        } catch (InterruptedException e) {
            System.err.println(name + " was interrupted.");
            Thread.currentThread().interrupt();
        } finally {
            System.out.println(name + " relinquishing leadership.\n");
        }
    }
}

你可以在takeLeadership進行任務的分配等等,并且不要返回,如果你想要要此實例一直是leader的話可以加一個死循環。調用 leaderSelector.autoRequeue();保證在此實例釋放領導權之后還可能獲得領導權。 在這里我們使用AtomicInteger來記錄此client獲得領導權的次數, 它是”fair”, 每個client有平等的機會獲得領導權。

public class LeaderSelectorDemo {

    protected static String PATH = "/francis/leader";
    private static final int CLIENT_QTY = 10;


    public static void main(String[] args) throws Exception {
        List<CuratorFramework> clients = Lists.newArrayList();
        List<LeaderSelectorAdapter> examples = Lists.newArrayList();
        TestingServer server = new TestingServer();
        try {
            for (int i = 0; i < CLIENT_QTY; i++) {
                CuratorFramework client
                        = CuratorFrameworkFactory.newClient(server.getConnectString(), new ExponentialBackoffRetry(20000, 3));
                clients.add(client);
                LeaderSelectorAdapter selectorAdapter = new LeaderSelectorAdapter(client, PATH, "Client #" + i);
                examples.add(selectorAdapter);
                client.start();
                selectorAdapter.start();
            }
            System.out.println("Press enter/return to quit\n");
            new BufferedReader(new InputStreamReader(System.in)).readLine();
        } finally {
            System.out.println("Shutting down...");
            for (LeaderSelectorAdapter exampleClient : examples) {
                CloseableUtils.closeQuietly(exampleClient);
            }
            for (CuratorFramework client : clients) {
                CloseableUtils.closeQuietly(client);
            }
            CloseableUtils.closeQuietly(server);
        }
    }
}

對比可知,LeaderLatch必須調用close()方法才會釋放領導權,而對于LeaderSelector,通過LeaderSelectorListener可以對領導權進行控制, 在適當的時候釋放領導權,這樣每個節點都有可能獲得領導權。從而,LeaderSelector具有更好的靈活性和可控性,建議有LeaderElection應用場景下優先使用LeaderSelector。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容