Zookeeper3:状态管理简介

在应用程序中,很多时候需要知道ZooKeeper集合的状态。例如,备份主节点需要知道主要主节点已经崩溃,从节点需要知道任务分配给了自己,甚至ZooKeeper的客户端会定时轮询ZooKeeper集合,检查系统状态是否发生了变化。然而轮询方式并非高效的方式,尤其是在期望的变化发生频率很低时。

 

例如,在主要主节点崩溃时,备份主节点需要知道这一情况, 以便它们可以进行故障处理。为了减少主节点崩溃后的恢复时间,我们需要频繁轮询,如每50毫秒,那每秒就产生20次查询。如果结点很多,这些消息开销就很大,假如将监听时间拉长,就可能导致处理故障不及时。

为此,可以通过ZooKeeper通知客户端感兴趣的具体事件来避免轮询的调优和轮询流量。ZooKeeper提供了处理变化的重要机制——监视点(watch)。客户端可以对指定的znode节点注册一个通知请求,在发生变化时就会收到一个单次的通知。例如,我们的主节点创建了一个临时性的znode节点来标识主节点锁,而备份节点注册一个监视点来监视这个主节点锁是否存在,如果主节点崩溃,主节点锁自动被删除,并通知所有备份主节点。一旦备份主节点收到通知, 它们就可以开始进行主节点选举,例如通过尝试创建一个临时的znode节点来标识主节点锁。

监视点和通知形成了一个通用机制,使客户端可以观察变化情况, 而不用不断地轮询ZooKeeper,该机制还适用于很多情况。

先明确一个概念,我们所说的事件 (event)表示一个znode节点执行了更新操作,也就是对结点进行了增删改。而一个监视点(watch) 表示一个znode节点和与之关联的事件类型组成的,也称为单次触发器。当一个监视点被一个事件触发时,就会产生一个通知(notification)。通知也就是应用客户端收到的事件报告的消息。

客户端设置的每个监视点与会话关联,如果会话过期,等待中的监视点将会被删除。

单次触发是否会丢失事件呢?会的,不过丢失事件通常并不是问题,因为任何在接收通知与注册新监视点之间的变化情况,均可以通过重新读取ZooKeeper的状态信息来获得。假设一个从节点接收到一个新任务分配给它的通知。为了接收新任 务,从节点读取任务列表,如果在通知接收后,又给这个从节点分配了 更多的任务,在通过getChildren调用获取任务列表时会返回所有的任务。同时调用getChildren时也可以设置新的监视点,从而保证从节点不 会丢失任务。

1 如何设置监视点

ZooKeeper的API中的所有读操作:getData、getChildren和exists, 均可以选择在读取的znode节点上设置监视点。使用监视点机制,我们 需要实现Watcher接口类,实现其中的process方法,这个方法在我们前面的演示中都有,不过只是打印了一下:

public void process(WatchedEvent event);

WatchedEvent数据结构包括以下信息:

  • 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 状态的变化情况,如会话状态的变化。对于ZooKeeper节点的事件的通 知,你可以使用默认的监视点,也可以单独实现一个。例如,getData调 用有两种方式设置监视点:

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节点的子节点 数。

对于监视点的一个重要问题是,一旦设置监视点就无法移除。要想移除一个监视点,只有两个方法,一是触发这个监视点,二是使其会话 被关闭或过期。在未来版本中可能会改变这个特性,这是因为开发社区致力于在版本3.5.0中提供该功能。

ZooKeeper监控状态的通用模型是:

1.进行调用异步。

2.实现回调对象,并传入异步调用函数中。

3.如果操作需要设置监视点,实现一个Watcher对象,并传入异步调用函数中。

以下为exists的异步调用的示例代码:

zk.exists("/myZnode", // 异步方式调用exists
             myWatcher,
             existsCallback,
             null);
Watcher myWatcher = new Watcher() {//2 Watcher的实现
       public void process(WatchedEvent e) {
           // Process the watch event
} }
StatCallback existsCallback = new StatCallback() {//3 exists的回调对象
       public void processResult(int rc, String path, Object ctx, Stat stat) {
           // Process the result of the exists call
} };

2 主从模式的例子

