使用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 str_pad() 将字符串填充成指定长度的字符串
Feb 23 PHP
php 网页播放器用来播放在线视频的代码(自动判断并选择视频文件类型)
Jun 03 PHP
PHP生成excel时单元格内换行问题的解决方法
Aug 26 PHP
解析PayPal支付接口的PHP开发方式
Nov 28 PHP
基于curl数据采集之单页面采集函数get_html的使用
Apr 28 PHP
关于Sphinx创建全文检索的索引介绍
Jun 25 PHP
PHP中的插件机制原理和实例
Jul 08 PHP
php实现字符串首字母大写和单词首字母大写的方法
Mar 14 PHP
yii2中dropDownList实现二级和三级联动写法
Apr 26 PHP
php 7新特性之类型申明详解
Jun 06 PHP
PHP实现超简单的SSL加密解密、验证及签名的方法示例
Aug 28 PHP
PHP正则之正向预查与反向预查讲解与实例
Apr 06 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
php 判断网页是否是utf8编码的方法
2014/06/06 PHP
PHP数组相加操作及与array_merge的区别浅析
2016/11/26 PHP
Yii2-GridView 中让关联字段带搜索和排序功能示例
2017/01/21 PHP
这段js代码得节约你多少时间
2011/12/20 Javascript
js里怎么取select标签里的值并修改
2012/12/10 Javascript
Web跨浏览器进程通信(Web跨域)
2013/04/17 Javascript
JavaScript对IE操作的经典代码(推荐)
2014/03/10 Javascript
javascript中通过arguments参数伪装方法重载
2014/10/08 Javascript
详解页面滚动值scrollTop在FireFox与Chrome浏览器间的兼容问题
2015/12/03 Javascript
基于node实现websocket协议
2016/04/25 Javascript
jquery中的常见问题及快速解决方法小结
2016/06/14 Javascript
vue.js开发环境搭建教程
2017/05/04 Javascript
ExtJs的Ext.Ajax.request实现waitMsg等待提示效果
2017/06/14 Javascript
JavaScript之DOM_动力节点Java学院整理
2017/07/03 Javascript
详解适配器在JavaScript中的体现
2018/09/28 Javascript
Vue.js中对css的操作(修改)具体方式详解
2018/10/30 Javascript
JavaScript类的继承操作实例总结
2018/12/20 Javascript
js防抖函数和节流函数使用场景和实现区别示例分析
2020/04/11 Javascript
[02:11]DOTA2上海特级锦标赛主赛事第二日RECAP
2016/03/04 DOTA
[51:10]VP vs VGJ.S 2018国际邀请赛小组赛BO2 第二场 8.19
2018/08/21 DOTA
MySQLdb ImportError: libmysqlclient.so.18解决方法
2014/08/21 Python
Windows系统配置python脚本开机启动的3种方法分享
2015/03/10 Python
单利模式及python实现方式详解
2018/03/20 Python
python入门:这篇文章带你直接学会python
2018/09/14 Python
tensorflow pb to tflite 精度下降详解
2020/05/25 Python
Django后端分离 使用element-ui文件上传方式
2020/07/12 Python
CSS3实现跳动的动画效果
2016/09/12 HTML / CSS
HTML5移动端开发遇见的东西
2019/10/11 HTML / CSS
荷兰鞋子在线:Nelson Schoenen
2017/12/25 全球购物
工厂厂长岗位职责
2013/11/08 职场文书
高中毕业自我鉴定
2013/12/22 职场文书
媒体宣传策划方案
2014/05/25 职场文书
绿色出行口号
2014/06/18 职场文书
2015年网络舆情工作总结
2015/07/24 职场文书
销售人员管理制度
2015/08/06 职场文书
保安辞职申请书应该怎么写?
2019/07/15 职场文书