認認真真學習zookeeper(二)

本文內容包括:zookeeper的事件監聽機制、集羣搭建、一致性協議、leader選舉、角色等

傳送門(已完結):
認認真真學習zookeeper(一)
認認真真學習zookeeper(二)
認認真真學習zookeeper(三)

1. zookeeper 事件監聽機制

1.1 watcher概念

  zookeeper提供了數據的發佈/訂閱功能,多個訂閱者可同時監聽某一特定主題對象,當該主題對象的自身狀態發生變化時(例如節點內容改變、節點下的子節點列表改變等),會實時、主動通知所有訂閱者

  ​zookeeper採用了Watcher機制實現數據的發佈/訂閱功能。該機制在被訂閱對象發生變化時會異步通知客戶端,因此客戶端不必在Watcher註冊後輪詢阻塞,從而減輕了客戶端壓力。

​   watcher機制實際上與觀察者模式類似,也可看作是一種觀察者模式在分佈式場景下的實現方式。

1.2 watcher架構

  ​Watcher實現由三個部分組成:

  • Zookeeper服務端
  • Zookeeper客戶端
  • 客戶端的ZKWatchManager對象

  ​客戶端首先將Watcher註冊到服務端,同時將Watcher對象保存到客戶端的Watch管理器中。當ZooKeeper服務端監聽的數據狀態發生變化時,服務端會主動通知客戶端,接着客戶端的Watch管理器會觸發相關Watcher來回調相應處理邏輯,從而完成整體的數據發佈/訂閱流程。
在這裏插入圖片描述

1.3 watcher特性

特性 說明
一次性 watcher是一次性的,一旦被觸發就會移除,再次使用時需要重新註冊
客戶端順序回調 watcher回調是順序串行化執行的,只有回調後客戶端才能看到最新的數據狀態。一個watcher回調邏輯不應該太多,以免影響別的watcher執行
輕量級 WatchEvent是最小的通信單元,結構上只包含通知狀態、事件類型和節點路徑,並不會告訴數據節點變化前後的具體內容;
時效性 watcher只有在當前session徹底失效時纔會無效,若在session有效期內快速重連成功,則watcher依然存在,仍可接收到通知;

1.4 watcher接口設計

  ​Watcher是一個接口,任何實現了Watcher接口的類就是一個新的Watcher。Watcher內部包含了兩個枚舉類:KeeperState、EventType
在這裏插入圖片描述

  • Watcher通知狀態(KeeperState)
      KeeperState是客戶端與服務端連接狀態發生變化時對應的通知類型。路徑爲org.apache.zookeeper.Watcher.Event.KeeperState,是一個枚舉類,其枚舉屬性如下:
枚舉屬性 說明
SyncConnected 客戶端與服務器正常連接時
Disconnected 客戶端與服務器斷開連接時
Expired 會話session失效時
AuthFailed 身份認證失敗時
  • Watcher事件類型(EventType)
      EventType是數據節點(znode)發生變化時對應的通知類型。EventType變化時KeeperState永遠處於SyncConnected通知狀態下;當KeeperState發生變化時,EventType永遠爲None。其路徑org.apache.zookeeper.Watcher.Event.EventType,是一個枚舉類,枚舉屬性如下:
枚舉屬性 說明
None
NodeCreated Watcher監聽的數據節點被創建時
NodeDeleted Watcher監聽的數據節點被刪除時
NodeDataChanged Watcher監聽的數據節點內容發生變更時(無論內容數據是否變化)
NodeChildrenChanged Watcher監聽的數據節點的子節點列表發生變更時

注:客戶端接收到的相關事件通知中只包含狀態及類型等信息,不包括節點變化前後的具體內容,變化前的數據需業務自身存儲,變化後的數據需調用get等方法重新獲取;

1.5 捕獲相應的事件

  上面講到zookeeper客戶端連接的狀態和zookeeper對znode節點監聽的事件類型,下面我們來講解如何建立zookeeper的watcher監聽。在zookeeper中採用
zk.getChildren(path, watch)、zk.exists(path, watch)、zk.getData(path, watcher, stat)
這樣的方式爲某個znode註冊監聽。

下表以node-x節點爲例,說明調用的註冊方法和可監聽事件間的關係:

註冊方式 Created ChildrenChanged Changed Deleted
zk.exists(“/node-x”,watcher) 可監控 可監控 可監控
zk.getData(“/node-x”,watcher) 可監控 可監控
zk.getChildren(“/node-x”,watcher) 可監控 可監控

