在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
参考文档: