使用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 相关文章推荐
用DBSQL类加快开发MySQL数据库程序的速度
Oct 09 PHP
php5.2.0内存管理改进
Jan 22 PHP
一步一步学习PHP(7) php 字符串相关应用
Mar 05 PHP
基于PHPExcel的常用方法总结
Jun 13 PHP
PHP提示Cannot modify header information - headers already sent by解决方法
Sep 22 PHP
CI框架安全类Security.php源码分析
Nov 04 PHP
PHP将页面中点击数量高的链接进行高亮显示的方法
May 30 PHP
微信支付开发动态链接Native支付
Jul 12 PHP
php web环境和命令行环境下查找php.ini的位置
Jul 17 PHP
php创建多级目录与级联删除文件的方法示例
Sep 12 PHP
php实现的支付宝网页支付功能示例【基于TP5框架】
Sep 16 PHP
php反序列化长度变化尾部字符串逃逸(0CTF-2016-piapiapia)
Feb 15 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实现根据字符串生成对应数组的方法
2014/09/22 PHP
JavaScript实现禁止后退的方法
2006/12/27 Javascript
获取网站跟路径的javascript代码(站点及虚拟目录)
2009/10/20 Javascript
js getElementsByTagName的简写方式
2010/06/27 Javascript
javascript同页面多次调用弹出层具体实例代码
2013/08/16 Javascript
利用javascript实现web页面中指定区域打印
2013/10/30 Javascript
js正则表达exec与match的区别说明
2014/01/29 Javascript
动态添加删除表格行的js实现代码
2014/02/28 Javascript
JavaScript给按钮绑定点击事件(onclick)的方法
2015/04/07 Javascript
借助FileReader实现将文件编码为Base64后通过AJAX上传
2015/12/24 Javascript
JavaScript的ExtJS框架中数面板TreePanel的使用实例解析
2016/05/21 Javascript
前端学习笔记style,currentStyle,getComputedStyle的用法与区别
2016/05/28 Javascript
原生JS实现首页进度加载动画
2016/09/14 Javascript
jQuery插件FusionCharts绘制2D环饼图效果示例【附demo源码】
2017/04/10 jQuery
使用原生js写ajax实例(推荐)
2017/05/31 Javascript
js禁止Backspace键使浏览器后退的实现方法
2017/09/01 Javascript
Angular 作用域scope的具体使用
2017/12/11 Javascript
JS实现去除数组中重复json的方法示例
2017/12/21 Javascript
详解react关于事件绑定this的四种方式
2018/03/09 Javascript
浅谈Angular7 项目开发总结
2018/12/19 Javascript
es6 symbol的实现方法示例
2019/04/02 Javascript
深入学习JavaScript中的bom
2019/05/27 Javascript
Vue+Element UI+vue-quill-editor富文本编辑器及插入图片自定义
2019/08/20 Javascript
Vue动态加载图片在跨域时无法显示的问题及解决方法
2020/03/10 Javascript
Python中使用SAX解析xml实例
2014/11/21 Python
Python 冒泡,选择,插入排序使用实例
2015/02/05 Python
python编写分类决策树的代码
2017/12/21 Python
python如何保存文本文件
2020/06/07 Python
浅谈HTML5 Web Worker的使用
2018/01/05 HTML / CSS
Delphi工程师笔试题
2013/09/21 面试题
高一新生军训感言
2014/03/02 职场文书
房屋租赁意向书
2014/04/01 职场文书
《富饶的西沙群岛》教学反思
2014/04/09 职场文书
个人委托书范本
2014/09/13 职场文书
小学英语教学经验交流材料
2015/11/02 职场文书
javascript遍历对象的五种方式实例代码
2021/10/24 Javascript