1. 程式人生 > >Zookeeper的Java客戶端(要把zookeeper用的足夠熟練,才能說掌握了它)

Zookeeper的Java客戶端(要把zookeeper用的足夠熟練,才能說掌握了它)

zookeeper是一個分散式的,開放原始碼的分散式應用程式協調服務,是Google的Chubby一個開源的實現,是Hadoop和Hbase的重要元件。它是一個為分散式應用提供一致性服務的軟體,由於產品升級拓展,pom等檔案配置越來越複雜,因此zk派上用場

一.zookeeper簡介

ZooKeeper基本原理

1. 資料模型
zookeeper-tree
如上圖所示,ZooKeeper資料模型的結構與Unix檔案系統很類似,整體上可以看作是一棵樹,每個節點稱做一個ZNode。每個ZNode都可以通過其路徑唯一標識,比如上圖中第三層的第一個ZNode, 它的路徑是/app1/c1。在每個ZNode上可儲存少量資料(預設是1M, 可以通過配置修改, 通常不建議在ZNode上儲存大量的資料),這個特性非常有用,在後面的典型應用場景中會介紹到。另外,每個ZNode上還儲存了其Acl資訊,這裡需要注意,雖說ZNode的樹形結構跟Unix檔案系統很類似,但是其Acl與Unix檔案系統是完全不同的,每個ZNode的Acl的獨立的,子結點不會繼承父結點的,關於ZooKeeper中的Acl可以參考之前寫過的一篇文章《

說說Zookeeper中的ACL》。

2.重要概念 
2.1 ZNode
前文已介紹了ZNode, ZNode根據其本身的特性,可以分為下面兩類:

  • Regular ZNode: 常規型ZNode, 使用者需要顯式的建立、刪除
  • Ephemeral ZNode: 臨時型ZNode, 使用者建立它之後,可以顯式的刪除,也可以在建立它的Session結束後,由ZooKeeper Server自動刪除

ZNode還有一個Sequential的特性,如果建立的時候指定的話,該ZNode的名字後面會自動Append一個不斷增加的SequenceNo。

2.2 Session
Client與ZooKeeper之間的通訊,需要建立一個Session,這個Session會有一個超時時間。因為ZooKeeper叢集會把Client的Session資訊持久化,所以在Session沒超時之前,Client與ZooKeeper Server的連線可以在各個ZooKeeper Server之間透明地移動。

在實際的應用中,如果Client與Server之間的通訊足夠頻繁,Session的維護就不需要其它額外的訊息了。否則,ZooKeeper Client會每t/3 ms發一次心跳給Server,如果Client 2t/3 ms沒收到來自Server的心跳回應,就會換到一個新的ZooKeeper Server上。這裡t是使用者配置的Session的超時時間。

2.3 Watcher
ZooKeeper支援一種Watch操作,Client可以在某個ZNode上設定一個Watcher,來Watch該ZNode上的變化。如果該ZNode上有相應的變化,就會觸發這個Watcher,把相應的事件通知給設定Watcher的Client。需要注意的是,ZooKeeper中的Watcher是一次性的,即觸發一次就會被取消,如果想繼續Watch的話,需要客戶端重新設定Watcher。這個跟epoll裡的oneshot模式有點類似。

二.zookeeper應用場景

    Zookeeper的Java客戶端

