原文:Distributed application in PHP with Apache Zookeeper

地址:http://systemsarchitect.net/distributed-application-in-php-with-apache-zookeeper/

Apache Zookeeper是我最近遇到的最酷的技术,我是在研究Solr Cloud功能的时候发现的。Solr的分布式计算让我印象深刻。你只要开启一个新的实例就能自动在Solr Cloud中找到。它会将自己分派到某个分片中,并确定出自己是一个Leader(源)还是一个副本。不一会儿,你就可以在你的那些服务器上查询到了。即便某些服务器宕机了也可以继续工作。非常动态、聪明、酷。

将运行多个应用程序作为一个逻辑程序并不是什么新玩意。事实上,我在几年前就已写过类似的软件。这种架构比较让人迷惑,使用起来也费劲。为此Apache Zookeeper提供了一套工具用于管理这种软件。

为什么叫Zoo?“因为要协调的分布式系统是一个动物园”。

在本篇文章中,我将说明如何使用PHP安装和集成Apache ZooKeeper。我们将通过service来协调各个独立的PHP脚本,并让它们同意某个成为Leader(所以称作Leader选举)。当Leader退出(或崩溃)时,worker可检测到并再选出新的leader。

ZooKeeper是一个中性化的Service,用于管理配置信息、命名、提供分布式同步,还能组合Service。所有这些种类的Service都会在分布式应用程序中使用到。每次编写这些Service都会涉及大量的修bug和竞争情况。正因为这种编写这些Service有一定难度,所以通常都会忽视它们,这就使得在应用程序有变化时变得难以管理应用程序。即使处理得当,实现这些服务的不同方法也会使得部署应用程序变得难以管理。

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

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

  1. $ tar zxfv zookeeper-3.4.8.tar.gz
  2. $ cd zookeeper-3.4.8/src/c
  3. $ ./configure --prefix=/usr/
  4. $ make
  5. $ sudo make install

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

  1. $ cd
  2. $ git clone https://github.com/andreiz/php-zookeeper.git
  3. $ cd php-zookeeper
  4. $ phpize
  5. $ ./configure
  6. $ make
  7. $ sudo make install

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

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

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

  1. extension=zookeeper.so

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

  1. $ php -m | grep zookeeper
  2. zookeeper

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

  1. $ mkdir /home/you-account/zoo
  2. $ cd
  3. $ cd zookeeper-3.4.5/
  4. $ cp conf/zoo_sample.cfg conf/zoo.cfg
  5. $ vim conf/zoo.cfg

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

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

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

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

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

