ZooKeeper分佈式過程協同技術詳解-第四章-處理狀態變化

處理狀態變化

通過ZooKeeper通知客戶端感興趣的時間來避免輪詢。
ZooKeeper提供了處理變化的重要機制-監視點(watch)。通過監視點,客戶端可以對指定的znode節點註冊一個通知請求,在發生變化時就會收到一個單次的通知。
視點和通知形成了一個通用機制,使客戶端可以觀察變化情況,而不用不斷地輪詢ZooKeeper。

一、單次觸發器

  • 1、事件:事件 (event)表示一個znode節點執行了更新操作。
  • 2、監視點(Wacth):一個監視點(watch)表 示一個與之關聯的znode節點和事件類型組成的單次觸發器
  • 3、通知:當一個監視點被一個事件觸發 時,就會產生一個通知(notification)

當應用程序註冊了一個監視點來接收通知,匹配該監視點條件的第一個事件會觸發監視點的通知,並且最多隻觸發一次。

客戶端設置的每個監視點與會話關聯,如果會話過期,等待中的監視點將會被刪除。不過監視點可以跨越不同服務端的連接而保持,例如,當一個ZooKeeper客戶端與一個ZooKeeper服務端的連接斷開後連接到集合中的另一個服務端,客戶端會發送未觸發的監視點列表,在註冊監視點時, 服務端將要檢查已監視的znode節點在之前註冊監視點之後是否已經變化, 如果znode節點已經發生變化,一個監視點的事件就會被髮送給客戶端,否 則在新的服務端上註冊監視點。

1、單次觸發是否會丟失事件

答案是肯定的。一個應用在接收到通知後,註冊另一個監視點時,可能會丟失事件。因爲可能在這之間znode節點又發生了變化。

實際上,將多個事件分攤到一個通知上具有積極的作用,比如,應用進行高頻率的更新操作時,這種通知機制比每個事件都發送通知更加輕量化。舉個例子,如果每個通知平均捕獲兩個事件,我們爲每個事件只產生 了0.5個通知,而不是每個事件1個通知。

二、如何設置監視點

ZooKeeper的API中的所有讀操作:getData、getChildren和exists,均可以選擇在讀取的znode節點上設置監視點。使用監視點機制,我們需要實現 Watcher接口類,實現其中的process方法:

public void process(WatchedEvent event);

WatchEvent數據結構包含以下信息:

  • ZooKeeper會話狀態(KeeperState):Disconnected、 SyncConnected、AuthFailed、ConnectedReadOnly、SaslAuthenticated和 Expired。
  • 事件類型(EventType):NodeCreated、NodeDeleted、 NodeDataChanged、NodeChildrenChanged和None。
  • 如果事件類型不是None時,返回一個znode路徑。

其中前三個事件類型只涉及單個znode節點,第四個事件類型涉及監視 的znode節點的子節點。我們使用None表示無事件發生,而是ZooKeeper的 會話狀態發生了變化。

監視點有兩種類型:數據監視點和子節點監視點。創建、刪除或設置一個znode節點的數據都會觸發數據監視點,exists和getData這兩個操作可以設置數據監視點。只有getChildren操作可以設置子節點監視點,這種監 視點只有在znode子節點創建或刪除時才被觸發。對於每種事件類型,我們 通過以下調用設置監視點:

  • NodeCreated
    通過exists調用設置一個監視點。
  • NodeDeleted
    通過exists或getData調用設置監視點。
  • NodeDataChanged
    通過exists或getData調用設置監視點。
  • NodeChildrenChanged
    通過getChildren調用設置監視點。

當創建一個ZooKeeper對象(見第3章),我們需要傳遞一個默認的 Watcher對象,ZooKeeper客戶端使用這個監視點來通知應用ZooKeeper狀態的變化情況,如會話狀態的變化。

public byte[] getData(final String path, Watcher watcher, Stat stat);
public byte[] getData(String path, boolean watch, Stat stat);

兩個方法第一個參數均爲znode節點,第一個方法傳遞一個新的 Watcher對象(我們已經創建完畢),第二個方法則告訴客戶端使用默認的 監視點,我們只需要在調用時將第二個參數傳遞true。

