分散式系列七: zookeeper簡單用法

罪惡斯巴克發表於2018-10-03

zookeeper是分散式開源框架, 是Google Chubby的一個實現, 主要作為分散式系統的協調服務. Dobbo等框架使用了其功能.

zookeeper特性

  • 順序一致性: 事務請求最終會嚴格按順序執行
  • 原子性:
  • 可靠性:
  • 實時性:
  • 單一檢視:

安裝

使用windows的linux子系統時: cd /mnt/e/chromedownload/轉到windows下載路徑
拷貝 cp /mnt/e/chromedownload/zookeeper.tar.gz /program/zookeeper.tar.gz
轉到 cd /program 如果沒有的先mkdir program
解壓 tar -zxvf zookeeper.tar.gz
轉到 cd ZK_HOME/conf
拷貝 cp zoo_sample.cfg zoo.cfg

轉到 cd ZK_HOME/bin
啟動 sh zkServer.sh start

叢集搭建

  • zoo.cfg中配置叢集, 配置格式如 server.id=ip:port:port ; 有幾臺就配置幾個.例如:
    server.1=192.168.1.145:2897:3181
    server.2=192.168.1.146:2897:3181
    server.3=192.168.1.147:2897:3181
  • zoo.cfg中的dataDir所配置的目錄下新增myid檔案, 值對應server.id的id
  • zoo.cfg中, 如果需要observer,則新增peerType=observer, 並且修改server.id=ip:port:port:observer

zoo.cfg 配置

  • tickTime=2000 zk的方法最小時間單位
  • initTime=10 時長為10*tickTime, follow節點和leader節點同步的時間
  • syncLimit=5 時長為5*tickTime, leader和follow幾點進行心跳檢測的最大延遲時間
  • dataDir=/tmp/zookeeper 儲存快照檔案的目錄
  • dataLogDir = /log/zookeeper 事務日誌的儲存路徑, 預設在dataDir下
  • clientPort=2181 連線zookeeper的預設埠

zookeeper幾個概念

  • znode: zookeeper資料儲存為樹形結構, 深度層級沒有限制, znode是資料儲存節點,是zookeeper的最小儲存單元. 分為1.持久化節點;2.持久化有序節點;3.臨時節點;4.臨時有序節點;
  • 臨時節點, 是會話時生成的節點, 會話結束後節點會自動刪除.

客戶端命令操作

help 可以檢視客戶端支援的命令

  • create [-s] [-e] node : 建立節點 -s是有序 -e臨時節點
  • get path [watch] 獲取節點的資訊
  • set path data [version] : 修改節點的值
  • delete path [version] : 刪除節點, 當節點有子節點時無法刪除

[version] 樂觀鎖

[Watcher] 提供了釋出/訂閱, 允許客戶端向伺服器端註冊一個監聽, 當服務端觸發指定事件時會觸發watcher,服務端向客戶端傳送一個通知.
Watcher是一次性的, 觸發一次後自動失效

資訊節點

  • stat path 可以檢視節點的資訊

    cversion=0 子節點的版本
    AclVersion=0 acl的版本號, 許可權控制相關
    dataVersion=1 資料的版本號
    cxid 建立的事務id
    mzxid 最後一次修改的事務id
    pzxid 子節點最後一次修改的事務id
    ephemeralOwner 臨時會話的id
    dataLength 資料長度
    numChidren 子節點數量

java開發

引用依賴

 <dependency>
    <groupId>org.apache.zookeeper</groupId>
    <artifactId>zookeeper</artifactId>
    <version>3.5.4-beta</version>
</dependency>

java程式碼

/**
 * 定義Watcher
 */
public class MyWatcher implements Watcher {
    @Override
    public void process(WatchedEvent event) {
        if(event.getState()== Event.KeeperState.SyncConnected){
            System.out.println("---->>>>>"+event.getType());
            System.out.println("---->>>>>"+event.getPath());
        }
    }
}

