ZooKeeper实现成员列表管理

本文介绍ZooKeeper在分布式应用中的作用,包括其特性、安装配置、基本命令及组成员管理示例,展示了如何利用ZooKeeper解决部分失败难题。

概要

分布式应用的一个主要难点是“部分失败”。例如通过消息中间件,网络中的一个节点向另一个节点发送消息,发送节点很难知道消息是否成功送达,什么时候送达,接收节点是否正常运行,消息是否被正确处理,什么时候被处理等。ZooKeeper被用来在分布式应用中提供分布式协调服务,使用ZooKeeper本身并不能避免“部分失败”发生,因为“部分失败”是分布式应用的因有属性。相反,ZooKeeper提供一系列的工具,在分布式应用中安全处理“部分失败”。

ZooKeeper特性:

  • 简单:ZooKeeper核心是一个文件系统,支持一些简单操作及一些特别的抽象概念如“命令”、“通知”等。
  • 富于表现力:ZooKeeper提供了丰富的工具用来构建多种类型的协调数据结构及服务,如分布式队列、分布式锁、领导人选举等。
  • 高可用:ZooKeeper被设计成高可用集群,可以避免单点故障。
  • 解耦:应用的参考者不需要知道彼此的存在,甚至不需要在同一时间存在。
  • 开源库:对于通用协调模型,ZooKeeper提供了通用实现。
  • 高效

安装运行ZooKeeper

 单机版ZooKeeper在Linux上安装非常简单。首先需要Java 6及以上版本的运行时环境。下载ZooKeeper软件包并解压到指定目录。设置环境变量,将ZooKeeper中的可执行文件路径加入到PATH环境变量中。
 

% export ZOOKEEPER_INSTALL=/home/tom/zookeeper.x.y.z
% export PATH=$PATH:$ZOOKEEPER_INSTALL/bin

在安装目录的子目录conf下找到zoo.cfg文件,没有自行创建,这个是默认的配置文件。在其中加入如下三行:

tickTime=2000
dataDir=/Users/tom/zookeeper
clientPort=2181

以上是单机ZooKeeper需要的最小配置。简单点说,tickTime是ZooKeeper中的最小时间单位,其本向单位是毫秒。dataDir是ZooKeeper持久化数据的本地文件目录。clientPort是对外提供服务的端口号。执行如下命令启动ZooKeeper服务:

% zkServer.sh start

检查ZooKeeper是否正常运行:

% echo ruok | nc localhost 2181
imok

ZooKeeper常用命令由四个字符组成,如下表:

类型命令描述
服务状态ruok服务正确输出imok
 conf输出配置文件内容
 envi输出环境信息,如java版本、ZooKeeper版本等
 srvr输出统计信息,如延时统计、znode个数,节点运行模式等
 stat输出统计信息及客户端连接等
 srst重置统计信息
 isro显示服务模式:只读模式、读写模式等。
客户端连接dump显示全部会话、临时znode等,必需连接到主节点。
 cons显示客户端连接统计信息
 crst重置客户端连接统计信息
监视wchs显示服务监视的概略信息
 wchc列出所有对连接的监视,可能会影响性能
 wchp通过znode的路径列出所有监视,可能会影响性能
监控mntr系统性能监视,往住作为其它监视系统的数据源。

组成员例子

假设通过一组实例对外提供服务。我们希望客户端能够定位到一组服务中的一个实例并使用它。有两个主要问题。一个是成员列列管理问题,不能将成员列表管理在网络中的单个节点上,否则会出现单点故障。另一个是成员列表中成员的增减问题。服务实例本身可以向成员列表中自注册,但不能自己删除自己,比如在自己崩溃的情况下。我们需要一种主动的机制,在成员列表中的实例状态发生变化时,能够自动更新成员列表。ZooKeeper提供此类功能。

ZooKeeper中的znode概念。

ZooKeeper可以被看成是一个高可用的文件系统。它也有类似于文件系统中的目录节点、文件节点的概念。在ZooKeeper中其被称之为znode。znode即可像文件一样包含数据,也可以像目录一样包含其它znode。znode可以构成一个分层的名称空间,那么,最自然的管理成员列表的方法就是用组名称创建一个znode,其下的孩子znode则代表列表成员。本例中只需用到znode的名称,无需在其中存储数据。

创建组

通过ZooKeeper提供的Java API创建如下图一所示的znode。

               图一

代码如下:

public class CreateGroup implements Watcher {
    private static final int SESSION_TIMEOUT = 5000;
    
    private ZooKeeper zk;
    private CountDownLatch connectedSignal = new CountDownLatch(1);

