Watcher在ZooKeeper 是一個核心功能,Watcher 可以監控目錄節點的數據變化以及子目錄的變化,一旦這些狀態發生變化,服務器就會通知所有設置在這個目錄節點上的Watcher,從而每個客戶端都很快知道它所關注的目錄節點的狀態發生變化,而做出相應的反應.
可以設置觀察的操作:exists,getChildren,getData
可以觸發觀察的操作:create,delete,setData
znode以某種方式發生變化時,“觀察”(watch)機制可以讓客戶端得到通知.可以針對ZooKeeper服務的“操作”來設置觀察,該服務的其他操作可以觸發觀察.
比如,客戶端可以對某個客戶端調用exists操作,同時在它上面設置一個觀察,如果此時這個znode不存在,則exists返回false,如果一段時間之後,這個znode被其他客戶端創建,則這個觀察會被觸發,之前的那個客戶端就會得到通知.
說明: zookeeper客戶端對server的操作都是不可回退的。
意思是說,zk的客戶端每次和server進行通信的時候,會記住server上最新的zxid。如果某個時刻,客戶端和server斷開了連接,那麼等到下次重新連接到集羣中的機器上時,會檢查當前連接上的那個server是否和client有相同的zxid,或者已經是更新的zxid了。一旦客戶端發現server的zxid比自己小,那麼客戶端會斷開和這個server的連接,並且重新連接集羣中的其它server.
1、鏈接Zookeeper服務器
/**
* <p>連接Zookeeper</p>
* <pre>
* [關於connectString服務器地址配置]
* 格式: 192.168.1.1:2181,192.168.1.2:2181,192.168.1.3:2181
* 這個地址配置有多個ip:port之間逗號分隔,底層操作
* ConnectStringParser connectStringParser = new ConnectStringParser(“192.168.1.1:2181,192.168.1.2:2181,192.168.1.3:2181”);
* 這個類主要就是解析傳入地址列表字符串,將其它保存在一個ArrayList中
* ArrayList<InetSocketAddress> serverAddresses = new ArrayList<InetSocketAddress>();
* 接下去,這個地址列表會被進一步封裝成StaticHostProvider對象,並且在運行過程中,一直是這個對象來維護整個地址列表。
* ZK客戶端將所有Server保存在一個List中,然後隨機打亂(這個隨機過程是一次性的),並且形成一個環,具體使用的時候,從0號位開始一個一個使用。
* 因此,Server地址能夠重複配置,這樣能夠彌補客戶端無法設置Server權重的缺陷,但是也會加大風險。
*
* [客戶端和服務端會話說明]
* ZooKeeper中,客戶端和服務端建立連接後,會話隨之建立,生成一個全局唯一的會話ID(Session ID)。
* 服務器和客戶端之間維持的是一個長連接,在SESSION_TIMEOUT時間內,服務器會確定客戶端是否正常連接(客戶端會定時向服務器發送heart_beat,服務器重置下次SESSION_TIMEOUT時間)。
* 因此,在正常情況下,Session一直有效,並且ZK集羣所有機器上都保存這個Session信息。
* 在出現網絡或其它問題情況下(例如客戶端所連接的那臺ZK機器掛了,或是其它原因的網絡閃斷),客戶端與當前連接的那臺服務器之間連接斷了,
* 這個時候客戶端會主動在地址列表(實例化ZK對象的時候傳入構造方法的那個參數connectString)中選擇新的地址進行連接。
*
* [會話時間]
* 客戶端並不是可以隨意設置這個會話超時時間,在ZK服務器端對會話超時時間是有限制的,主要是minSessionTimeout和maxSessionTimeout這兩個參數設置的。
* 如果客戶端設置的超時時間不在這個範圍,那麼會被強制設置爲最大或最小時間。 默認的Session超時時間是在2 * tickTime ~ 20 * tickTime
* </pre>
* @param connectString Zookeeper服務地址
* @param sessionTimeout Zookeeper連接超時時間
*/
public void connectionZookeeper(String connectString, int sessionTimeout){
this.releaseConnection();
try {
// ZK客戶端允許我們將ZK服務器的所有地址都配置在這裏
zk = new ZooKeeper(connectString, sessionTimeout, this );
// 使用CountDownLatch.await()的線程(當前線程)阻塞直到所有其它擁有CountDownLatch的線程執行完畢(countDown()結果爲0)
connectedSemaphore.await();
} catch ( InterruptedException e ) {
LOG.error("連接創建失敗,發生 InterruptedException , e " + e.getMessage(), e);
} catch ( IOException e ) {
LOG.error( "連接創建失敗,發生 IOException , e " + e.getMessage(), e );
}
}
2、創建節點
/**
* <p>創建zNode節點, String create(path<節點路徑>, data[]<節點內容>, List(ACL訪問控制列表), CreateMode<zNode創建類型>) </p><br/>
* <pre>
* 節點創建類型(CreateMode)
* 1、PERSISTENT:持久化節點
* 2、PERSISTENT_SEQUENTIAL:順序自動編號持久化節點,這種節點會根據當前已存在的節點數自動加 1
* 3、EPHEMERAL:臨時節點客戶端,session超時這類節點就會被自動刪除
* 4、EPHEMERAL_SEQUENTIAL:臨時自動編號節點
* </pre>
* @param path zNode節點路徑
* @param data zNode數據內容
* @return 創建成功返回true, 反之返回false.
*/
public boolean createPath( String path, String data ) {
try {
String zkPath = this.zk.create(path, data.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
LOG.info( "節點創建成功, Path: " + zkPath + ", content: " + data );
return true;
} catch ( KeeperException e ) {
LOG.error( "節點創建失敗, 發生KeeperException! path: " + path + ", data:" + data
+ ", errMsg:" + e.getMessage(), e );
} catch ( InterruptedException e ) {
LOG.error( "節點創建失敗, 發生 InterruptedException! path: " + path + ", data:" + data
+ ", errMsg:" + e.getMessage(), e );
}
return false;
}
3、刪除節點
/**
* <p>刪除一個zMode節點, void delete(path<節點路徑>, stat<數據版本號>)</p><br/>
* <pre>
* 說明
* 1、版本號不一致,無法進行數據刪除操作.
* 2、如果版本號與znode的版本號不一致,將無法刪除,是一種樂觀加鎖機制;如果將版本號設置爲-1,不會去檢測版本,直接刪除.
* </pre>
* @param path zNode節點路徑
* @return 刪除成功返回true,反之返回false.
*/
public boolean deletePath( String path ){
try {
this.zk.delete(path,-1);
LOG.info( "節點刪除成功, Path: " + path);
return true;
} catch ( KeeperException e ) {
LOG.error( "節點刪除失敗, 發生KeeperException! path: " + path
+ ", errMsg:" + e.getMessage(), e );
} catch ( InterruptedException e ) {
LOG.error( "節點刪除失敗, 發生 InterruptedException! path: " + path
+ ", errMsg:" + e.getMessage(), e );
}
return false;
}
4、節點賦值/更新節點
/**
* <p>更新指定節點數據內容, Stat setData(path<節點路徑>, data[]<節點內容>, stat<數據版本號>)</p>
* <pre>
* 設置某個znode上的數據時如果爲-1,跳過版本檢查
* </pre>
* @param path zNode節點路徑
* @param data zNode數據內容
* @return 更新成功返回true,返回返回false
*/
public boolean writeData( String path, String data){
try {
Stat stat = this.zk.setData(path, data.getBytes(), -1);
LOG.info( "更新數據成功, path:" + path + ", stat: " + stat );
return true;
} catch (KeeperException e) {
LOG.error( "更新數據失敗, 發生KeeperException! path: " + path + ", data:" + data
+ ", errMsg:" + e.getMessage(), e );
} catch (InterruptedException e) {
LOG.error( "更新數據失敗, 發生InterruptedException! path: " + path + ", data:" + data
+ ", errMsg:" + e.getMessage(), e );
}
return false;
}
5、讀取節點值
/**
* <p>讀取指定節點數據內容,byte[] getData(path<節點路徑>, watcher<監視器>, stat<數據版本號>)</p>
* @param path zNode節點路徑
* @return 節點存儲的值,有值返回,無值返回null
*/
public String readData( String path ){
String data = null;
try {
data = new String( this.zk.getData( path, false, null ) );
LOG.info( "讀取數據成功, path:" + path + ", content:" + data);
} catch (KeeperException e) {
LOG.error( "讀取數據失敗,發生KeeperException! path: " + path
+ ", errMsg:" + e.getMessage(), e );
} catch (InterruptedException e) {
LOG.error( "讀取數據失敗,發生InterruptedException! path: " + path
+ ", errMsg:" + e.getMessage(), e );
}
return data;
}
6、判斷節點是否存在
/**
* <p>判斷某個zNode節點是否存在, Stat exists(path<節點路徑>, watch<並設置是否監控這個目錄節點,這裏的 watcher 是在創建 ZooKeeper 實例時指定的 watcher>)</p>
* @param path zNode節點路徑
* @return 存在返回true,反之返回false
*/
public boolean isExists( String path ){
try {
Stat stat = this.zk.exists( path, false );
return null != stat;
} catch (KeeperException e) {
LOG.error( "讀取數據失敗,發生KeeperException! path: " + path
+ ", errMsg:" + e.getMessage(), e );
} catch (InterruptedException e) {
LOG.error( "讀取數據失敗,發生InterruptedException! path: " + path
+ ", errMsg:" + e.getMessage(), e );
}
return false;
}
7、獲取某個節點下的子節點
/**
* <p>獲取某個節點下的所有子節點,List getChildren(path<節點路徑>, watcher<監視器>)該方法有多個重載</p>
* @param path zNode節點路徑
* @return 子節點路徑集合 說明,這裏返回的值爲節點名
* <pre>
* eg.
* /node
* /node/child1
* /node/child2
* getChild( "node" )戶的集合中的值爲["child1","child2"]
* </pre>
*
*
*
* @throws KeeperException
* @throws InterruptedException
*/
public List<String> getChild( String path ){
try{
List<String> list=this.zk.getChildren( path, false );
if(list.isEmpty()){
LOG.info( "中沒有節點" + path );
}
return list;
}catch (KeeperException e) {
LOG.error( "讀取子節點數據失敗,發生KeeperException! path: " + path
+ ", errMsg:" + e.getMessage(), e );
} catch (InterruptedException e) {
LOG.error( "讀取子節點數據失敗,發生InterruptedException! path: " + path
+ ", errMsg:" + e.getMessage(), e );
}
return null;
}
8、釋放鏈接
/**
* 關閉ZK連接
*/
public void releaseConnection() {
if ( null != zk ) {
try {
this.zk.close();
} catch ( InterruptedException e ) {
LOG.error("release connection error ," + e.getMessage() ,e);
}
}
}
轉載請註明出處:[http://www.cnblogs.com/dennisit/p/4340688.html]