//測試
public class MySession {

    private static final String CONN = "localhost:2181";
    private static Stat stat = new Stat();

    public static void main(String[] args) throws IOException, KeeperException, InterruptedException {
        ZooKeeper zooKeeper = new ZooKeeper(CONN,1000,new MyWatcher());

        // 建立
        zooKeeper.create("/xlx","this is a string".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
        // 查詢,註冊watcher
        byte[] rst = zooKeeper.getData("/xlx",true,stat);
        System.out.println(new String(rst));

        //刪除(只能刪除永久節點)
        zooKeeper.delete("/xlx",-1);

        // 建立
        zooKeeper.create("/xlx","this is a string".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);

        // 查詢
        byte[] rs = zooKeeper.getData("/xlx",true,stat);
        System.out.println(new String(rs));

        // 註冊watcher
        zooKeeper.exists("/xlx/yy",true);

        // 建立
        zooKeeper.create("/xlx/yy","this is a sub child string".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);

        // 獲取子節點
        List<String> children = zooKeeper.getChildren("/xlx", true);
        System.out.println(children);

        //刪除(只能刪除永久節點)
        zooKeeper.delete("/xlx/yy",-1);

        // 修改
        zooKeeper.setData("/xlx","this is a modified string".getBytes(),-1);
        byte[] rss = zooKeeper.getData("/xlx",true,stat);
        System.out.println(new String(rss));

        //刪除(只能刪除永久節點)
        zooKeeper.delete("/xlx",-1);

        // watcher 非同步的, 這裡停留段時間才可以檢視到watcher列印的資訊
        Thread.sleep(2000);
    }
}

三方API

  • zkClient
  • curator 這個用的較多,Netflix開源

curator開發

特點:

  • 抽象層次更高
  • 鏈式程式設計風格
  • 非同步回撥
public class CuratorSession {

    private static final String CONN = "localhost:2181";

    public static void main(String[] args) throws Exception {
        // 建立
        CuratorFramework curatorFramework = CuratorFrameworkFactory.newClient(CONN, 2000, 5000, new ExponentialBackoffRetry(1000,3));
        curatorFramework.start();
        System.out.println(curatorFramework.getState());
        //另一種方式
        //curatorFramework = CuratorFrameworkFactory.builder().build();

        // 新增節點
        curatorFramework.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT).forPath("/curator/chd/dd","dfadfe".getBytes());

        //讀取
        byte[] data = curatorFramework.getData().forPath("/curator/chd/dd");
        java.lang.String string = new java.lang.String(data);
        System.out.println(string);

        // 修改
        Stat stat = curatorFramework.setData().forPath("/curator/chd/dd","fdaefv".getBytes());
        System.out.println(stat);

        curatorFramework.setData().inBackground(new BackgroundCallback() {
            @Override
            public void processResult(CuratorFramework client, CuratorEvent event) throws Exception {
                System.out.println(event);
            }
        }).forPath("/curator/chd/dd","fafdae".getBytes());

        //刪除
        curatorFramework.delete().guaranteed().deletingChildrenIfNeeded().forPath("/curator/chd");

        Thread.sleep(2000);
    }
}

zookeeper應用場景

  • 分散式鎖

    可以使用redis,資料庫,zookeeper等實現.
    排他鎖(寫鎖), 共享鎖(讀鎖)

  • 釋出/訂閱(配置中心)

    配置中心資料的特點: 1. 資料量比較小;2.各臺伺服器的配置內容一致;3.配置資訊在執行時會發生變更;

    配置中心有兩種實現方式: pull,push; zookeeper結合這兩種方式實現配置中心. 具體為: 客戶端向伺服器註冊自己關注的節點, 當節點發生變化時, 伺服器向客戶端傳送watcher事件通知,再由客戶端pull變化的節點資料.

  • 負載均衡

    leader處理請求, 並將請求路由到特定節點. 核心是負載均衡的演算法.

  • leader選舉
  • 命名服務
  • 分散式佇列