    public void connect(String hosts) throws IOExeption, InterruptedException {
        zk = new ZooKeeper(hosts, SESSION_TIMEOUT, this);
        connectedSignal.await();
    }

    @Override
    public void process(WatchedEvent event) {
        if (event.getState() == KeeperState.SyncConnected) {
            connectedSignal.countDown();
        }
    }

    public void create(String groupName) throws KeeperException,
        InterruptedException {
        String path = "/" + groupName;
        String createdPath = zk.create(path, null/*data*/, Ids.OPEN_ACL_UNSAFE,
            CreateMode.PERSISTENT);
        System.out.println("Created " + createdPath);
    }

    public void close() throws InterruptedException {
        zk.close();
    }

    public static void main(String[] args) throws Exception {
        CreateGroup createGroup = new CreateGroup();
        createGroup.connect(args[0]);
        createGroup.create(args[1]);
        createGroup.close();
    }
}

代码解析:
在main函数中首先创建CreateGroup实例,然后调用connect成员函数。connect成员函数创建ZooKeeper实例,代表与ZooKeper服务器的连接。它需要三个参数,第一个参数是服务器地址,默认端口号2181。第二个参数是超时值,这里是5秒。每三个参数是监视器,这里就是新创建的CreateGroup实例。
在创建ZooKeeper实例时,会启动一个线程与服务器连接,并且调用立刻返回。因此需要手动添加一个等待器,等连接完成以后再返回。这里使用Java的CountDownLatch类实现。其实例connectedSignal的初始数值为1,在调用connect函数时,调用函灵长connectedSignal.await()等侍其值变成0。当新启动的线程连接到服务器并完成后,会回调监视器就是process函数,当process函数收到keeperState.SyncConnected时,表示连接已经建立,调用connectedSignal.countDown()减少其初始值1到0,此时connectedSignal.await()返回,并且connect函数也返回。

接下来调用createGroup.create()函数。zk.create函数创建znode时需要四个参数:路径、znode内容、访问控制、类型。类型主要有两类,一类是临时性的,当连接失效时,ZooKeeper服务自动删除此类znode,另一类是持久性的,当连接失效时并不自动删除,除非显示删除。

加入组

每个成员作为一个单独的应用,在运行时需要将自己加入到组成员列表中,并且当应用退出时,自动从组成员列表中删除,因此我们创建一个临时znode。代码如下,ConnectionWatcher表示如何创建ZooKeeper实例及如何连接服务,省略:

public class JoinGroup extends ConnectionWatcher {
    public void join(String groupName, String memberName) throws KeeperException,
        InterruptedException {
        String path = "/" + groupName + "/" + memberName;
        String createPath = zk.create(path, null/*data*/, Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
        System.out.println("Created " + createPath);
    }

    public static void main(String[] args) throws Exception {
        JoinGroup joinGroup = new JoinGroup();
        joinGroup.connected(args[0]);
        joinGroup.join(args[1], args[2]);
        Thread.sleep(Long.MAX_VALUE);
    }
}

列出组成员

代码如下,省略实现连接建立的逻辑ConnectionWatcher类:
 

public class ListGroup extends ConnectionWatcher {
    public void list(String groupName) throws KeeperException,
        InterruptedException {
        String path = "/" + groupName;

        try {
            List<String> children = zk.getChildren(path, false);
            if (children.isEmpty()) {
                System.out.printf("No members in group %s\n", groupName);
                System.exit(1);
            }
            for(String child: children) {
                System.out.println(child);
            }
        } catch (KeeperException.NoNodeException e) {
            System.out.print("Group %s does not exist\n", groupName);
            System.exit(1);
        }
    }

    public static void main(String[] args) throws Exception {
        ListGroup listGroup = new ListGroup();
        listGroup.connect(args[0]);
        listGroup.list(args[1]);
        listGroup.close();
    }
}

删除组

代码如下:
 

public class DeleteGroup extends ConnectionWatcher {
    public void delete(String groupName) throws KeeperException,
        InterruptedException {
        String path = "/" + groupName;

        try {
            List<String> children = zk.getChildren(path, false);
            for (String child: children) {
                zk.delete(path + "/" + child, -1);
            }
            zk.delete(path, -1);
        } catch (KeeperException.NoNodeException e) {
          System.out.printf("Group %s does not exist\n", groupName);
          System.exit(1);
        }
    }
    
    public static void main(String[] args) throws Exception {
        DeleteGroup deleteGroup = new DeleteGroup();
        deleteGroup.connect(args[0]);
        deleteGroup.delete(args[1]);
        deleteGroup.close();
    }
}

 

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值