标题:【PHP小课堂】在PHP中使用Zookeeper

文章目录
    分类: 标签:

    在PHP中使用Zookeeper

    不知道大家对于 Zookeeper 的了解有多少,我在实际的项目中没有使用过,但是之前学过一点。因此,今天我们只来看看 PHP 中关于 Zookeeper 的扩展相关函数的使用,不会涉及更加深入的 Zookeeper 相关概念和细节的研究。

    Zookeeper

    还是先来简单地介绍一下 Zookeeper 吧。从名字可以看出,这个系统是 动物园管理员 的意思。为什么呢?因为在大数据相关的系统中,很多系统的标志都是各种动物,就像我们的 PHP 的标志也是一个大象。而 Zookeeper 的意思就是来管理这一大群动物的。它可以做到的功能包括:配置维护、域名服务、分布式同步、组服务等,并且提供了分布式独享锁、选举、队列的接口。


    看着都是很高大上的功能吧。其实说白了,Zookeeper 就是一个分布式的应用程序协调服务。它提供了一个类似于 Linux 文件系统的目录树,这个目录树的每个结点都可以看成是一个 K/V 形式的 键值对 内容。可以生成永久的或者临时的、带序号或者不带序号的目录树。并且自带一个监听器,可以随时监听节点的变化方便从而实现对于系统的各种通知。


    具体在应用方面,我们常见的服务注册、集群上下线通知、配置文件变更、分布式锁等相关功能都可以用它来实现。


    具体的实现以及更详细的内容大家可以专门去找找 Zookeeper 相关的资料进行深入的学习。现在你可以去开三台虚拟机,然后搭建一个最简单的 Zookeeper 集群等着我们后面的使用了。具体的教程网上有很多,这里就不赘述了。

    扩展安装

    在 PHP 中使用 Zookeeper 扩展的安装会比较麻烦一点。我们还是先从 PECL 下载 Zookeeper 扩展。但是在 ./configuare 的时候会要求我们指定 libzookeeper 的路径。这个东西是需要编译源码安装在系统中的。


    首先在 https://downloads.apache.org/zookeeper/ 下载源码。我这里下载的是 3.5.7 版本。下载成功后解压,接下来就进行源码的安装,依次执行下面的命令。

    yum install ant
    # zookeeper 根目录下使用 ant 编译一下,否则下面无法 ./configure
    ant compile_jute 
    
    cd apache-zookeeper-3.5.7/zookeeper-client/zookeeper-client-c
    ./configure --prefix=/usr/local/zookeeper
    make && make install

    通过上述命令,Zookeeper 就被编译安装在了 /user/local/zookeeper 目录下。接下来进行 PHP 扩展安装,这里我下载的是 PECL 上的 1.0.0 版本。

    cd zookeeper-1.0.0
    ./configure --prefix=/usr/local/zookeeper
    make && make install

    最后就是在 php.ini 文件中打开扩展。

    [zookeeper]
    extension=zookeeper

    最后我们用 php -m 验证一下扩展安装成功,看到 zookeeper 出现了就可以了。

    普通操作

    普通的操作无非就是添加、修改和删除节点,以及建立与 Zookeeper 的集群连接。

    建立连接

    建立连接非常简单,只需要实例化 Zookeeper 对象,然后将服务器连接信息传递给构造参数即可。

    $host = "192.168.10.102:2181,192.168.10.103:2181,192.168.10.104:2181";
    
    $zookeeper = new Zookeeper($host);

    添加节点

    添加节点的操作也比较简单,直接使用一个 create() 函数即可。

    $acl = array(
      array(
        'perms'  => Zookeeper::PERM_ALL,
        'scheme' => 'world',
        'id'     => 'anyone',
      )
    );
    if(!$zookeeper->exists("/test")){
        $path = $zookeeper->create("/test", "ttt", $acl);
        echo $path; // /test
    }

    在这段测试代码中,我们还使用了一个 exists() 函数用于判断节点是否存在。如果节点不存在的话,我们就创建一个节点。create() 函数有四个参数,第一个是路径,第二个是路径的值,第三个是这个节点的权限。在这里我们给予的是全部权限 Zookeeper::PERM_ALL 。


    如果要创建带序号的临时节点,我们可以使用 create() 的第四个参数。

    echo $zookeeper->create("/test/app_", "appinfo" . date("Y-m-d H:i:s"), $acl, Zookeeper::EPHEMERAL|Zookeeper::SEQUENCE);
    // /test/app_0000000077/test/show2

    注意,Zookeeper 是可以生成 永久不带序号、永久带序号、临时不带序号、临时带序号 四种类型的节点的。直接根据第四个参数进行组合即可。当前这个测试代码的效果实际上就是 Zookeeper 命令行 create -s -e "/test/app_" "appinfo" 的效果。


    接下来我们再创建两个节点用于后面的测试。

    if(!$zookeeper->exists("/test/show1")){
        $path = $zookeeper->create("/test/show1", "show1", $acl);
        echo $path, PHP_EOL; // /test/show1
    }
    
    if(!$zookeeper->exists("/test/show2")){
        $path = $zookeeper->create("/test/show2", "show1", $acl);
        echo $path, PHP_EOL; // /test/show2
    }

    获取信息

    获取节点信息的函数也非常简单,就是一个 get() 函数。

    echo $zookeeper->get("/test/show1"), PHP_EOL;
    // show1

    除了获取节点的值内容外,我们还可以获取节点的权限信息内容。

    $stat = [];
    $zookeeper->get("/test/show1", null, $stat);
    print_r($stat);
    // Array
    // (
    //     [czxid] => 21474836499
    //     [mzxid] => 21474836731
    //     [ctime] => 1638752001331
    //     [mtime] => 1638760238766
    //     [version] => 28
    //     [cversion] => 0
    //     [aversion] => 0
    //     [ephemeralOwner] => 0
    //     [dataLength] => 6
    //     [numChildren] => 0
    //     [pzxid] => 21474836499
    // )
    
    print_r($zookeeper->getAcl("/test/show1"));
    // Array
    // (
    //     [0] => Array
    //         (
    //             [czxid] => 21474836499
    //             [mzxid] => 21474836731
    //             [ctime] => 1638752001331
    //             [mtime] => 1638760238766
    //             [version] => 28
    //             [cversion] => 0
    //             [aversion] => 0
    //             [ephemeralOwner] => 0
    //             [dataLength] => 6
    //             [numChildren] => 0
    //             [pzxid] => 21474836499
    //         )
    
    //     [1] => Array
    //         (
    //             [0] => Array
    //                 (
    //                     [perms] => 31
    //                     [scheme] => world
    //                     [id] => anyone
    //                 )
    
    //         )
    
    // )

    注意,get() 的第二个参数是定义监听器的,这个我们后面再讲,可以赋值为 null 就暂时不会监听。第三个参数是一个引用类型的参数,可以返回节点的状态内容信息。


    getAcl() 是获取权限信息的函数,可以看到,它获取到的内容是两个数组,第一个数组里面是节点的状态信息,第二个数组里面是我们定义的节点权限信息。


    最后我们再看一下获取子节点信息的函数 getChildren() 。

    print_r($zookeeper->getChildren("/test"));
    // Array
    // (
    //     [0] => show2
    //     [1] => show1
    //     [2] => app_0000000077
    // )

    返回的内容就是我们指定节点的子结点名称数组。

    修改删除

    修改节点使用的是 set() 函数,用于修改节点的值内容。

    var_dump($zookeeper->set("/test/show1", "show11")); // bool(true)
    echo $zookeeper->get("/test/show1"), PHP_EOL; // show11

    另外我们也可以修改节点的权限信息,比如下面这样。

    var_dump($zookeeper->setAcl("/test/show2", -1, [['perms'=>Zookeeper::PERM_READ, 'id'=>'anyone', 'scheme'=>'world']]));
    
    print_r($zookeeper->getAcl("/test/show2"));
    //……
    //             [0] => Array
    //                 (
    //                     [perms] => 1
    //                     [scheme] => world
    //                     [id] => anyone
    //                 )
    // ……
    
    $zookeeper->set("/test/show2", "show22");
    // PHP Fatal error:  Uncaught ZookeeperAuthenticationException: not authenticated in /data/www/blog/zookeeper/1.php:50

    使用 setAcl() 将这个节点的权限修改为只读之后,再使用 set() 去修改它的值,就会报出下面注释中的异常。


    删除节点的操作是使用 delete() 函数。

    var_dump($zookeeper->delete("/test/show2")); // bool(true)
    
    print_r($zookeeper->getChildren("/test"));
    // Array
    // (
    //     [0] => show1
    //     [1] => app_0000000077
    // )

    这里需要注意的是,节点的只读权限并不影响删除。

    监听

    PHP 的 Zookeeper 扩展默认提供了一个 setWatcher() 函数,不过我测不出来它的效果。

    $zookeeper->setWatcher(function(){
        print_r(func_get_args());
    });

    上面的代码在运行的时候并没有执行,所以如果有使用过的小伙伴可以留言一起学习下哈,我们来看另外一种方式,也就是我们前面说过的 get() 函数可以添加监听器。

    function watcher1($type, $state, $path){
        global $zookeeper;
        echo $type, ",", $state, ",", $path;
        switch ($type){
            case Zookeeper::CREATED_EVENT:
                echo "新建了目录:" . $path, PHP_EOL;
                break;
            case Zookeeper::DELETED_EVENT:
                echo "删除了目录:" . $path, PHP_EOL;
                break;
            case Zookeeper::CHANGED_EVENT:
                echo "修改了目录:" . $path, PHP_EOL;
                break;
            case Zookeeper::CHILD_EVENT:
                echo "修改了子目录:" . $path, PHP_EOL;
                break;
            default:
                echo "其它操作:" . $path, PHP_EOL;
                break;
            }
            $zookeeper->get("/test",'watcher1');
    }
    
    $zookeeper->get("/test",'watcher1');
    
    while(1){sleep(2);};

    在 Zookeeper 中,监听器只能注册一次监听一次,所以我们在监听器函数内部再调用了一次监听器用于下次的监听。监听回调函数包含三个参数,分别是 操作类型、状态值、路径 。我们的测试代码用于根据监听器的操作类型来返回对应的信息。别忘了,在文件底部还要加一个循环或者任何别的方式让代码保持挂载运行。


    现在,你可以启动这个测试文件,然后进入 Zookeeper 命令行进行测试。比如我们输入下面的内容来修改节点值。

    // 命令行
    set /test "testaaa"
    
    // 脚本输出
    3,3,/test修改了目录:/test

    除了值的监听外,还可以监听子节点的变化。

    function watcher($type, $state, $path){
        global $zookeeper;
        echo $type, ",", $state, ",", $path;
        switch ($type){
            case Zookeeper::CREATED_EVENT:
                echo "新建了目录:" . $path, PHP_EOL;
                break;
            case Zookeeper::DELETED_EVENT:
                echo "删除了目录:" . $path, PHP_EOL;
                break;
            case Zookeeper::CHANGED_EVENT:
                echo "修改了目录:" . $path, PHP_EOL;
                break;
            case Zookeeper::CHILD_EVENT:
                echo "修改了子目录:" . $path, PHP_EOL;
                break;
            default:
                echo "其它操作:" . $path, PHP_EOL;
                break;
            }
            $zookeeper->getChildren("/test",'watcher');
    }
    
    $zookeeper->getChildren("/test",'watcher');

    同样地,我们还是通过命令行来测试。

    // 命令行
    create -s -e /test/ooo "oo"
    
    // 脚本输出
    4,3,/test修改了子目录:/test
    
    // 命令行
    delete /test/ooo0000000082
    
    // 脚本输出
    4,3,/test修改了子目录:/test

    不管是服务注册、集群上下线、分布式锁等功能,其实都是在监听的基础上实现的。比如说集群管理,上线一台服务器就新建一个目录节点,然后其他服务器监听这个目录,有变化了就去查询一下,将新的服务器信息加入到本地配置中。

    其他函数

    除了上面这些主要的操作函数之外,还有一些辅助函数,大家了解一下即可。

    // 当前客户端连接的 sessionid 等信息
    var_dump($zookeeper->getClientId());
    // array(2) {
    //     [0]=>
    //     int(144115198348099588)
    //     [1]=>
    //     string(17) "��슅��˟g��o�-"
    //   }
    
    // ZookeeperConfig 配置对象
    var_dump($zookeeper->getConfig());
    // object(ZookeeperConfig)#3 (0) {
    // }
    
    // 当前连接的过期时间
    echo $zookeeper->getRecvTimeout(), PHP_EOL; // 10000
    
    // 连接状态,3 是已连接,和上面监听器中的 state 一样
    echo $zookeeper->getState(), PHP_EOL; // 3
    
    // 检查当前连接状态是否可以恢复
    var_dump($zookeeper->isRecoverable()); // bool(true)

    总结

    今天我们就是简单地学习了一下 PHP 中 Zookeeper 相关扩展的使用。在开头就说了,其实我并没有太多的实战经验,包括这个扩展,目前似乎也有直接可以在 Composer 中直接使用的 Zookeeper 组件了,并不需要这么费劲的安装原生编译的这种。所以,如果大家有相关经验或者资料的话,也希望多多留言,一起学习,共同进步。


    测试代码:


    https://github.com/zhangyue0503/dev-blog/blob/master/php/2021/12/source/1.%E5%9C%A8PHP%E4%B8%AD%E4%BD%BF%E7%94%A8Zookeeper.php


    参考文档:


    https://www.php.net/manual/zh/book.zookeeper.php

    搜索
    关注