现在运行该脚本:

  1. $ php zookeeperdemo1.php

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

  1. [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:

  1. <?php
  2. class Worker extends Zookeeper {
  3. const CONTAINER = '/cluster';
  4. protected $acl = array(
  5. array(
  6. 'perms' => Zookeeper::PERM_ALL,
  7. 'scheme' => 'world',
  8. 'id' => 'anyone' ) );
  9. private $isLeader = false;
  10. private $znode;
  11. public function __construct( $host = '', $watcher_cb = null, $recv_timeout = 10000 ) {
  12. parent::__construct( $host, $watcher_cb, $recv_timeout );
  13. }
  14. public function register() {
  15. if( ! $this->exists( self::CONTAINER ) ) {
  16. $this->create( self::CONTAINER, null, $this->acl );
  17. }
  18. $this->znode = $this->create( self::CONTAINER . '/w-',
  19. null,
  20. $this->acl,
  21. Zookeeper::<span class="KSFIND_CLASS" id="0KSFindDIV">EPHEMER</span>AL | Zookeeper::SEQUENCE );
  22. $this->znode = str_replace( self::CONTAINER .'/', '', $this->znode );
  23. printf( "I'm registred as: %s\n", $this->znode );
  24. $watching = $this->watchPrevious();
  25. if( $watching == $this->znode ) {
  26. printf( "Nobody here, I'm the leader\n" );
  27. $this->setLeader( true );
  28. }
  29. else {
  30. printf( "I'm watching %s\n", $watching );
  31. }
  32. }
  33. public function watchPrevious() {
  34. $workers = $this->getChildren( self::CONTAINER );
  35. sort( $workers );
  36. $size = sizeof( $workers );
  37. for( $i = 0 ; $i < $size ; $i++ ) {
  38. if( $this->znode == $workers[ $i ] ) {
  39. if( $i > 0 ) {
  40. $this->get( self::CONTAINER . '/' . $workers[ $i - 1 ], array( $this, 'watchNode' ) );
  41. return $workers[ $i - 1 ];
  42. }
  43. return $workers[ $i ];
  44. }
  45. }
  46. throw new Exception(  sprintf( "Something went very wrong! I can't find myself: %s/%s",
  47. self::CONTAINER,
  48. $this->znode ) );
  49. }
  50. public function watchNode( $i, $type, $name ) {
  51. $watching = $this->watchPrevious();
  52. if( $watching == $this->znode ) {
  53. printf( "I'm the new leader!\n" );
  54. $this->setLeader( true );
  55. }
  56. else {
  57. printf( "Now I'm watching %s\n", $watching );
  58. }
  59. }
  60. public function isLeader() {
  61. return $this->isLeader;
  62. }
  63. public function setLeader($flag) {
  64. $this->isLeader = $flag;
  65. }
  66. public function run() {
  67. $this->register();
  68. while( true ) {
  69. if( $this->isLeader() ) {
  70. $this->doLeaderJob();
  71. }
  72. else {
  73. $this->doWorkerJob();
  74. }
  75. sleep( 2 );
  76. }
  77. }
  78. public function doLeaderJob() {
  79. echo "Leading\n";
  80. }
  81. public function doWorkerJob() {
  82. echo "Working\n";
  83. }
  84. }
  85. $worker = new Worker( '127.0.0.1:2181' );
  86. $worker->run();

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

  1. # term1
  2. $ php worker.php
  3. I'm registred as: w-0000000001
  4. Nobody here, I'm the leader
  5. Leading
  6. # term2
  7. $ php worker.php
  8. I'm registred as: w-0000000002
  9. I'm watching w-0000000001
  10. Working
  11. # term3
  12. $ php worker.php
  13. I'm registred as: w-0000000003
  14. I'm watching w-0000000002
  15. Working

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

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

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

每个znode都是EPHEMERAL和SEQUENCE的。

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

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

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

相关连接:
Yahoo research on ZooKeeper. 非常好的阅读材料,拥有真实的应用程序示例。如果你只要阅读一份ZooKeeper的资料,那么就是这份了。

PHP ZooKeeper

PHP ZooKeeper API

PHP ZooKeeper example

关于 php for zookeeper的更多相关文章

  1. 架构设计:远程调用服务架构设计及zookeeper技术详解(下篇)

    一.下篇开头的废话 终于开写下篇了,这也是我写远程调用框架的第三篇文章,前两篇都被博客园作为[编辑推荐]的文章,很兴奋哦,嘿嘿~~~~,本人是个很臭美的人,一定得要截图为证: 今天是2014年的第一天 ...

  2. [译]ZOOKEEPER RECIPES-Leader Election

    选主 使用ZooKeeper选主的一个简单方法是,在创建znode时使用Sequence和Ephemeral标志.主要思想是,使用一个znode,比如"/election",每个客 ...

  3. zookeeper源码分析之六session机制

    zookeeper中session意味着一个物理连接,客户端连接服务器成功之后,会发送一个连接型请求,此时就会有session 产生. session由sessionTracker产生的,sessio ...

  4. zookeeper源码分析之五服务端(集群leader)处理请求流程

    leader的实现类为LeaderZooKeeperServer,它间接继承自标准ZookeeperServer.它规定了请求到达leader时需要经历的路径: PrepRequestProcesso ...

  5. zookeeper源码分析之四服务端(单机)处理请求流程

    上文: zookeeper源码分析之一服务端启动过程 中,我们介绍了zookeeper服务器的启动过程,其中单机是ZookeeperServer启动,集群使用QuorumPeer启动,那么这次我们分析 ...

  6. zookeeper源码分析之三客户端发送请求流程

    znode 可以被监控,包括这个目录节点中存储的数据的修改,子节点目录的变化等,一旦变化可以通知设置监控的客户端,这个功能是zookeeper对于应用最重要的特性,通过这个特性可以实现的功能包括配置的 ...

  7. zookeeper源码分析之二客户端启动

    ZooKeeper Client Library提供了丰富直观的API供用户程序使用,下面是一些常用的API: create(path, data, flags): 创建一个ZNode, path是其 ...

  8. zookeeper源码分析之一服务端启动过程

    zookeeper简介 zookeeper是为分布式应用提供分布式协作服务的开源软件.它提供了一组简单的原子操作,分布式应用可以基于这些原子操作来实现更高层次的同步服务,配置维护,组管理和命名.zoo ...

  9. zookeeper集群的搭建以及hadoop ha的相关配置

    1.环境 centos7 hadoop2.6.5 zookeeper3.4.9 jdk1.8 master作为active主机,data1作为standby备用机,三台机器均作为数据节点,yarn资源 ...

  10. 如何编译Zookeeper源码

    1. 安装Ant Ant下载地址:http://ant.apache.org/bindownload.cgi 解压即可. 2. 下载Zookeeper源码包 https://github.com/ap ...

随机推荐

  1. 获取手机本地IP地址

    public String getLocalIpAddress() { try { for (Enumeration<NetworkInterface> en = NetworkInter ...

  2. gradle自动刷新

    1.gradle每次更新build.gradle都要刷新一下才能下载依赖 这样显得不太方便. 2.可以在Preferences修改配置来达到自动刷新的目的 如图所示,直接勾选即可,自动同步项目

  3. java课堂疑问解答与思考2

    问题一 编写一个方法,使用以上算法生成指定数目(比如1000个)的随机整数. 答:Xn+1=(7^5*Xn)mod(2^31-1) 程序源码: import java.util.Random; imp ...

  4. Java锁机制ReentrantLock

    ReentrantLock 锁常用于保证程序的人为顺序执行. 写一个类模拟ReentrantLock类的功能 class MyLock{ private boolean lock = false; p ...

  5. linxu passwd 给linux用户设置密码 命令

    [root@localhost ~]# passwd # 修改 root 用户的密码 passwd 给linux用户设置密码 命令 passwd www 直接passwd是当前用户设置密码 非交互式修 ...

  6. python 并发编程 多线程 互斥锁

    互斥锁 并行变成串行,牺牲效率 保证数据安全,实现局部串行 保护不同的数据,应该加不同的锁 现在一个进程 可以有多个线程 所有线程都共享进程的地址空间 实现数据共享 共享带来问题就会出现竞争 竞争就会 ...

  7. java期末考试

    水仙花数 package txt; public class shuixianhua { public static void main(String[] args) { // TODO Auto-g ...

  8. tableau日常管理

    各文件位置: https://help.tableau.com/current/server-linux/zh-cn/cli_default_filepaths_tsm.htm ldap配置: htt ...

  9. airflow部署

    官网: http://airflow.apache.org/installation.html 原理: https://www.cnblogs.com/cord/p/9450910.html 安装: ...

  10. Solr 4.4.0安装

    软件包准备 JDK http://download.oracle.com/otn/java/jdk/6u45-b06/jdk-6u45-linux-x64.bin Solr https://archi ...