ZooKeeper分布式过程协同技术详解-第四章-处理状态变化

文章目录

  • 处理状态变化
    • 一、单次触发器
      • 1、单次触发是否会丢失事件
    • 二、如何设置监视点
    • 三、普遍模型
    • 四、主-从模式的例子
      • 1、管理权变化
      • 2、主节点等待从节点列表的变化

处理状态变化

通过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)。
ZooKeeper分布式过程协同技术详解-第四章-处理状态变化_第1张图片

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 
		}
    } 
}

你可能感兴趣的:(ZooKeeper)