1. 名字服務(NameService) 
分散式應用中,通常需要一套完備的命令機制,既能產生唯一的標識,又方便人識別和記憶。 我們知道,每個ZNode都可以由其路徑唯一標識,路徑本身也比較簡潔直觀,另外ZNode上還可以儲存少量資料,這些都是實現統一的NameService的基礎。下面以在HDFS中實現NameService為例,來說明實現NameService的基本布驟:

  • 目標:通過簡單的名字來訪問指定的HDFS機群
  • 定義命名規則:這裡要做到簡潔易記憶。下面是一種可選的方案: [serviceScheme://][zkCluster]-[clusterName],比如hdfs://lgprc-example/表示基於lgprc ZooKeeper叢集的用來做example的HDFS叢集
  • 配置DNS對映: 將zkCluster的標識lgprc通過DNS解析到對應的ZooKeeper叢集的地址
  • 建立ZNode: 在對應的ZooKeeper上建立/NameService/hdfs/lgprc-example結點,將HDFS的配置檔案儲存於該結點下
  • 使用者程式要訪問hdfs://lgprc-example/的HDFS叢集,首先通過DNS找到lgprc的ZooKeeper機群的地址,然後在ZooKeeper的/NameService/hdfs/lgprc-example結點中讀取到HDFS的配置,進而根據得到的配置,得到HDFS的實際訪問入口

2. 配置管理(Configuration Management) 
在分散式系統中,常會遇到這樣的場景: 某個Job的很多個例項在執行,它們在執行時大多數配置項是相同的,如果想要統一改某個配置,一個個例項去改,是比較低效,也是比較容易出錯的方式。通過ZooKeeper可以很好的解決這樣的問題,下面的基本的步驟:

  • 將公共的配置內容放到ZooKeeper中某個ZNode上,比如/service/common-conf
  • 所有的例項在啟動時都會傳入ZooKeeper叢集的入口地址,並且在執行過程中Watch /service/common-conf這個ZNode
  • 如果叢集管理員修改了了common-conf,所有的例項都會被通知到,根據收到的通知更新自己的配置,並繼續Watch /service/common-conf

本文主要介紹基於java的客戶端開發

三.基於JAVA客戶端實戰

3.1Client

// 建立一個與伺服器的連線 需要(服務端的 ip+埠號)(session過期時間)(Watcher監聽註冊) 
		 ZooKeeper zk = new ZooKeeper("10.154.156.180:2181", 
		        3000, new Watcher() { 
		            // 監控所有被觸發的事件
					public void process(WatchedEvent event) {
						// TODO Auto-generated method stub
						System.out.println("已經觸發了" + event.getType() + "事件!"); 
					} 
		  });

		 // 建立一個目錄節點
		 /**
		  * CreateMode:
		  * 	PERSISTENT (持續的,相對於EPHEMERAL,不會隨著client的斷開而消失)
		  *		PERSISTENT_SEQUENTIAL(持久的且帶順序的)
		  *		EPHEMERAL (短暫的,生命週期依賴於client session)
		  *		EPHEMERAL_SEQUENTIAL  (短暫的,帶順序的)
		  */
		 zk.create("/testRootPath", "testRootData".getBytes(), Ids.OPEN_ACL_UNSAFE,CreateMode.PERSISTENT); 
		 
		 // 建立一個子目錄節點
		 zk.create("/testRootPath/testChildPathOne", "testChildDataOne".getBytes(),Ids.OPEN_ACL_UNSAFE,CreateMode.PERSISTENT); 
		 System.out.println(new String(zk.getData("/testRootPath",false,null))); 
		 
		 // 取出子目錄節點列表
		 System.out.println(zk.getChildren("/testRootPath",true)); 	
		 
		 // 建立另外一個子目錄節點
		 zk.create("/testRootPath/testChildPathTwo", "testChildDataTwo".getBytes(), Ids.OPEN_ACL_UNSAFE,CreateMode.PERSISTENT); 		 
		 System.out.println(zk.getChildren("/testRootPath",true)); 	
		 
		// 修改子目錄節點資料
		 zk.setData("/testRootPath/testChildPathOne","hahahahaha".getBytes(),-1); 	 		 
		 byte[] datas = zk.getData("/testRootPath/testChildPathOne", true, null);
		 String str = new String(datas,"utf-8");
		 System.out.println(str); 	
		 
		 //刪除整個子目錄   -1代表version版本號,-1是刪除所有版本
		 zk.delete("/testRootPath/testChildPathOne", -1);	 
		 System.out.println(zk.getChildren("/testRootPath",true)); 
		 System.out.println(str);