1. 程式人生 > >java架構之路-(分散式zookeeper)zookeeper叢集配置和選舉機制詳解

java架構之路-(分散式zookeeper)zookeeper叢集配置和選舉機制詳解

  上次部落格我們說了一下zookeeper的配置檔案,以及命令的使用https://www.cnblogs.com/cxiaocai/p/11597465.html。我們這次來說一下我們的zookeeper的叢集配置和java的API相關操作。

叢集:

  一般情況下我們用zookeeper來做任務排程中心的,所以一定要做到高可用的,單機的不可能做到永不宕機,我們也不會信任他單機的永不宕機,這時我們就需要做叢集處理,來實現我們的高可用。

配置叢集時,我們儘可能採用奇數的伺服器來配置,什麼意思呢?盡力採用3,5,7,9臺伺服器來配置,原因是zookeeper會預設識別半數以上伺服器正常執行,才認為zookeeper是正常執行的,比如我們現在部署4臺zookeeper伺服器,這時其中兩臺宕機了,這時zookeeper會認為這個叢集時不可用的,同理我們如果是5臺伺服器的情況,有兩臺宕機了,可以正常執行,三臺宕機了,才被認為是不可用的,這個很重要,包括後面的選舉機制也是這樣的。資金有限啊,我先用3臺伺服器搭建一下zookeeper叢集。

1.叢集配置

  下載解壓什麼的就不說了啊,上次都說過了,我們直接看下配置檔案吧。和單機配置基本一致,我們看到dataDir=/tmp/zookeeper,也就是我們的資料儲存路徑,分別建立三個檔案myid,內部輸入1-255的數字

 每臺伺服器別重複,切記一定建立在配置檔案dataDir對應的目錄下,不然啟動會報找不到myid檔案的錯誤,沒有對應/tmp/zookeeper目錄的,可以啟動一下zookeeper再關閉就有檔案夾了,或者自己手動建立也行。

再每一個配置檔案內加入配置

server.1=172.16.140.106:2888:3888

server.2=172.16.140.105:2888:3888

server.3=172.16.214.74:2888:3888

server.myid(myid檔案的數字)=ip(與myid相對應的IP):叢集之間相互通訊的IP:選舉時通訊的IP。三分配置檔案都是一樣的。

我們來分別啟動一下。說到這裡我們的叢集配置也就成功了。

啟動成功以後,我們分別輸入./bin/zkServer.sh status 我們可以看到我們的伺服器角色

 

 2.角色:

leader 主節點,又名領導者。用於寫入資料,通過選舉產生,如果宕機將會選舉新的主節點。

follower 子節點,又名追隨者。用於實現資料的讀取。同時他也是主節點的備選節點,並用擁有投票權。

observer 次級子節點,又名觀察者。用於讀取資料,與fllower區別在於沒有投票權,不能選為主節點。並且在計算叢集可用狀態時不會將observer計算入內。也就是我們的半數原則計算。

observer配置:

只要在叢集配置中加上observer字尾即可,示例如下:

server.3=127.0.0.1:2889:3889:observer

選舉機制:

  先說一個簡單的,投票機制的。假設我們現在有1,2,3,4,5五個follower要進行選舉。

 

 

 簡單流程就是這樣的,第一輪都認為自己很可以,自己要當選leader,但是選舉流程失敗了,還得繼續,接下來會把自己的票全盤拖出給自己臨近的id,1就會給2一票,2現在有了兩票了,發現還是不夠半數啊,半數是2.5啊,算了還得繼續,2又把自己的兩票都給了3,3這時獲得了3票了,大於半數了,當選leader。

每輪選舉結束後都會統一來處理,如果一輪投票就發現server1的zxid較大,那麼直接server1會當選leader。

優先檢查ZXID。ZXID比較大的伺服器優先作為Leader。

如果ZXID相同,那麼就比較myid。myid較大的伺服器作為Leader伺服器。

留下一個思考題,5臺伺服器,如果啟動可以指定 4號為leader服務 。

javaAPI相關操作

  maven的pom檔案內加入

<dependency>
    <groupId>org.apache.zookeeper</groupId>
    <artifactId>zookeeper</artifactId>
    <version>3.5.5</version>
</dependency>

首先我們先初始化我們的Zookeeper的連線

connectString ->連線String連線串,包括ip+埠 ,叢集模式下用逗號隔開192.168.0.149:2181,192.168.0.150:2181

sessionTimeout ->會話超時時間,型別int,該值不能超過服務端所設定的minSessionTimeout(預設2s)和maxSessionTimeout(預設60s),單位毫秒

