使用Zookeeper分布式部署PHP应用程序


Posted in PHP onMarch 15, 2019

Zookper是一种分布式的,开源的,应用于分布式应用的协作服务。它提供了一些简单的操作,使得分布式应用可以基于这些接口实现诸如同步、配置维护和分集群或者命名的服务。Zookper很容易编程接入,它使用了一个和文件树结构相似的数据模型。

虽然ZooKeeper是一个Java应用程序,但C也可以使用。这里就有个PHP的扩展,你可以从PECL中下载,或从GitHub中直接获取PHP-ZooKeeper。

要使用该扩展你首先要安装ZooKeeper。可以从官方网站下载。

$ tar zxfv zookeeper-3.4.5.tar.gz
$ cd zookeeper-3.4.5/src/c
$ ./configure --prefix=/usr/
$ make
$ sudo make install

这样就会安装ZooKeeper的库和头文件。现在准备编译PHP扩展。

$ git clone https://github.com/andreiz/php-zookeeper.git
$ cd php-zookeeper
$ phpize
$ ./configure
$ make
$ sudo make install

将“zookeeper.so”添加到PHP配置中。

$ vim /etc/php5/cli/conf.d/20-zookeeper.ini

因为我不需要运行在web服务环境下,所以这里我只编辑了CLI的配置。将下面的行复制到ini文件中。

extension=zookeeper.so

使用如下命令来确定扩展是否已起作用。

$ php -m | grep zookeeper
zookeeper

现在是时候运行ZooKeeper了。目前唯一还没有做的是配置。创建一个用于存放所有service数据的目录。

$ mkdir /home/you-account/zoo
$ cd
$ cd zookeeper-3.4.5/
$ cp conf/zoo_sample.cfg conf/zoo.cfg
$ vim conf/zoo.cfg

找到名为“dataDir”的属性,将其指向“/home/you-account/zoo”目录。

$ bin/zkServer.sh start
$ bin/zkCli.sh -server 127.0.0.1:2181
[zk: 127.0.0.1:2181(CONNECTED) 14] create /test 1
Created /test
[zk: 127.0.0.1:2181(CONNECTED) 19] ls /
[test, zookeeper]

此时,你已成功连到了ZooKeeper,并创建了一个名为“/test”的znode(稍后我们会用到)。ZooKeeper以树形结构保存数据。这很类似于文件系统,但“文件夹”(译者注:这里指非最底层的节点)又和文件很像。znode是ZooKeeper保存的实体。Node(节点)的说法很容易被混淆,所以为了避免混淆这里使用了znode。

因为我们稍后还会使用,所以这里我们让客户端保持连接状态。开启一个新窗口,并创建一个zookeeperdemo1.php文件。

<?php
class ZookeeperDemo extends Zookeeper {
 public function watcher( $i, $type, $key ) {
  echo "Insider Watcher\n";
  // Watcher gets consumed so we need to set a new one
  $this->get( '/test', array($this, 'watcher' ) );
 }
}
$zoo = new ZookeeperDemo('127.0.0.1:2181');
$zoo->get( '/test', array($zoo, 'watcher' ) );
while( true ) {
 echo '.';
 sleep(2);
}

现在运行该脚本。

$ php zookeeperdemo1.php

此处应该会每隔2秒产生一个点。现在切换到ZooKeeper客户端,并更新“/test”值。

[zk: 127.0.0.1:2181(CONNECTED) 20] set /test foo

这样就会静默触发PHP脚本中的“Insider Watcher”消息。怎么会这样的?

ZooKeeper提供了可以绑定在znode的监视器。如果监视器发现znode发生变化,该service会立即通知所有相关的客户端。这就是PHP脚本如何知道变化的。Zookeeper::get方法的第二个参数是回调函数。当触发事件时,监视器会被消费掉,所以我们需要在回调函数中再次设置监视器。

现在你可以准备创建分布式应用程序了。其中的挑战是让这些独立的程序决定哪个(是leader)协调它们的工作,以及哪些(是worker)需要执行。这个处理过程叫做leader选举,在ZooKeeper Recipes and Solutions你能看到相关的实现方法。

这里简单来说就是,每个处理(或服务器)紧盯着相邻的那个处理(或服务器)。如果一个已被监视的处理(也即Leader)退出或者崩溃了,监视程序就会查找其相邻(此时最老)的那个处理作为Leader。

