Apache Curator 操作Zookeeper api

codeing_docs發表於2019-01-16

Zookeeper客戶端(Apache Curator)

ZooKeeper常用客戶端

  • zookeeper自帶的客戶端是官方提供的,比較底層、使用起來寫程式碼麻煩、不夠直接。
  • Apache Curator是Apache的開源專案,封裝了zookeeper自帶的客戶端,使用相對簡便,易於使用。
  • zkclient是另一個開源的ZooKeeper客戶端,其地址:github.com/adyliu/zkcl…

Curator主要解決了三類問題

  • 封裝ZooKeeper client與ZooKeeper server之間的連線處理
  • 提供了一套Fluent風格的操作API
  • 提供ZooKeeper各種應用場景(recipe, 比如共享鎖服務, 叢集領導選舉機制)的抽象封裝

Java操作api

package com.qxw.controller;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.apache.commons.lang.StringUtils;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.data.Stat;
/**
 *  Curator主要解決了三類問題
	1.封裝ZooKeeper client與ZooKeeper server之間的連線處理
	2.提供了一套Fluent風格的操作API
	3.提供ZooKeeper各種應用場景(recipe, 比如共享鎖服務, 叢集領導選舉機制)的抽象封裝
 * @author qxw
 * @data 2018年8月14日下午2:08:51
 */
public class CuratorAp {
	/**
	 * Curator客戶端
	 */
    public static CuratorFramework client = null;
    /**
     * 叢集模式則是多個ip
     */
//    private static final String zkServerIps = "192.168.10.124:2182,192.168.10.124:2183,192.168.10.124:2184";
    private static final String zkServerIps = "127.0.0.1:2181";

	public static CuratorFramework getConnection(){
	        if(client==null){
	             synchronized (CuratorAp.class){
	               if(client==null){
	            		//通過工程建立連線
	           		client= CuratorFrameworkFactory.builder()
	           				.connectString(zkServerIps)
	           				.connectionTimeoutMs(5000) ///連線超時時間
	           				.sessionTimeoutMs(5000)  // 設定會話時間
	           				.retryPolicy(new ExponentialBackoffRetry(1000, 10))   // 重試策略:初試時間為1s 重試10次
//	           				.namespace("super")  // 設定名稱空間以及開始建立連線
	           				.build();
	           		
	           		 //開啟連線
	           		  client.start();
	           		  //分佈鎖
	           		 
		        	  System.out.println(client.getState());
	                }
	            }
	        }
			return client;
	  }
	
	/**
	 * 建立節點   不加withMode預設為持久型別節點
	 * @param path  節點路徑
	 * @param value  值
	 */
	public static String create(String path,String value){
		try {
			//若建立節點的父節點不存在會先建立父節點再建立子節點
			return getConnection().create().creatingParentsIfNeeded().forPath("/super"+path,value.getBytes());
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}
	
	/**
	 * 建立節點 
	 * @param path  節點路徑
	 * @param value  值
	 * @param modeType 節點型別
	 */
	public static String create(String path,String value,String modeType){
		try {
			if(StringUtils.isEmpty(modeType)){
				return null;
			}
			//持久型節點
			if(CreateMode.PERSISTENT.equals(modeType)){
				//若建立節點的父節點不存在會先建立父節點再建立子節點
				return getConnection().create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT).forPath("/super"+path,value.getBytes());
			}
			//臨時節點
			if(CreateMode.EPHEMERAL.equals(modeType)){
				//若建立節點的父節點不存在會先建立父節點再建立子節點
				return getConnection().create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL).forPath("/super"+path,value.getBytes());
			}
			
			//持久型別順序性節點
			if(CreateMode.PERSISTENT_SEQUENTIAL.equals(modeType)){
				//若建立節點的父節點不存在會先建立父節點再建立子節點
				return getConnection().create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT_SEQUENTIAL).forPath("/super"+path,value.getBytes());
			}
			