stat入參爲Stat類型的實例化對象,ZooKeeper使用該對象返回指定的 path參數的znode節點信息。Stat結構包括znode節點的屬性信息,如該znode 節點的上次更新(zxid)的時間戳,以及該znode節點的子節點數。

對於監視點的一個重要問題是,一旦設置監視點就無法移除。要想移 除一個監視點,只有兩個方法,一是觸發這個監視點,二是使其會話被關 閉或過期。

三、普遍模型

我們進入主-從模式例子的章節之前,先看一些ZooKeeper的應用中使用的通用代碼的模型:

  • 1、進行調用異步
  • 2、實現回調對象,並傳入異步調用函數中。
  • 3、如果操作需要設置監視點,實現一個Wacther對象,並傳入異步調用函數中。
zk.exists("/myZnode", //1 
	myWatcher,existsCallback,null);
Watcher myWatcher = new Watcher() 
{//2 
	public void process(WatchedEvent e) 
	{
	    // Process the watch event
	} 
}
StatCallback existsCallback = new StatCallback() 
{ //3
	public void processResult(int rc, String path, Object ctx, Stat stat) 
	{
	 	// Process the result of the exists call
	 }
};

四、主-從模式的例子

現在,我們通過主-從模式的例子來看看如何處理狀態的變化。以下爲一個任務列表,一個組件需要等待處理的變化情況:

  • 1、管理權變化。
  • 2、主節點等待從節點列表的變化。
  • 3、主節點等待新任務進行分配。
  • 4、從節點等待分配新任務。
  • 5、客戶端等待任務的執行結果。

1、管理權變化

應用客戶端通過創建/master節點來推選自己爲主節點(我們稱爲“主節點競選”),如果znode節點已經存在,應用客戶端確認自己不是主要主節點並返回。然而,這種實現方式無法容忍主要主節點的崩潰。如果主要主節點崩潰,備份主節點並不知道,因此我們需要在/master上設置監視點,在節點刪除時(無論是顯式關閉還是因爲主要主節點的會話過期),ZooKeeper會通知客戶端。

StringCallback masterCreateCallback = new StringCallback() 
{
	public void processResult(int rc, String path, Object ctx, String name) 
	{
		switch (Code.get(rc)) 
		{ 
			case CONNECTIONLOSS:
				checkMaster(); //1
               	break;
           	case OK:
				state = MasterStates.ELECTED; 				
				takeLeadership(); //2
				break;
			case NODEEXISTS:
				state = MasterStates.NOTELECTED; 		
				masterExists(); //3
				break;
			default:
				state = MasterStates.NOTELECTED;
				LOG.error("Something went wrong when running for master.", //4
					KeeperException.create(Code.get(rc), path));
		} 
	}
};

void masterExists() 
{
	zk.exists("/master",//5 
		masterExistsWatcher,masterExistsCallback,null);
}


Watcher masterExistsWatcher = new Watcher() 
{
	public void process(WatchedEvent e) 
	{ 
		if(e.getType() == EventType.NodeDeleted) 
		{
			assert "/master".equals( e.getPath() );
			runForMaster(); //6 
		}
	} 
};
  • 1、在連接丟失事件發生的情況下,客戶端檢查/master節點是否存在, 因爲客戶端並不知道是否能夠創建這個節點。
  • 2、如果返回OK,那麼開始行使領導權。
  • 3、如果其他進程已經創建了這個znode節點,客戶端需要監視該節點。
  • 4、如果發生了某些意外情況,就會記錄錯誤日誌,而不再做其他事 情。
  • 5、通過exists調用在/master節點上設置了監視點。
  • 6、如果/master節點刪除了,那麼再次競選主節點。
StatCallback masterExistsCallback = new StatCallback() {
	public void processResult(int rc, String path, Object ctx, Stat stat) 
	{ 
		switch (Code.get(rc)) 
		{
			case CONNECTIONLOSS:
				masterExists(); //1
    			break;
			case OK:
				if(stat == null) 
				{
					state = MasterStates.RUNNING; 
					runForMaster(); //2
				}
   	 			break;
			default:
				checkMaster(); //3
				break; 
		}
	} 
};