现在,我们通过主-从模式的例子详细研究如何处理状态的变化。通常,一个组件需要等待处理的情况至少包含以下几个:管理权变化、主节点等待从节点列表的变化、主节点等待新任务进行分配、从节点等待分配新任务和客户端等待任务的执行结果。

该例子来自《ZooKeeper-分布式过程协同技术详解》一书,案例设计的是master充当主结点,worker模拟要执行的任务,client用于提交任务。通过整个例子,我们会感觉到虽然代码整体上是上一文的组合,但是基于原生zookeeper开发还是非常复杂,要自己实现是非常困难的。我们只以”管理权变化“为例,看一下核心的代码如何做的。

应用客户端通过创建/master节点来推选自 己为主节点(我们称为“主节点竞选”),如果znode节点已经存在,应用客户端确认自己不是主要主节点并返回,然而,如果主要主节点崩溃,备份主节点并不知道,因此我们需要在/master上设置监视点,在节点删除时(无论是显式关闭还是因为主要主节点的会话过期),ZooKeeper会通知客户端。

为了设置监视点,我们新建一个新的监视点对象,命名为 masterExistsWatcher,并传入exists方法中。一旦/master删除就会发出通 知,就会调用在masterExistsWatcher定义的process函数,并调用 runForMaster方法。

StringCallback masterCreateCallback = new StringCallback() {
    public void processResult(int rc, String path, Object ctx, String name) {
        switch (Code.get(rc)) { 
        case CONNECTIONLOSS:
        //在连接丢失事件发生的情况下,客户端检查/master节点是否存 在,因为客户端并不知道是否能够创建这个节点。
            checkMaster();           
            break;
        case OK:
            state = MasterStates.ELECTED;
            //如果返回OK,那么开始行使领导权
            takeLeadership();
​
            break;
        case NODEEXISTS:
            state = MasterStates.NOTELECTED;
            //如果其他进程已经创建了这个znode节点,客户端需要监视该节点。
            masterExists();
            
            break;
        default:
            state = MasterStates.NOTELECTED;
            LOG.error("Something went wrong when running for master.", 
                    KeeperException.create(Code.get(rc), path));
        }
        LOG.info("I'm " + (state == MasterStates.ELECTED ? "" : "not ") + "the leader " + serverId);
    }
};
​
void masterExists() {
    zk.exists("/master", //通过exists调用在/master节点上设置了监视点
            masterExistsWatcher, 
            masterExistsCallback, 
            null);
}
Watcher masterExistsWatcher = new Watcher(){
        public void process(WatchedEvent e) {
            if(e.getType() == EventType.NodeDeleted) {
                assert "/master".equals( e.getPath() );
                //如果/master节点删除了,那么再次竞选主节点。
                runForMaster();
            }
        }
};

下面继续采用我们在前面所讨论的异步方式,我们同样需要为 exists调用创建一个回调函数,以便在回调函数中关注某些情况。首先,在发生连接丢失的事件时,因为需要在/master节点上设置监视点, 所以需要再次调用exists操作;其次,在create的回调方法执行和exists操 作执行之间发生了/master节点被删除的情况,因此在exists返回操作成功后(返回OK),我们需要检查返回的stat对象是否为空,因为当节点不存在时,stat为null;最后,如果返回的结果不是OK或 CONNECTIONLOSS,我们通过获取节点数据来检查/master节点。加入客户端的会话过期,在这种情况下,获得/master数据的回调方法会记录 一个错误信息并退出。以下为我们的exists回调方法的代码:

StatCallback masterExistsCallback = new StatCallback() {
    public void processResult(int rc, String path, Object ctx, Stat stat){
        switch (Code.get(rc)) { 
        case CONNECTIONLOSS:
        //连接丢失的情况下重试。
            masterExists();
            break;
        case OK:
            state = MasterStates.RUNNING;
            runForMaster();
        case NONODE:
            state = MasterStates.RUNNING;
            runForMaster();
            LOG.info("It sounds like the previous master is gone, " +
                      "so let's run for master again."); 
            
            break;
        default:     
            checkMaster();
            break;
        }
    }
};

其他代码请参考原书,为了简化zk开发的难度,我们一般使用Curator来进行,Curator对zk进行了大量的二次封装,开发更为方便。

想看我的更多新文章,就关注我吧。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

纵横千里,捭阖四方

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值