在真实的应用程序中,leader会给worker分配任务、监控进程和保存结果。这里为了简化,我跳过了这些部分。

创建一个新的PHP文件,命名为worker.php。

<?php
class Worker extends Zookeeper {
 const CONTAINER = '/cluster';
 protected $acl = array(
          array(
           'perms' => Zookeeper::PERM_ALL,
           'scheme' => 'world',
           'id' => 'anyone' ) );
 private $isLeader = false;
 private $znode;
 public function __construct( $host = '', $watcher_cb = null, $recv_timeout = 10000 ) {
  parent::__construct( $host, $watcher_cb, $recv_timeout );
 }
 public function register() {
  if( ! $this->exists( self::CONTAINER ) ) {
   $this->create( self::CONTAINER, null, $this->acl );
  }
  $this->znode = $this->create( self::CONTAINER . '/w-',
                 null,
                 $this->acl,
                 Zookeeper::EPHEMERAL | Zookeeper::SEQUENCE );
  $this->znode = str_replace( self::CONTAINER .'/', '', $this->znode );
  printf( "I'm registred as: %s\n", $this->znode );
  $watching = $this->watchPrevious();
  if( $watching == $this->znode ) {
   printf( "Nobody here, I'm the leader\n" );
   $this->setLeader( true );
  }
  else {
   printf( "I'm watching %s\n", $watching );
  }
 }
 public function watchPrevious() {
  $workers = $this->getChildren( self::CONTAINER );
  sort( $workers );
  $size = sizeof( $workers );
  for( $i = 0 ; $i < $size ; $i++ ) {
   if( $this->znode == $workers[ $i ] ) {
    if( $i > 0 ) {
     $this->get( self::CONTAINER . '/' . $workers[ $i - 1 ], array( $this, 'watchNode' ) );
     return $workers[ $i - 1 ];
    }
    return $workers[ $i ];
   }
  }
  throw new Exception( sprintf( "Something went very wrong! I can't find myself: %s/%s",
             self::CONTAINER,
             $this->znode ) );
 }
 public function watchNode( $i, $type, $name ) {
  $watching = $this->watchPrevious();
  if( $watching == $this->znode ) {
   printf( "I'm the new leader!\n" );
   $this->setLeader( true );
  }
  else {
   printf( "Now I'm watching %s\n", $watching );
  }
 }
 public function isLeader() {
  return $this->isLeader;
 }
 public function setLeader($flag) {
  $this->isLeader = $flag;
 }
 public function run() {
  $this->register();
  while( true ) {
   if( $this->isLeader() ) {
    $this->doLeaderJob();
  }
  else {
   $this->doWorkerJob();
  }
   sleep( 2 );
  }
 }
 public function doLeaderJob() {
  echo "Leading\n";
 }
 public function doWorkerJob() {
  echo "Working\n";
 }
}
$worker = new Worker( '127.0.0.1:2181' );
$worker->run();

打开至少3个终端,在每个终端中运行以下脚本:

# term1
$ php worker.php
I'm registred as: w-0000000001
Nobody here, I'm the leader
Leading
# term2
$ php worker.php
I'm registred as: w-0000000002
I'm watching w-0000000001
Working
# term3
$ php worker.php
I'm registred as: w-0000000003
I'm watching w-0000000002
Working

现在模拟Leader崩溃的情形。使用Ctrl+c或其他方法退出第一个脚本。刚开始不会有任何变化,worker可以继续工作。后来,ZooKeeper会发现超时,并选举出新的leader。

虽然这些脚本很容易理解,但是还是有必要对已使用的Zookeeper标志作注释。

$this->znode = $this->create( self::CONTAINER . '/w-',
               null,
               $this->acl,
               Zookeeper::EPHEMERAL | Zookeeper::SEQUENCE );

每个znode都是EPHEMERAL和SEQUENCE的。

EPHEMRAL代表当客户端失去连接时移除该znode。这就是为何PHP脚本会知道超时。SEQUENCE代表在每个znode名称后添加顺序标识。我们通过这些唯一标识来标记worker。

在PHP部分还有些问题要注意。该扩展目前还是beta版,如果使用不当很容易发生segmentation fault。比如,不能传入普通函数作为回调函数,传入的必须为方法。我希望更多PHP社区的同仁可以看到Apache ZooKeeper的好,同时该扩展也会获得更多的支持。

