Curator之Master/Leader選舉

在實際生產中,特別是分佈式系統中,我們經常遇到這樣的場景:一個複雜的任務,近需要從分佈式機器中選出一臺機器來執行。諸如此類的問題,我們統稱爲“Master選舉”。比如,在分佈式系統中很常見的一個問題就是定時任務的執行。如果多臺機器同時執行相同的定時任務,業務複雜則可能出現災難性的後果。本篇博客就以定時任務爲例來示例說明Curator的Master選舉用法。

原理

利用zookeeper來實現Master選舉的基本思路如下: 
選擇一個根節點(與其他業務隔離),比如/jobMaster,多臺機器同時在此節點下面創建一個子節點/jobMaster/lock,zookeeper保證了最終只有一臺機器能夠創建成功,那麼這臺機器將成爲Master。由它來執行業務操作。

Curator所做的事情就是將上面的思路進行了封裝,把原生API的節點創建、事件監聽和自動選舉進行整合封裝,提供了一套簡單易用的解決方案。

在實際生產中,特別是分佈式系統中,我們經常遇到這樣的場景:一個複雜的任務,近需要從分佈式機器中選出一臺機器來執行。諸如此類的問題,我們統稱爲“Master選舉”。比如,在分佈式系統中很常見的一個問題就是定時任務的執行。如果多臺機器同時執行相同的定時任務,業務複雜則可能出現災難性的後果。本篇博客就以定時任務爲例來示例說明Curator的Master選舉用法。

原理

利用zookeeper來實現Master選舉的基本思路如下: 
選擇一個根節點(與其他業務隔離),比如/jobMaster,多臺機器同時在此節點下面創建一個子節點/jobMaster/lock,zookeeper保證了最終只有一臺機器能夠創建成功,那麼這臺機器將成爲Master。由它來執行業務操作。

Curator所做的事情就是將上面的思路進行了封裝,把原生API的節點創建、事件監聽和自動選舉進行整合封裝,提供了一套簡單易用的解決方案。

構造方法

public LeaderLatch(CuratorFramework client, String latchPath)

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

示例代碼

package com.secbro.learn.curator;

import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.leader.LeaderLatch;
import org.apache.curator.framework.recipes.leader.LeaderLatchListener;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.curator.utils.CloseableUtils;

import java.util.ArrayList;
import java.util.List;

public class LeaderLatchTest {
    private static final String PATH = "/demo/leader";
    public static void main(String[] args) {
        List<LeaderLatch> latchList = new ArrayList<>();
        List<CuratorFramework> clients = new ArrayList<>();
        try {
            for (int i = 0; i < 10; i++) {
                CuratorFramework client = getClient();
                clients.add(client);

                final LeaderLatch leaderLatch = new LeaderLatch(client, PATH, "client#" + i);
                leaderLatch.addListener(new LeaderLatchListener() {
                    @Override
                    public void isLeader() {
                        System.out.println(leaderLatch.getId() +  ":I am leader. I am doing jobs!");
                    }
                    @Override
                    public void notLeader() {
                        System.out.println(leaderLatch.getId() +  ":I am not leader. I will do nothing!");
                    }
                });
                latchList.add(leaderLatch);
                leaderLatch.start();
            }
            Thread.sleep(Integer.MAX_VALUE);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            for(CuratorFramework client : clients){
                CloseableUtils.closeQuietly(client);
            }

            for(LeaderLatch leaderLatch : latchList){
                CloseableUtils.closeQuietly(leaderLatch);
            }
        }
    }
    private static CuratorFramework getClient() {
        RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
        CuratorFramework client = CuratorFrameworkFactory.builder()
                .connectString("127.0.0.1:2181")
                .retryPolicy(retryPolicy)
                .sessionTimeoutMs(6000)
                .connectionTimeoutMs(3000)
                .namespace("demo")
                .build();
        client.start();
        return client;
    }
}

打印結果:

client#6:I am leader. I am doing jobs!

重複執行幾次會發現是不同的client獲得leader。

本示例啓動了10個client,程序會隨機選中其中一個作爲leader。通過註冊監聽的方式來判斷自己是否成爲leader。調用close()方法釋放當前領導權。