			//臨時型別順序性節點
			if(CreateMode.EPHEMERAL_SEQUENTIAL.equals(modeType)){
				//若建立節點的父節點不存在會先建立父節點再建立子節點
				return getConnection().create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL_SEQUENTIAL).forPath("/super"+path,value.getBytes());
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}
	
	
	
	
	
	/**
	 * 獲取單個節點
	 * @param path
	 * @return
	 */
	public static String getData(String path){
		try {
			String str = new String(getConnection().getData().forPath("/super"+path));
			return str;
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}

	/**
	 *獲取位元組點
	 * @param path
	 * @return
	 */
	public static List<String> getChildren(String path){
		try {
			List<String> list = getConnection().getChildren().forPath("/super"+path);
			return list;
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}
	
	
	/**
	 * 修改節點值
	 * @param path
	 * @param valu
	 * @return
	 */
	public static String setData(String path,String valu){
		try {
			getConnection().setData().forPath("/super"+path,valu.getBytes());
			String str = new String(getConnection().getData().forPath("/super"+path));
			return str;
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * 刪除節點
	 * @param path
	 */
	public static void  delete(String path){
		try {
			getConnection().delete().guaranteed().deletingChildrenIfNeeded().forPath("/super"+path);
		} catch (Exception e) {
			e.printStackTrace();
		}
		
	}
	/**
	 * 檢測節點是否存在
	 * @param path
	 * @return
	 */
	public static boolean  checkExists(String path){
		try {
			Stat s=getConnection().checkExists().forPath("/super"+path);
			return s==null? false:true;
		} catch (Exception e) {
			e.printStackTrace();
		}
		return false;
		
	}
	
	/**
	 * 分散式鎖 物件
	 * @param path
	 * @return
	 */
	public static InterProcessMutex getLock(String path){
		InterProcessMutex lock=null;
		try {
			lock=new InterProcessMutex(getConnection(), "/super"+path);
			 return  lock;
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}
	
	
	
	public static void main(String[] args) throws Exception {
//		if(checkExists("/qxw")){
//			delete("/qxw");
//		}
//		System.out.println("建立節點:"+create("/qxw/q1", "蘇打水法薩芬撒"));
//		System.out.println("建立節點:"+create("/qxw/q2", "蘇打水法薩芬撒"));
//		System.out.println("建立節點:"+create("/qxw/q3", "蘇打水法薩芬撒"));
//		
//
//		
//		ExecutorService pool = Executors.newCachedThreadPool();
//		getConnection().create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT).inBackground(new BackgroundCallback() {
//			public void processResult(CuratorFramework cf, CuratorEvent ce) throws Exception {
//				System.out.println("code:" + ce.getResultCode());
//				System.out.println("type:" + ce.getType());
//				System.out.println("執行緒為:" + Thread.currentThread().getName());
//			}
//		}, pool)
//		.forPath("/super/qxw/q4","q4內容".getBytes());
//		
//		System.out.println("讀取節點: "+getData("/qxw"));
//		System.out.println("讀取位元組點:"+getChildren("/qxw").toString());
		
		test();
		
	}
	
	/***
	 * 分佈鎖演示
	 */
	private static int count=0;
	public  static void test() throws InterruptedException{
		final InterProcessMutex lock=getLock("/lock");
		final CountDownLatch c=new CountDownLatch(10);
		ExecutorService pool = Executors.newCachedThreadPool();
		for (int i = 0; i <10; i++) {
			pool.execute(new Runnable() {
				public void run() {
					try {		
						c.countDown();
						Thread.sleep(1000);
						//加鎖
						lock.acquire();
						System.out.println(System.currentTimeMillis()+"___"+(++count));
					} catch (Exception e) {
						e.printStackTrace();
					}finally{
						try {
							lock.release();
						} catch (Exception e) {
							e.printStackTrace();
						}
					}
					
				}
			});
		}
		pool.shutdown();
		c.await();
		System.out.println("CountDownLatch執行完");
	}
}

複製程式碼

zookeeper 叢集的 監控圖形化頁面

gitee.com/crystony/zo…

如果你是gradle使用者(2.0以上),請直接執行以下命令執行專案:

gradle jettyRun
複製程式碼

如果你沒使用gralde,執行專案跟路徑下的指令碼,linux/windows使用者執行

gradlew/gradlew.bat jettyRun
複製程式碼

自動下載gralde完成後,會自動使用jetty啟動專案

如果想將專案匯入IDE除錯,eclipse使用者執行

 gradlew/gradlew.bat eclipse
複製程式碼

idea使用者執行

gradlew/gradlew.bat idea
複製程式碼

zookeeper分散式鎖原理

分散式鎖主要用於在分散式環境中保護跨程式、跨主機、跨網路的共享資源實現互斥訪問,以達到保證資料的一致性。

輸入圖片說明
螢幕截圖.png

左邊的整個區域表示一個Zookeeper叢集,locker是Zookeeper的一個持久節點,node_1、node_2、node_3是locker這個持久節點下面的臨時順序節點。client_1、client_2、client_n表示多個客戶端,Service表示需要互斥訪問的共享資源。

分散式鎖獲取思路

  1. 在獲取分散式鎖的時候在locker節點下建立臨時順序節點,釋放鎖的時候刪除該臨時節點。
  2. 客戶端呼叫createNode方法在locker下建立臨時順序節點,然後呼叫getChildren(“locker”)來獲取locker下面的所有子節點,注意此時不用設定任何Watcher。
  3. 客戶端獲取到所有的子節點path之後,如果發現自己建立的子節點序號最小,那麼就認為該客戶端獲取到了鎖。
  4. 如果發現自己建立的節點並非locker所有子節點中最小的,說明自己還沒有獲取到鎖,此時客戶端需要找到比自己小的那個節點,然後對其呼叫exist()方法,同時對其註冊事件監聽器。
  5. 之後,等待它釋放鎖,也就是等待獲取到鎖的那個客戶端B把自己建立的那個節點刪除。,則客戶端A的Watcher會收到相應通知,此時再次判斷自己建立的節點是否是locker子節點中序號最小的,如果是則獲取到了鎖,如果不是則重複以上步驟繼續獲取到比自己小的一個節點並註冊監聽。

相關文章