zookeeper學習系列:3、利用zookeeper作選舉和鎖

以前只理解zk能夠作命名,配置服務,如今學習下他怎麼用做選舉和鎖,進一步還可構建master-slave模式的分佈式系統。php

爲何叫Zoo?「由於要協調的分佈式系統是一個動物園」。java

ZooKeeper是一箇中性化的Service,用於管理配置信息、命名、提供分佈式同步,還能組合Service。全部這些種類的Service都會在分佈式應用程序中使用到。每次編寫這些Service都會涉及大量的修bug和競爭狀況。正由於這種編寫這些Service有必定難度,因此一般都會忽視它們,這就使得在應用程序有變化時變得難以管理應用程序。即便處理得當,實現這些服務的不一樣方法也會使得部署應用程序變得難以管理。node

下邊代碼是參考文獻的java版本,經過service來協調各個獨立的PHP腳本,並讓它們贊成某個成爲Leader(因此稱做Leader選舉)。當Leader退出(或崩潰)時,worker可檢測到並再選出新的leader。經過這種方式便可理解通常的master-slave結構分佈式系統是如何實現如何調度的,zk是個好東西。apache

首先須要瞭解建立節點的模式:服務器

PERSISTENT:持久化目錄節點,這個目錄節點存儲的數據不會丟失;session

PERSISTENT_SEQUENTIAL:順序自動編號的目錄節點,這種目 錄節點會根據當前已近存在的節點數自動加 1,而後返回給客戶端已經成功建立的目錄節點名;app

EPHEMERAL:臨時目錄節點,一旦建立這個節點的客戶端與服務器端口也就是 session 超時,這種節點會被自動刪除;分佈式

EPHEMERAL_SEQUENTIAL:臨時自動編號節點。ide

臨時節點在leader選舉、鎖服務中起着很是重要的做用。 學習

1、選舉

程序邏輯:

1)首先建立根節點/cluster,並建立自身子節點,以 /cluster/w- 爲前綴,使用臨時自動編號節點模式建立節點

2)獲取/cluster的全部子節點並排序,當發現自身是第一個節點時,則自我選舉爲leader,不然認定爲follower

3)註冊監聽事件,當/cluster裏前一個節點有變更時,回到2)

這樣,便實現了自動選舉,當有節點在timeout時段後不可用時,自動產生新的leader,也可根據當前節點數進行預警。

package zookeeper;

import org.apache.zookeeper.*;

import java.io.IOException;
import java.util.Collections;
import java.util.List;

/**
 * Created with IntelliJ IDEA.
 *
 * @author guanpu
 *         Date: 14-10-22
 *         Time: 下午5:11
 *         To change this template use File | Settings | File Templates.
 */
public class Worker extends ZooKeeper implements Runnable, Watcher {
    public static final String NODE_NAME = "/cluster";
    public String znode;
    private boolean leader;

    public Worker(String connectString, int sessionTimeout, Watcher watcher) throws IOException {
        super(connectString, sessionTimeout, watcher);
    }

    public boolean register() throws InterruptedException, KeeperException {
        if (this.exists(NODE_NAME, null) == null) {
            this.create(NODE_NAME, "test".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE,
                    CreateMode.PERSISTENT);
        }
        znode = this.create(NODE_NAME + "/w-", null, ZooDefs.Ids.OPEN_ACL_UNSAFE,
                CreateMode.EPHEMERAL_SEQUENTIAL);
        znode = znode.replace(NODE_NAME + "/", "");
        String node = watchPrevious();
        if (node.equals(znode)) {
            System.out.println("nobody here ,i am leader");
            leader = true;
        } else {
            System.out.println("i am watching");
        }
        return true;
    }

    private String watchPrevious() throws InterruptedException, KeeperException {
        List<String> works = this.getChildren(NODE_NAME, this);
        Collections.sort(works);
        System.out.println(works);
        int i = 0;
        for (String work : works) {
            if (znode.equals(work)) {
                if (i > 0) {
                   //this.getData(NODE_NAME + "/" + works.get(i - 1), this, null);
                    return works.get(i - 1);
                }
                return works.get(0);
            }
        }
        return "";

    }

    @Override
    public void run() {
        try {
            this.register();
        } catch (InterruptedException e) {
        } catch (KeeperException e) {
        }
        while (true) {
            try {
                if (leader) {
                    System.out.println("leading");
                } else {
                    System.out.println("following");
                }
                Thread.sleep(1000);
            } catch (InterruptedException e) {
            }
        }
    }

    public static void main(String[] args) {
        try {
            String hostPort = "10.16.73.22,10.16.73.12,10.16.73.13";
            new Thread(new Worker(hostPort, 3000, null)).start();
        } catch (IOException e) {
        }
    }


    @Override
    public void process(WatchedEvent event) {
        String t = String.format("hello event! type=%s, stat=%s, path=%s", event.getType(), event.getState(), event.getPath());
        System.out.println(t);
        System.out.println("hello ,my cluster id is :"+znode);
        String node = "";
        try {
            node = this.watchPrevious();
        } catch (InterruptedException e) {
        } catch (KeeperException e) {
        }

        if (node.equals(znode)) {
            System.out.println("process: nobody here ,i am leader");
            leader = true;
        } else {
            System.out.println("process: i am watching");
        }
    }
}

 啓動至少三個終端,模擬Leader崩潰的情形。使用Ctrl+c或其餘方法退出第一個腳本。剛開始不會有任何變化,worker能夠繼續工做。後來,ZooKeeper會發現超時,並選舉出新的leader。

 

php移植到java有兩個問題,第一個是watcher註冊,第一次父類初始化未完成時不能調用自身做爲watcher,會報一次watcher調用空指針。

第二個問題:

 this.getData(NODE_NAME + "/" + works.get(i - 1), this, null);

這個不生效,看方法註釋是當改動和移除節點時,觸發watcher的process,但實驗中並未觸發,在java裏系統的自動刪除並不歸類在這兩個操做以內?

php版本的是正常的,做爲遺留問題。爲了程序正常運行,更改成 List<String> works = this.getChildren(NODE_NAME, this);   當子節點有變更時執行process方法。  但這樣會致使從衆效應,當集羣服務器衆多且帶寬延時較大時候會很明顯,leader的狀態變化會引發全部follower的變化,follower之一短連,也會致使整個集羣去響應這個變化。

2、鎖 

加鎖:

1)zk調用create()方法建立一個路徑格式爲"_locknode_/lock-"的節點,類型爲sequence和ephemeral,臨時節點且順序編號

2)在建立的鎖節點上調用getChildren()方法,以獲取鎖目錄下最小編號節點,且不設置watch

3)若是步驟2得到的節點是步驟1建立的節點,那麼客戶端得到鎖,而後退出操做

4)客戶端在鎖目錄上調用exists()方法,設置watch來監視鎖目錄下序號相對本身小的連續臨時節點的狀態

5)監視節點狀態發生變化,則跳到步驟2,繼續後續操做,直到退出鎖競爭。

解鎖:

將加鎖操做步驟1中建立的臨時節點刪除便可。

 

參考文獻:

http://anykoro.sinaapp.com/2013/04/05/使用apache-zookeeper分佈式部署php應用程序/

相關文章
相關標籤/搜索