LeaderLatch通過增加了一個ConnectionStateListener監聽連接問題。如果出現SUSPENDED或者LOST,leader會報告自己不再是leader(直到重新建立連接,否則不會有leader)。如果LOST的連接被重新建立即RECONNECTED,leaderLatch會刪除先前的zNode並重新建立zNode。

Leader Election

通過LeaderSelectorListener可以對領導權進行控制, 在適當的時候釋放領導權,這樣每個節點都有可能獲得領導權。 而LeaderLatch則一直持有leadership, 除非調用close方法,否則它不會釋放領導權。

構造方法

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

示例代碼

package com.secbro.learn.curator;

import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.leader.LeaderLatch;
import org.apache.curator.framework.recipes.leader.LeaderLatchListener;
import org.apache.curator.framework.recipes.leader.LeaderSelector;
import org.apache.curator.framework.recipes.leader.LeaderSelectorListener;
import org.apache.curator.framework.state.ConnectionState;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.curator.utils.CloseableUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executors;


public class LeaderSelectorTest {

    private static final String PATH = "/demo/leader";
    public static void main(String[] args) {
        List<LeaderSelector> selectors = new ArrayList<>();
        List<CuratorFramework> clients = new ArrayList<>();
        try {
            for (int i = 0; i < 10; i++) {
                CuratorFramework client = getClient();
                clients.add(client);

                final String name = "client#" + i;
                LeaderSelector leaderSelector = new LeaderSelector(client, PATH, new LeaderSelectorListener() {
                    @Override
                    public void takeLeadership(CuratorFramework client) throws Exception {

                        System.out.println(name + ":I am leader.");
                        Thread.sleep(2000);
                    }
                    @Override
                    public void stateChanged(CuratorFramework client, ConnectionState newState) {

                    }
                });
                leaderSelector.autoRequeue();
                leaderSelector.start();
                selectors.add(leaderSelector);
            }
            Thread.sleep(Integer.MAX_VALUE);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            for(CuratorFramework client : clients){
                CloseableUtils.closeQuietly(client);
            }

            for(LeaderSelector selector : selectors){
                CloseableUtils.closeQuietly(selector);
            }

        }
    }
    private static CuratorFramework getClient() {
        RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
        CuratorFramework client = CuratorFrameworkFactory.builder()
                .connectString("127.0.0.1:2181")
                .retryPolicy(retryPolicy)
                .sessionTimeoutMs(6000)
                .connectionTimeoutMs(3000)
                .namespace("demo")
                .build();
        client.start();
        return client;
    }
}

執行結果,控制檯不停打印:

client#5:I am leader.
client#8:I am leader.
client#3:I am leader.
client#1:I am leader.
client#4:I am leader.
client#0:I am leader.

本示例創建了10個LeaderSelector並對起添加監聽,當被選爲leader之後,調用takeLeadership方法進行業務邏輯處理,處理完成即釋放領導權。其中autoRequeue()方法的調用確保此實例在釋放領導權後還可能獲得領導權。

LeaderSelectorListener類繼承了ConnectionStateListener。一旦LeaderSelector啓動,它會向curator客戶端添加監聽器。 使用LeaderSelector必須時刻注意連接的變化。一旦出現連接問題如SUSPENDED,curator實例必須確保它可能不再是leader,直至它重新收到RECONNECTED。如果LOST出現,curator實例不再是leader並且其takeLeadership()應該直接退出。

推薦的做法是,如果發生SUSPENDED或者LOST連接問題,最好直接拋CancelLeadershipException,此時,leaderSelector實例會嘗試中斷並且取消正在執行takeLeadership()方法的線程。 建議擴展LeaderSelectorListenerAdapter, LeaderSelectorListenerAdapter中已經提供了推薦的處理方式 。

總結

Curator提供了兩種方法來實現Leader選舉,根據不同的業務場景可選擇不同的方式。總之,Curator幫住開發人員封裝了很多繁雜的操作,使得實現一個Leader選舉變得輕而易舉。






發佈了122 篇原創文章 · 獲贊 57 · 訪問量 33萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章