分散式鎖實現(java api)

原理: 當需要鎖時, 客戶端會向服務端的節點A寫有序的子節點, 此時可能有其他的客戶端也同時需要獲取鎖,因此節點A下可能會有若干子節點001,002,003...., 然後客戶端獲取父節點A的所有子節點,並取得其中最小的節點001, 如果001是當前客戶端建立的, 則當前客戶端獲取到鎖;如果001不是當前客戶端建立, 比如當前客戶端建立了003, 則取003的前一個節點,也就是002新增監控watcher, 當002監控事件為刪除時, 建立了003的客戶端得到一個通知, 說明前一個節點已經釋放, 此時003可以獲取到鎖.而002節點則監控001, 也就是說每個節點監控其前一個節點, 前一個節點釋放, 則表示當前節點可以獲取到鎖了.

public class DistributeLock {

    private static final String ROOT_LOCKS="/LOCK";
    private ZooKeeper zooKeeper;
    private int sessionTimeout;
    private String lockID;
    private final static byte[] date={1,2};
    private CountDownLatch countDownLatch = new CountDownLatch(1);

    public DistributeLock() throws IOException, InterruptedException {
        this.zooKeeper = ZookeeperClient.getZookeeperClient();
        this.sessionTimeout = ZookeeperClient.SESSIONTIMEOUT;
    }

    public boolean lock(){
        try{
            lockID = zooKeeper.create(ROOT_LOCKS+"/",date, ZooDefs.Ids.OPEN_ACL_UNSAFE,CreateMode.EPHEMERAL_SEQUENTIAL);
            System.out.println(Thread.currentThread().getName()+"->成功建立了lock節點["+lockID+"], 開始去競爭鎖");
            List<String> childrenNodes = zooKeeper.getChildren(ROOT_LOCKS,true);
            SortedSet<String> sortedSet = new TreeSet<>();
            for(String children:childrenNodes){
                sortedSet.add(ROOT_LOCKS+"/"+children);
            }
            String first = sortedSet.first();
            if (first.equals(lockID)){
                System.out.println(Thread.currentThread().getName()+"->成功獲得鎖,lock節點為:["+lockID+"]");
                return true;
            }

            SortedSet<String> lessThanLockId = ((TreeSet<String>) sortedSet).headSet(lockID);
            if (!lessThanLockId.isEmpty()){
                String prevLockID = lessThanLockId.last();
                zooKeeper.exists(prevLockID,new LockWatcher(countDownLatch));
                countDownLatch.await(sessionTimeout,TimeUnit.MILLISECONDS);
                System.out.println(Thread.currentThread().getName()+" 成功獲取鎖:["+lockID+"]");
            }
        }catch (Exception e){e.printStackTrace();}
        return false;
    }

    public boolean unlock(){
        System.out.println(Thread.currentThread().getName()+"->開始釋放鎖:["+lockID+"]");
        try {
            zooKeeper.delete(lockID,-1);
            System.out.println("節點["+lockID+"]成功被刪除");
            return true;
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (KeeperException e) {
            e.printStackTrace();
        }
        return false;
    }


    public static void main(String[] args) {
        final CountDownLatch latch=new CountDownLatch(10);
        Random random=new Random();
        for(int i=0;i<10;i++){
            new Thread(()->{
                DistributeLock lock=null;
                try {
                    lock=new DistributeLock();
                    latch.countDown();
                    latch.await();
                    lock.lock();
                    Thread.sleep(random.nextInt(500));
                } catch (IOException e) {
                    e.printStackTrace();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    if(lock!=null){
                        lock.unlock();
                    }
                }
            }).start();
        }
    }
}

這些程式碼寫下來就算入門了, curator真正有用的使用場景還沒接觸到, 比如分散式鎖,leader選舉等, curator有示例程式, 可以在github上檢視Curator原始碼.

相關文章