watcher -> 會話監聽器Watcher,服務端事件將會觸該監聽

sessionId -> 自定義會話ID long

sessionPasswd ->byte[] 會話密碼

canBeReadOnly ->boolean該連線是否為只讀的 

hostProvider ->HostProvider 服務端地址提供者,指示客戶端如何選擇某個服務來呼叫,預設採用StaticHostProvider實現

@Before
public void init() throws IOException {
    String conn = "47.111.109.3:2181"; // 連線字串
    int sessionTimeout  = 4000;  //連線超時時間
    zooKeeper = new ZooKeeper(conn, sessionTimeout, new Watcher() {
        public void process(WatchedEvent watchedEvent) {

        }
    });
}

我們先來看看我們的新增節點,刪除節點等操作吧。

檢視節點:

我們使用getData方法,新增三個引數,分別是路徑,是否監聽,和返回值(狀態stat)。

/**
 * 獲取資料
 *
 * @throws KeeperException
 * @throws InterruptedException
 */
@Test
public void getData() throws KeeperException, InterruptedException {
    byte[] data = zooKeeper.getData("/root", false, null);
    System.out.println(new String(data));
}

新增監聽:

/**
 * 新增監聽,結果在初始化裡
 *
 * @throws KeeperException
 * @throws InterruptedException
 */
@Test
public void getWatchData() throws KeeperException, InterruptedException {
    byte[] data = zooKeeper.getData("/root", true, null);
    System.out.println(new String(data));
    Thread.sleep(Long.MAX_VALUE);
}

這個監聽是一次性的,而且結果在我們的初始化的watch裡,初始化方法改為

@Before
public void init() throws IOException {
    String conn = "47.111.109.3:2181"; // 連線字串
    int sessionTimeout = 4000;  //連線超時時間
    zooKeeper = new ZooKeeper(conn, sessionTimeout, new Watcher() {
        public void process(WatchedEvent watchedEvent) {
            System.out.println(watchedEvent.getPath());
        }
    });
}

永久監聽設定,我們只要將Watcher新建一下就可以了嗎...我們來看一下實現

/**
 * 永久監聽
 *
 * @throws KeeperException
 * @throws InterruptedException
 */
@Test
public void getWatchDataForever() throws KeeperException, InterruptedException {
    byte[] data = zooKeeper.getData("/root", new Watcher() {
        public void process(WatchedEvent watchedEvent) {
            try {
                zooKeeper.getData(watchedEvent.getPath(),this,null);
            } catch (KeeperException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(watchedEvent.getPath());
        }
    }, null);
    System.out.println(new String(data));
    Thread.sleep(Long.MAX_VALUE);
}

stat:

/**
 * Stat
 *
 * @throws KeeperException
 * @throws InterruptedException
 */
@Test
public void getDataStat() throws InterruptedException, KeeperException {
    Stat stat = new Stat();
    zooKeeper.getData("/root",false, stat);
    System.out.println(stat);
}

輸出結果和我們命令stat的輸入其實是完全一致的,只不過一個事16進位制,一個事10進位制的,可以自己對比一下。

獲取子節點 :

/**
 * 獲取子節點
 *
 * @throws KeeperException
 * @throws InterruptedException
 */
@Test
public void getWatchChildDataForever() throws KeeperException, InterruptedException {
    List<String> children = zooKeeper.getChildren("/root", false);
    for (int i = 0; i < children.size(); i++) {
        System.out.println(children.get(i));
    }
}

刪除節點:

/**
 * 刪除節點
 *
 * @throws KeeperException
 * @throws InterruptedException
 */
@Test
public void deletePath() throws KeeperException, InterruptedException {
    zooKeeper.delete("/root/d2", 0);
}

建立節點:

/**
 * 建立節點
 *
 * @throws KeeperException
 * @throws InterruptedException
 */
@Test
public void createPath() throws KeeperException, InterruptedException {
    List<ACL> acl = new ArrayList<ACL>();
    int perm = ZooDefs.Perms.ADMIN | ZooDefs.Perms.CREATE | ZooDefs.Perms.READ;
    ACL aclObj = new ACL(perm, new Id("world", "anyone"));
    acl.add(aclObj);
    zooKeeper.create("/root/d4", "hello".getBytes(), acl, CreateMode.CONTAINER);
}

說到這我們的API和叢集操作就差不多說完了。

這次程式碼不多,就先不上傳了,寫完下次部落格再一起上傳。

&nb