ZooKeeper是一个强大的软件,拥有简洁和简单的API。由于文档和示例都做的很好,任何人都可以很容易的编写分布式软件。

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对三水点靠木的支持。如果你想了解更多相关内容请查看下面相关链接

PHP 相关文章推荐
PHP中的正规表达式(一)
Oct 09 PHP
php面向对象全攻略 (三)特殊的引用“$this”的使用
Sep 30 PHP
curl和libcurl的区别简介
Jul 01 PHP
php实现购物车功能(下)
Jan 05 PHP
PHP执行shell脚本运行程序不产生core文件的方法
Dec 28 PHP
PHP实现的DES加密解密类定义与用法示例
Nov 02 PHP
PHP面向对象程序设计重载(overloading)操作详解
Jun 13 PHP
yii框架使用分页的方法分析
Jul 25 PHP
php文件包含的几种方式总结
Sep 19 PHP
php设计模式之职责链模式定义与用法经典示例
Sep 19 PHP
PHP number_format函数原理及实例解析
Jul 14 PHP
让你的PHP,APACHE,NGINX支持大文件上传
Mar 09 PHP
php根据命令行参数生成配置文件详解
Mar 15 #PHP
详解PHP的抽象类和抽象方法以及接口总结
Mar 15 #PHP
PHP基于面向对象封装的分页类示例
Mar 15 #PHP
浅谈PHP无限极分类原理
Mar 14 #PHP
详解PHP队列的实现
Mar 14 #PHP
PHP精确到毫秒秒杀倒计时实例详解
Mar 14 #PHP
PHP的简单跳转提示的实现详解
Mar 14 #PHP
You might like
nginx+php-fpm配置文件的组织结构介绍
2012/11/07 PHP
php curl模拟post请求小实例
2013/11/13 PHP
jquery 图片Silhouette Fadeins渐显效果
2010/02/07 Javascript
js对象数组按属性快速排序
2011/01/31 Javascript
js中 关于undefined和null的区别介绍
2013/04/16 Javascript
js实现点击获取验证码倒计时效果
2021/01/28 Javascript
详解XMLHttpRequest(一)同步请求和异步请求
2016/09/14 Javascript
详解打造 Vue.js 可复用组件
2017/03/24 Javascript
jQuery树插件zTree使用方法详解
2017/05/02 jQuery
js实现canvas图片与img图片的相互转换的示例
2017/08/31 Javascript
简单实现vue验证码60秒倒计时功能
2017/10/11 Javascript
使用Angular-CLI构建NPM包的方法
2018/09/07 Javascript
详解ES6 Fetch API HTTP请求实用指南
2018/11/14 Javascript
详解基于Vue,Nginx的前后端不分离部署教程
2018/12/04 Javascript
NodeJs生成sitemap站点地图的方法示例
2019/06/11 NodeJs
浅析JavaScript 函数柯里化
2020/09/08 Javascript
[02:07]DOTA2新英雄展现中国元素,完美“圣典”亮相央视
2016/12/19 DOTA
详细介绍Python函数中的默认参数
2015/03/30 Python
python在线编译器的简单原理及简单实现代码
2018/02/02 Python
Python基于sklearn库的分类算法简单应用示例
2018/07/09 Python
TensorFlow Session会话控制&amp;Variable变量详解
2018/07/30 Python
对python中的six.moves模块的下载函数urlretrieve详解
2018/12/19 Python
在python 中split()使用多符号分割的例子
2019/07/15 Python
Python笔记之观察者模式
2019/11/20 Python
基于python 取余问题(%)详解
2020/06/03 Python
python获取系统内存占用信息的实例方法
2020/07/17 Python
海淘母婴商城:国际妈咪
2016/07/23 全球购物
咖啡书吧创业计划书
2014/01/13 职场文书
幼儿园师德师风学习材料
2014/05/29 职场文书
关于教师节的广播稿
2014/09/10 职场文书
校园广播稿精选
2014/10/01 职场文书
2015年六一儿童节活动总结
2015/02/11 职场文书
优秀党员个人总结
2015/02/14 职场文书
爱岗敬业事迹材料
2019/06/20 职场文书
Nginx安装完成没有生成sbin目录的解决方法
2021/03/31 Servers
Pygame Draw绘图函数的具体使用
2021/11/17 Python