1.6 註冊watcher的方法

1.6.1 客服端與服務器的連接狀態

KeeperState 通知狀態
SyncConnected:客戶端與服務器正常連接時
Disconnected:客戶端與服務器斷開連接時
Expired:會話session失效時
AuthFailed:身份認證失敗時

事件類型爲:None

案例:

import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;

import java.util.concurrent.CountDownLatch;

public class ZKConnectionWatcher implements Watcher {

    // 計數器對象
    private static CountDownLatch countDownLatch = new CountDownLatch(1);
    // 連接對象
    private static ZooKeeper zooKeeper;

    @Override
    public void process(WatchedEvent event) {
        try {
            // 事件類型
            if (event.getType() == Event.EventType.None) {
                if (event.getState() == Event.KeeperState.SyncConnected) {
                    System.out.println("連接創建成功!");
                    countDownLatch.countDown();
                } else if (event.getState() == Event.KeeperState.Disconnected) {
                    System.out.println("斷開連接!");
                } else if (event.getState() == Event.KeeperState.Expired) {
                    System.out.println("會話超時!");
                    zooKeeper = new ZooKeeper("192.168.188.133:2181", 5000, new ZKConnectionWatcher());
                } else if (event.getState() == Event.KeeperState.AuthFailed) {
                    System.out.println("認證失敗!");
                }
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }

    }

    public static void main(String[] args) {
        try {
            zooKeeper = new ZooKeeper("192.168.188.133:2181", 5000, new ZKConnectionWatcher());
            // 阻塞線程等待連接的創建
            countDownLatch.await();
            // 會話id
            System.out.println(zooKeeper.getSessionId());
            // 添加授權用戶
            zooKeeper.addAuthInfo("digest1","sky1:123456".getBytes());
            byte [] bs=zooKeeper.getData("/node12",false,null);
            System.out.println(new String(bs));
            Thread.sleep(50000);
            zooKeeper.close();
            System.out.println("結束");
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
}

1.6.2 檢查節點是否存在

// 使用連接對象的監視器
exists(String path, boolean b)
// 自定義監視器
exists(String path, Watcher w)
   
// NodeCreated:節點創建
// NodeDeleted:節點刪除
// NodeDataChanged:節點內容發生變化
  • path- znode路徑。
  • b- 是否使用連接對象中註冊的監視器。
  • w-監視器對象。

案例:

import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import java.io.IOException;
import java.util.concurrent.CountDownLatch;

public class ZKWatcherExists {

    private String IP = "192.168.188.133:2181";
    private ZooKeeper zooKeeper = null;

    @Before
    public void before() throws IOException, InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(1);
        // 連接zookeeper客戶端
        zooKeeper = new ZooKeeper(IP, 6000, new Watcher() {
            @Override
            public void process(WatchedEvent event) {
                System.out.println("連接對象的參數!");
                // 連接成功
                if (event.getState() == Event.KeeperState.SyncConnected) {
                    countDownLatch.countDown();
                }
                System.out.println("path=" + event.getPath());
                System.out.println("eventType=" + event.getType());
            }
        });
        countDownLatch.await();
    }

    @After
    public void after() throws InterruptedException {
        zooKeeper.close();
    }

    @Test
    public void watcherExists1() throws KeeperException, InterruptedException {
        // arg1:節點的路徑
        // arg2:使用連接對象中的watcher
        zooKeeper.exists("/watcher1", true);
        Thread.sleep(50000);
        System.out.println("結束");
    }


    @Test
    public void watcherExists2() throws KeeperException, InterruptedException {
        // arg1:節點的路徑
        // arg2:自定義watcher對象
        zooKeeper.exists("/watcher1", new Watcher() {
            @Override
            public void process(WatchedEvent event) {
                System.out.println("自定義watcher");
                System.out.println("path=" + event.getPath());
                System.out.println("eventType=" + event.getType());
            }
        });
        Thread.sleep(50000);
        System.out.println("結束");
    }

    @Test
    public void watcherExists3() throws KeeperException, InterruptedException {
        // watcher一次性
        Watcher watcher = new Watcher() {
            @Override
            public void process(WatchedEvent event) {
                try {
                    System.out.println("自定義watcher");
                    System.out.println("path=" + event.getPath());
                    System.out.println("eventType=" + event.getType());
                    zooKeeper.exists("/watcher1", this);
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        };
        zooKeeper.exists("/watcher1", watcher);
        Thread.sleep(80000);
        System.out.println("結束");
    }


    @Test
    public void watcherExists4() throws KeeperException, InterruptedException {
        // 註冊多個監聽器對象
        zooKeeper.exists("/watcher1", new Watcher() {
            @Override
            public void process(WatchedEvent event) {
                System.out.println("1");
                System.out.println("path=" + event.getPath());
                System.out.println("eventType=" + event.getType());
            }
        });
        zooKeeper.exists("/watcher1", new Watcher() {
            @Override
            public void process(WatchedEvent event) {
                System.out.println("2");
                System.out.println("path=" + event.getPath());
                System.out.println("eventType=" + event.getType());
            }
        });
        Thread.sleep(80000);
        System.out.println("結束");
    }
}

1.6.3 查看節點

// 使用連接對象的監視器
getData(String path, boolean b, Stat stat)
// 自定義監視器
getData(String path, Watcher w, Stat stat)
   
// NodeDeleted:節點刪除
// NodeDataChanged:節點內容發生變化
  • path- znode路徑。
  • b- 是否使用連接對象中註冊的監視器。
  • w-監視器對象。
  • stat- 返回znode的元數據。

案例:

import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.Stat;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import java.io.IOException;
import java.util.concurrent.CountDownLatch;

public class ZKWatcherGetData {

    String IP = "192.168.188.133:2181";
    ZooKeeper zooKeeper = null;

    @Before
    public void before() throws IOException, InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(1);
        // 連接zookeeper客戶端
        zooKeeper = new ZooKeeper(IP, 6000, new Watcher() {
            @Override
            public void process(WatchedEvent event) {
                System.out.println("連接對象的參數!");
                // 連接成功
                if (event.getState() == Event.KeeperState.SyncConnected) {
                    countDownLatch.countDown();
                }
                System.out.println("path=" + event.getPath());
                System.out.println("eventType=" + event.getType());
            }
        });
        countDownLatch.await();
    }

    @After
    public void after() throws InterruptedException {
        zooKeeper.close();
    }

    @Test
    public void watcherGetData1() throws KeeperException, InterruptedException {
        // arg1:節點的路徑
        // arg2:使用連接對象中的watcher
        zooKeeper.getData("/watcher2", true, null);
        Thread.sleep(50000);
        System.out.println("結束");
    }

    @Test
    public void watcherGetData2() throws KeeperException, InterruptedException {
        // arg1:節點的路徑
        // arg2:自定義watcher對象
        zooKeeper.getData("/watcher2", new Watcher() {
            @Override
            public void process(WatchedEvent event) {
                System.out.println("自定義watcher");
                System.out.println("path=" + event.getPath());
                System.out.println("eventType=" + event.getType());
            }
        }, null);
        Thread.sleep(50000);
        System.out.println("結束");
    }

    @Test
    public void watcherGetData3() throws KeeperException, InterruptedException {
        // 一次性
        Watcher watcher = new Watcher() {
            @Override
            public void process(WatchedEvent event) {
                try {
                    System.out.println("自定義watcher");
                    System.out.println("path=" + event.getPath());
                    System.out.println("eventType=" + event.getType());
                    if(event.getType()==Event.EventType.NodeDataChanged) {
                        zooKeeper.getData("/watcher2", this, null);
                    }
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        };
        zooKeeper.getData("/watcher2", watcher, null);
        Thread.sleep(50000);
        System.out.println("結束");
    }

    @Test
    public void watcherGetData4() throws KeeperException, InterruptedException {
        // 註冊多個監聽器對象
        zooKeeper.getData("/watcher2", new Watcher() {
            @Override
            public void process(WatchedEvent event) {
                try {
                    System.out.println("1");
                    System.out.println("path=" + event.getPath());
                    System.out.println("eventType=" + event.getType());
                    if(event.getType()==Event.EventType.NodeDataChanged) {
                        zooKeeper.getData("/watcher2", this, null);
                    }
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        },null);
        zooKeeper.getData("/watcher2", new Watcher() {
            @Override
            public void process(WatchedEvent event) {
                try {
                    System.out.println("2");
                    System.out.println("path=" + event.getPath());
                    System.out.println("eventType=" + event.getType());
                    if(event.getType()==Event.EventType.NodeDataChanged) {
                        zooKeeper.getData("/watcher2", this, null);
                    }
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        },null);
        Thread.sleep(50000);
        System.out.println("結束");
    }
}

1.6.4 查看子節點

// 使用連接對象的監視器
getChildren(String path, boolean b)
// 自定義監視器
getChildren(String path, Watcher w)
   
// NodeChildrenChanged:子節點發生變化
// NodeDeleted:節點刪除
  • path- znode路徑。
  • b- 是否使用連接對象中註冊的監視器。
  • w-監視器對象。

案例:

import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.CountDownLatch;

public class ZKWatcherGetChild {
    String IP = "192.168.188.133:2181";
    ZooKeeper zooKeeper = null;

    @Before
    public void before() throws IOException, InterruptedException {
        CountDownLatch connectedSemaphore = new CountDownLatch(1);
        // 連接zookeeper客戶端
        zooKeeper = new ZooKeeper(IP, 5000, new Watcher() {
            @Override
            public void process(WatchedEvent event) {
                System.out.println("連接對象的參數!");
                // 連接成功
                if (event.getState() == Event.KeeperState.SyncConnected) {
                    connectedSemaphore.countDown();
                }
                System.out.println("path=" + event.getPath());
                System.out.println("eventType=" + event.getType());
            }
        });
        connectedSemaphore.await();
    }

    @After
    public void after() throws InterruptedException {
        zooKeeper.close();
    }

    @Test
    public void watcherGetChild1() throws KeeperException, InterruptedException {
        // arg1:節點的路徑
        // arg2:使用連接對象中的watcher
        zooKeeper.getChildren("/watcher3", true);
        Thread.sleep(50000);
        System.out.println("結束");
    }


    @Test
    public void watcherGetChild2() throws KeeperException, InterruptedException {
        // arg1:節點的路徑
        // arg2:自定義watcher
        zooKeeper.getChildren("/watcher3", new Watcher() {
            @Override
            public void process(WatchedEvent event) {
                System.out.println("自定義watcher");
                System.out.println("path=" + event.getPath());
                System.out.println("eventType=" + event.getType());
            }
        });
        Thread.sleep(50000);
        System.out.println("結束");
    }

    @Test
    public void watcherGetChild3() throws KeeperException, InterruptedException {
        // 一次性
        Watcher watcher = new Watcher() {
            @Override
            public void process(WatchedEvent event) {
                try {
                    System.out.println("自定義watcher");
                    System.out.println("path=" + event.getPath());
                    System.out.println("eventType=" + event.getType());
                    if (event.getType() == Event.EventType.NodeChildrenChanged) {
                        zooKeeper.getChildren("/watcher3", this);
                    }
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        };
        zooKeeper.getChildren("/watcher3", watcher);
        Thread.sleep(50000);
        System.out.println("結束");
    }

    @Test
    public void watcherGetChild4() throws KeeperException, InterruptedException {
        // 多個監視器對象
        zooKeeper.getChildren("/watcher3", new Watcher() {
            @Override
            public void process(WatchedEvent event) {
                try {
                    System.out.println("1");
                    System.out.println("path=" + event.getPath());
                    System.out.println("eventType=" + event.getType());
                    if (event.getType() == Event.EventType.NodeChildrenChanged) {
                        zooKeeper.getChildren("/watcher3", this);
                    }
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        });

        zooKeeper.getChildren("/watcher3", new Watcher() {
            @Override
            public void process(WatchedEvent event) {
                try {
                    System.out.println("2");
                    System.out.println("path=" + event.getPath());
                    System.out.println("eventType=" + event.getType());
                    if (event.getType() == Event.EventType.NodeChildrenChanged) {
                        zooKeeper.getChildren("/watcher3", this);
                    }
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        });
        Thread.sleep(50000);
        System.out.println("結束");
    }
}

1.7 配置中心案例

  工作中有這樣的一個場景: 數據庫用戶名和密碼信息放在一個配置文件中,應用讀取該配置文件,配置文件信息放入緩存。

​  若數據庫的用戶名和密碼改變時候,還需要重新加載緩存,比較麻煩,通過ZooKeeper可以輕鬆完成,當數據庫發生變化時自動完成緩存同步。

設計思路:

  1. 連接zookeeper服務器
  2. 讀取zookeeper中的配置信息,註冊watcher監聽器,存入本地變量
  3. 當zookeeper中的配置信息發生變化時,通過watcher的回調方法捕獲數據變化事件
  4. 重新獲取配置信息

案例:

import java.util.concurrent.CountDownLatch;

import com.itcast.watcher.ZKConnectionWatcher;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.Watcher.Event.EventType;
import org.apache.zookeeper.ZooKeeper;

public class MyConfigCenter implements Watcher {

    //  zk的連接串
    private String IP = "192.168.188.133:2181";
    //  計數器對象
    private CountDownLatch countDownLatch = new CountDownLatch(1);
    // 連接對象
    private static ZooKeeper zooKeeper;

    // 用於本地化存儲配置信息
    private String url;
    private String username;
    private String password;

    @Override
    public void process(WatchedEvent event) {
        try {
            // 捕獲事件狀態
            if (event.getType() == EventType.None) {
                if (event.getState() == Event.KeeperState.SyncConnected) {
                    System.out.println("連接成功");
                    countDownLatch.countDown();
                } else if (event.getState() == Event.KeeperState.Disconnected) {
                    System.out.println("連接斷開!");
                } else if (event.getState() == Event.KeeperState.Expired) {
                    System.out.println("連接超時!");
                    // 超時後服務器端已經將連接釋放,需要重新連接服務器端
                    zooKeeper = new ZooKeeper(IP, 6000,
                            new ZKConnectionWatcher());
                } else if (event.getState() == Event.KeeperState.AuthFailed) {
                    System.out.println("驗證失敗!");
                }
                // 當配置信息發生變化時
            } else if (event.getType() == EventType.NodeDataChanged) {
                initValue();
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    // 構造方法
    private MyConfigCenter() {
        try {
            // 創建連接對象
            zooKeeper = new ZooKeeper(IP, 5000, this);
            // 阻塞線程,等待連接的創建成功
            countDownLatch.await();
            initValue();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
    // 連接zookeeper服務器,讀取配置信息
    private void initValue() {
        try {
            // 讀取配置信息
            this.url = new String(zooKeeper.getData("/config/url", true, null));
            this.username = new String(zooKeeper.getData("/config/username", true, null));
            this.password = new String(zooKeeper.getData("/config/password", true, null));
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }


    public static void main(String[] args) {
        try {
            MyConfigCenter myConfigCenter = new MyConfigCenter();
            for (int i = 1; i <= 20; i++) {
                Thread.sleep(5000);
                System.out.println("url:"+myConfigCenter.getUrl());
                System.out.println("username:"+myConfigCenter.getUsername());
                System.out.println("password:"+myConfigCenter.getPassword());
                System.out.println("########################################");
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }


    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
    
}

1.8 生成分佈式唯一ID

  在過去的單庫單表型系統中,通常可以使用數據庫字段自帶的auto_increment屬性來自動爲每條記錄生成一個唯一的ID。但是分庫分表後,就無法在依靠數據庫的auto_increment屬性來唯一標識一條記錄了。此時我們就可以用zookeeper在分佈式環境下生成全局唯一ID。

設計思路:

  1. 連接zookeeper服務器
  2. 指定路徑生成臨時有序節點
  3. 取序列號及爲分佈式環境下的唯一ID

案例:

import java.util.concurrent.CountDownLatch;

import com.itcast.watcher.ZKConnectionWatcher;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.Watcher.Event.KeeperState;
import org.apache.zookeeper.ZooDefs.Ids;
import org.apache.zookeeper.ZooKeeper;

public class GloballyUniqueId implements Watcher {
    //  zk的連接串
    private String IP = "192.168.188.133:2181";
    //  計數器對象
    private CountDownLatch countDownLatch = new CountDownLatch(1);
    //  用戶生成序號的節點
    private String defaultPath = "/uniqueId";
    //  連接對象
    private ZooKeeper zooKeeper;

    @Override
    public void process(WatchedEvent event) {
        try {
            // 捕獲事件狀態
            if (event.getType() == Event.EventType.None) {
                if (event.getState() == KeeperState.SyncConnected) {
                    System.out.println("連接成功");
                    countDownLatch.countDown();
                } else if (event.getState() == KeeperState.Disconnected) {
                    System.out.println("連接斷開!");
                } else if (event.getState() == KeeperState.Expired) {
                    System.out.println("連接超時!");
                    // 超時後服務器端已經將連接釋放,需要重新連接服務器端
                    zooKeeper = new ZooKeeper(IP, 6000,
                            new ZKConnectionWatcher());
                } else if (event.getState() == KeeperState.AuthFailed) {
                    System.out.println("驗證失敗!");
                }
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    // 構造方法
    private GloballyUniqueId() {
        try {
            //打開連接
            zooKeeper = new ZooKeeper(IP, 5000, this);
            // 阻塞線程,等待連接的創建成功
            countDownLatch.await();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    // 生成id的方法
    private String getUniqueId() {
        String path = "";
        try {
            //創建臨時有序節點
            path = zooKeeper.create(defaultPath, new byte[0], Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        // /uniqueId0000000001
        return path.substring(9);
    }

    public static void main(String[] args) {
        GloballyUniqueId globallyUniqueId = new GloballyUniqueId();
        for (int i = 1; i <= 5; i++) {
            String id = globallyUniqueId.getUniqueId();
            System.out.println(id);
        }
    }

}

1.9 分佈式鎖

  分佈式鎖有多種實現方式,比如通過數據庫、redis都可實現。作爲分佈式協同工具ZooKeeper,當然也有着標準的實現方式。下面介紹在zookeeper中如何實現排他鎖。

設計思路:

  1. 每個客戶端往/Locks下創建臨時有序節點/Locks/Lock_000000001
  2. 客戶端取得/Locks下子節點,並進行排序,判斷排在最前面的是否爲自己,如果自己的鎖節點在第一位,代表獲取鎖成功
  3. 如果自己的鎖節點不在第一位,則監聽自己前一位的鎖節點。例如,自己鎖節點Lock_000000001
  4. 當前一位鎖節點(Lock_000000002)的邏輯
  5. 監聽客戶端重新執行第2步邏輯,判斷自己是否獲得了鎖

案例:


import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;

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

public class MyLock {
    //  zk的連接串
    private String IP = "192.168.188.133:2181";
    //  計數器對象
    private CountDownLatch countDownLatch = new CountDownLatch(1);
    //ZooKeeper配置信息
    private ZooKeeper zooKeeper;
    private static final String LOCK_ROOT_PATH = "/Locks";
    private static final String LOCK_NODE_NAME = "Lock_";
    private String lockPath;

    // 打開zookeeper連接
    MyLock() {
        try {
            zooKeeper = new ZooKeeper(IP, 5000, new Watcher() {
                @Override
                public void process(WatchedEvent event) {
                    if (event.getType() == Event.EventType.None) {
                        if (event.getState() == Event.KeeperState.SyncConnected) {
                            System.out.println("連接成功!");
                            countDownLatch.countDown();
                        }
                    }
                }
            });
            countDownLatch.await();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    //獲取鎖
    public void acquireLock() throws Exception {
        //創建鎖節點
        createLock();
        //嘗試獲取鎖
        attemptLock();
    }

    //創建鎖節點
    private void createLock() throws Exception {
        //判斷Locks是否存在,不存在創建
        Stat stat = zooKeeper.exists(LOCK_ROOT_PATH, false);
        if (stat == null) {
            zooKeeper.create(LOCK_ROOT_PATH, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
        }
        // 創建臨時有序節點
        lockPath = zooKeeper.create(LOCK_ROOT_PATH + "/" + LOCK_NODE_NAME, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
        System.out.println("節點創建成功:" + lockPath);
    }

    //監視器對象,監視上一個節點是否被刪除
    private final Watcher watcher = new Watcher() {
        @Override
        public void process(WatchedEvent event) {
            if (event.getType() == Event.EventType.NodeDeleted) {
                synchronized (this) {
                    notifyAll();
                }
            }
        }
    };

    //嘗試獲取鎖
    private void attemptLock() throws Exception {
        // 獲取Locks節點下的所有子節點
        List<String> list = zooKeeper.getChildren(LOCK_ROOT_PATH, false);
        // 對子節點進行排序
        Collections.sort(list);
        // /Locks/Lock_000000001
        int index = list.indexOf(lockPath.substring(LOCK_ROOT_PATH.length() + 1));
        if (index == 0) {
            System.out.println("獲取鎖成功!");
        } else {
            // 上一個節點的路徑
            String path = list.get(index - 1);
            Stat stat = zooKeeper.exists(LOCK_ROOT_PATH + "/" + path, watcher);
            if (stat == null) {
                attemptLock();
            } else {
                synchronized (watcher) {
                    watcher.wait();
                }
                attemptLock();
            }
        }

    }

    //釋放鎖
    public void releaseLock() throws Exception {
            //刪除臨時有序節點
            zooKeeper.delete(this.lockPath,-1);
            zooKeeper.close();
            System.out.println("鎖已經釋放:"+this.lockPath);
    }

    public static void main(String[] args) {
        try {
            MyLock myLock = new MyLock();
            myLock.createLock();
        } catch (Exception ex) {
            ex.printStackTrace();
        }

    }
}
public class TicketSeller {

    private void sell(){
        System.out.println("售票開始");
        // 線程隨機休眠數毫秒,模擬現實中的費時操作
        int sleepMillis = 5000;
        try {
            //代表複雜邏輯執行了一段時間
            Thread.sleep(sleepMillis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("售票結束");
    }

    private void sellTicketWithLock() throws Exception {
        MyLock lock = new MyLock();
        // 獲取鎖
        lock.acquireLock();
        sell();
        //釋放鎖
        lock.releaseLock();
    }
    public static void main(String[] args) throws Exception {
        TicketSeller ticketSeller = new TicketSeller();
        for(int i=0;i<10;i++){
            ticketSeller.sellTicketWithLock();
        }
    }
}

2. zookeeper 集羣搭建

  單機環境下,jdk、zookeeper 安裝完畢,基於一臺虛擬機,進行zookeeper僞集羣搭建,zookeeper集羣中包含3個節點,節點對外提供服務端口號分別爲2181、2182、2183

  1. 基於zookeeper-3.4.9複製三份zookeeper安裝好的服務器文件,目錄名稱分別爲zookeeper2181、zookeeper2182、zookeeper2183
cp -r zookeeper-3.4.9 zookeeper2181
cp -r zookeeper-3.4.9 zookeeper2182
cp -r zookeeper-3.4.9 zookeeper2183
  1. 修改zookeeper2181服務器對應配置文件。
#服務器對應端口號
clientPort=2181
#數據快照文件所在路徑
dataDir=/opt/zookeeper2181/data
#集羣配置信息
#server.A=B:C:D    
#A:是一個數字,表示這個是服務器的編號    
#B:是這個服務器的ip地址    
#C:Zookeeper服務器之間的通信端口    
#D:Leader選舉的端口    
server.1=192.168.188.133:2287:3387
server.2=192.168.188.133:2288:3388
server.3=192.168.188.133:2289:3389
  1. 在 上一步 dataDir 指定的目錄下,創建 myid 文件,然後在該文件添加上一步server 配置的對應 A 數字。
#zookeeper2181對應的數字爲1
#/opt/zookeeper2181/data目錄下執行命令
echo "1" > myid
  1. zookeeper2182、zookeeper2183參照步驟2/3進行相應配置
  2. 分別啓動三臺服務器,檢驗集羣狀態
    啓動命令:
./zkServer.sh start

登錄命令:

./zkCli.sh -server 192.168.188.133:2181
./zkCli.sh -server 192.168.188.133:2182
./zkCli.sh -server 192.168.188.133:2183

3. 一致性協議:zab協議

  zab協議 的全稱是 Zookeeper Atomic Broadcast (zookeeper原子廣播)。zookeeper 是通過 zab協議來保證分佈式事務的最終一致性
  基於zab協議,zookeeper集羣中的角色主要有以下三類,如下表所示:
在這裏插入圖片描述
  zab廣播模式工作原理,通過類似兩階段提交協議的方式解決數據一致性:
在這裏插入圖片描述

  1. leader從客戶端收到一個寫請求
  2. leader生成一個新的事務併爲這個事務生成一個唯一的ZXID
  3. leader將這個事務提議(propose)發送給所有的follows節點
  4. follower節點將收到的事務請求加入到歷史隊列(history queue)中,併發送ack給leader
  5. 當leader收到大多數follower(半數以上節點)的ack消息,leader會發送commit請求
  6. 當follower收到commit請求時,從歷史隊列中將事務請求commit

4. zookeeper的leader選舉

4.1 服務器狀態

looking:尋找leader狀態。當服務器處於該狀態時,它會認爲當前集羣中沒有leader,因此需要進入leader選舉狀態。
leading: 領導者狀態。表明當前服務器角色是leader。
following: 跟隨者狀態。表明當前服務器角色是follower。
observing:觀察者狀態。表明當前服務器角色是observer。

4.2 服務器啓動時期的leader選舉

  在集羣初始化階段,當有一臺服務器server1啓動時,其單獨無法進行和完成leader選舉,當第二臺服務器server2啓動時,此時兩臺機器可以相互通信,每臺機器都試圖找到leader,於是進入leader選舉過程。選舉過程如下:

  1. 每個server發出一個投票。由於是初始情況,server1和server2都會將自己作爲leader服務器來進行投票,每次投票會包含所推舉的服務器的myid和zxid,使用(myid, zxid)來表示,此時server1的投票爲(1, 0),server2的投票爲(2, 0),然後各自將這個投票發給集羣中其他機器。
  2. 集羣中的每臺服務器接收來自集羣中各個服務器的投票。
  3. 處理投票。針對每一個投票,服務器都需要將別人的投票和自己的投票進行pk,pk規則如下:
  • 優先檢查zxid。zxid比較大的服務器優先作爲leader。
  • 如果zxid相同,那麼就比較myid。myid較大的服務器作爲leader服務器。

對於Server1而言,它的投票是(1, 0),接收Server2的投票爲(2, 0),首先會比較兩者的zxid,均爲0,再比較myid,此時server2的myid最大,於是更新自己的投票爲(2, 0),然後重新投票,對於server2而言,其無須更新自己的投票,只是再次向集羣中所有機器發出上一次投票信息即可。

  1. 統計投票。每次投票後,服務器都會統計投票信息,判斷是否已經有過半機器接受到相同的投票信息,對於server1、server2而言,都統計出集羣中已經有兩臺機器接受了(2, 0)的投票信息,此時便認爲已經選出了leader
  2. 改變服務器狀態。一旦確定了leader,每個服務器就會更新自己的狀態,如果是follower,那麼就變更爲following,如果是leader,就變更爲leading。

4.3 服務器運行時期的Leader選舉

  在zookeeper運行期間,leader與非leader服務器各司其職,即便當有非leader服務器宕機或新加入,此時也不會影響leader,但是一旦leader服務器掛了,那麼整個集羣將暫停對外服務,進入新一輪leader選舉,其過程和啓動時期的Leader選舉過程基本一致。
​  假設正在運行的有server1、server2、server3三臺服務器,當前leader是server2,若某一時刻leader掛了,此時便開始Leader選舉。選舉過程如下:

  1. 變更狀態。leader掛後,餘下的服務器都會將自己的服務器狀態變更爲looking,然後開始進入leader選舉過程。
  2. 每個server會發出一個投票。在運行期間,每個服務器上的zxid可能不同,此時假定server1的zxid爲122,server3的zxid爲122,在第一輪投票中,server1和server3都會投自己,產生投票(1, 122),(3, 122),然後各自將投票發送給集羣中所有機器。
  3. 接收來自各個服務器的投票。與啓動時過程相同
  4. 處理投票。與啓動時過程相同,此時,server3將會成爲leader。
  5. 統計投票。與啓動時過程相同。
  6. 改變服務器的狀態。與啓動時過程相同。

5. observer角色及其配置

​  observer角色特點:

  1. 不參與集羣的leader選舉
  2. 不參與集羣中寫數據時的ack反饋

​  爲了使用observer角色,在任何想變成observer角色的配置文件中加入如下配置:

peerType=observer

​  並在所有server的配置文件中,配置成observer模式的server的那行配置追加:observer,例如:

server.3=192.168.188.133:2289:3389:observer

6. zookeeperAPI連接集羣

ZooKeeper(String connectionString, int sessionTimeout, Watcher watcher)
  • connectionString - zooKeeper集合主機。
  • sessionTimeout - 會話超時(以毫秒爲單位)。
  • watcher - 實現“監視器”界面的對象。ZooKeeper集合通過監視器對象返回連接狀態。

案例:

import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;

import java.util.concurrent.CountDownLatch;

public class ZookeeperConnection {
    public static void main(String[] args) {
        try {
            // 計數器對象
            CountDownLatch countDownLatch=new CountDownLatch(1);
            // arg1:服務器的ip和端口
            // arg2:客戶端與服務器之間的會話超時時間  以毫秒爲單位的
            // arg3:監視器對象
            ZooKeeper zooKeeper=new ZooKeeper("192.168.188.134:2181,192.168.188.134:2182,192.168.188.134:2183",
                    5000, new Watcher() {
                @Override
                public void process(WatchedEvent event) {
                    if(event.getState()==Event.KeeperState.SyncConnected) {
                        System.out.println("連接創建成功!");
                        countDownLatch.countDown();
                    }
                }
            });
            // 主線程阻塞等待連接對象的創建成功
            countDownLatch.await();
            // 會話編號
            System.out.println(zooKeeper.getSessionId());
            zooKeeper.close();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章