對/master節點執行exists操作,返回結果也許是該znode節點已經被刪除,這時因爲無法保證監視點的設置是在znode節點刪除前,所以客戶端需要再次競選/master節點。如果再次嘗試成爲主節點失敗,那麼客戶端就知道有其他客戶端成功了,之後該客戶端就需要再次爲/master節點添加監視點。如果收到的是/master節點創建的通知,而不是刪除通知,客戶端就不 再競選/master節點,同時,對應的exists操作(設置監視點的操作)會返 回/master節點不存在,然後觸發exists回調方法,進行/master節點競選操 作。

圖4-1直觀地展示了這些交錯的操作。如果競選主節點成功(圖中 a),create操作執行完成,應用客戶端不需要做其他事情。如果create操作 失敗,則意味着該節點已經存在,客戶端就會執行exists操作來設置/master 節點的監視點(圖中b)。在競選主節點和執行exists操作之間,也 許/master節點已經刪除了,這時,如果exists調用返回該節點依然存在,客 戶端只需要等待通知的到來,否則就需要再次嘗試創建/master進行競選主 節點操作。如果創建/master節點成功,監視點就會被觸發,表示znode節點 發生了變化(圖中c),不過,這個通知沒有什麼意義,因爲這是客戶端自 己引起的變化。如果再次執行create操作失敗,我們就會通過執行exists設 置監視點來重新執行這一流程(圖中d)。
在這裏插入圖片描述

2、主節點等待從節點列表的變化

系統中任何時候都可能發生新的從節點加入進來,或舊的從節點退役 的情況,從節點執行分配給它的任務前也許會崩潰。爲了確認某個時間點 可用的從節點信息,我們通過在ZooKeeper中的/workers下添加子節點來注 冊新的從節點。當一個從節點崩潰或從系統中被移除,如會話過期等情 況,需要自動將對應的znode節點刪除。優雅實現的從節點會顯式地關閉其 會話,而不需要ZooKeeper等待會話過期。

Watcher workersChangeWatcher = new Watcher() 
{ //1 
	public void process(WatchedEvent e) 
	{
		if(e.getType() == EventType.NodeChildrenChanged) 
		{ 
			assert "/workers".equals( e.getPath() ); getWorkers();
		} 
	}
};
void getWorkers() 
{
	zk.getChildren("/workers", workersChangeWatcher,
                       workersGetChildrenCallback,
                       null);
}
ChildrenCallback workersGetChildrenCallback = new ChildrenCallback() 
{
	public void processResult(int rc, String path, Object ctx,List<String> children) 
	{
		switch (Code.get(rc)) 
		{ 
			case CONNECTIONLOSS:
				getWorkerList(); //2
              	break;
           	case OK:
				LOG.info("Succesfully got a list of workers: " + children.size()+ " workers"); 
				reassignAndSet(children); //3
               	break;
           	default:
				LOG.error("getChildren failed",KeeperException.create(Code.get(rc), path));
		} 
	}
};
  • workersChangeWatcher爲從節點列表的監視點對象。
  • 當CONNECTIONLOSS事件發生時,我們需要重新獲取子節點並設置監視點的操作。
  • 重新分配崩潰從節點的任務,並重新設置新的從節點列表。

我們從getWorkerList方法開始執行,通過異步方式執行getChildren方法,傳入workersGetChildrenCallback參數用於處理操作結果。如果客戶端 失去與服務端的連接(CONNECTIONLOSS事件),監視點不會被添加, 我們也不會得到從節點的列表,我們再次執行getWorkerList來設置監視點 並獲取從節點列表,如果執行getChildren成功,我們就會調用 reassignAndSet方法,該方法的代碼如下:

ChildrenCache workersCache; //1
void reassignAndSet(List<String> children) 
{
	List<String> toProcess;
	if(workersCache == null) 
	{
		workersCache = new ChildrenCache(children); //2
		toProcess = null; //3 
	} 
	else 
	{
		LOG.info( "Removing and setting" );
		toProcess = workersCache.removedAndSet( children ); //4 
	}
    if(toProcess != null) 
    {
    	for(String worker : toProcess) 
    	{
			getAbsentWorkerTasks(worker); //5 
		}
    } 
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章