PHP和zookeeper結合實踐


Zookeeper

簡單介紹

Apache Zookeeper是開發和維護開源服務器的服務,它能夠實現高度可靠的分布式協調。

安裝Zookeeper(無需安裝)


wget http://mirror.bit.edu.cn/apache/zookeeper/zookeeper-3.4.10/zookeeper-3.4.10.tar.gz
tar zxvf zookeeper-3.4.10.tar.gz

安裝Zookeeper C擴展支持


cd zookeeper-3.4.8/src/c
./configure --prefix=/usr/
make
make install

安裝php的zookeeper擴展


wget https://pecl.php.net/get/zookeeper-0.6.2.tgz
tar zxvf zookeeper-0.6.2.tgz
cd zookeeper-0.6.2
phpize
./configure --with-libzookeeper-dir=/usr/
$ make
$ make install

啟動zookeeper server


# {zookeeperserver}代表你zookeeper解壓的目錄

mkdir -p /project/zookeeper/demo/data

#zoo.conf 是默認啟動所加載的配置文件
cp /{zookeeperserver}/conf/zoo_sample.cfg /{zookeeperserver}/conf/zoo.cfg

/{zookeeperserver}/bin/zkServer start

zookeeper client 測試


/{zookeeperserver}/bin/zkCli.sh -server 127.0.0.1:2181

# [zk: 127.0.0.1:2181(CONNECTED) x] 客戶端連接信息shell
[zk: 127.0.0.1:2181(CONNECTED) 3] ls /
[zookeeper]
[zk: 127.0.0.1:2181(CONNECTED) 4] create /test qkl001
Created /test
[zk: 127.0.0.1:2181(CONNECTED) 5] create /zgq zgq002
Created /zgq
[zk: 127.0.0.1:2181(CONNECTED) 6] ls / 
[zookeeper, test, zgq]
[zk: 127.0.0.1:2181(CONNECTED) 5] delete /zgq
Created /zgq
[zk: 127.0.0.1:2181(CONNECTED) 7] quit

PHP下實踐

zookeeper類信息


Zookeeper — Zookeeper類

  -- Zookeeper::addAuth — 指定應用程序憑據

  -- Zookeeper::connect — 創建與Zookeeper溝通的句柄

  -- Zookeeper::__construct — 創建與Zookeeper溝通的句柄

  -- Zookeeper::create — 同步創建節點

  -- Zookeeper::delete — 同步刪除Zookeeper中的一個節點

  -- Zookeeper::exists — 同步檢查Zookeeper節點的存在性

  -- Zookeeper::get — 同步獲取與節點關聯的數據。

  -- Zookeeper::getAcl — 同步地獲取與節點關聯的ACL。

  -- Zookeeper::getChildren — 同步列出節點的子節點

  -- Zookeeper::getClientId — 返回客戶端會話ID,僅在連接當前連接時才有效(即最后觀察者狀態為ZooOnCeleDelphi狀態)

  -- Zookeeper::getRecvTimeout — 返回此會話的超時,如果連接當前連接(只有上次觀察者狀態為ZooOnCeleTytStand狀態)才有效。此值可能在服務器重新連接后發生更改。

  -- Zookeeper::getState — 獲取Zookeeper連接的狀態

  -- Zookeeper::isRecoverable — 檢查當前的Zookeeper連接狀態是否可以恢復

  -- Zookeeper::set — 設置與節點關聯的數據

  -- Zookeeper::setAcl — 同步設置與節點關聯的ACL

  -- Zookeeper::setDebugLevel — 設置庫的調試級別

  -- Zookeeper::setDeterministicConnOrder — 啟用/禁用仲裁端點順序隨機化

  -- Zookeeper::setLogStream — 設置庫用於日志記錄的流

  -- Zookeeper::setWatcher — 設置觀察函數

客戶端操作


zkCli.cmd

# output 表示連接成功
WatchedEvent state:SyncConnected type:None path:null
[zk: localhost:2181(CONNECTED) 0]

# 以下是操作命令
ls /
create /ztest 1

獲取節點信息(Zookeeper::get)


# get.php

$zoo = new Zookeeper('192.168.1.45:2181');
$r = $zoo->get( '/ztest');
var_dump($r);

獲取節點信息(Zookeeper::get)並設置watcher


# get.php
class ZookeeperDemo extends Zookeeper
{

    public function watcher($type, $state, $key)
    {

        var_dump($type);
        var_dump($state);
        var_dump($key);

        if ($type == 3) {
            var_dump($this->get('/zgetw'));
            // Watcher gets consumed so we need to set a new one
            $this->get('/zgetw', array($this, 'watcher'));
        }
    }

}

$zoo = new ZookeeperDemo('192.168.1.45:2181');
$zoo->get('/zgetw', [$zoo, 'watcher']);

while (true) {
    echo '.';
    sleep(1);
}

客戶端操作


set /ztest 2
set /ztest 3
set /ztest 5

獲取節點信息(Zookeeper::get) 結果


php get.php

#output 
......int(3)
int(3)
string(6) "/ztest"
string(1) "2"
..int(3)
int(3)
string(6) "/ztest"
string(1) "3"
..int(3)
int(3)
string(6) "/ztest"
string(1) "4"
.int(3)
int(3)
string(6) "/ztest"
string(1) "5"

創建子節點


create /ztest/child1 c1
# output: Created /ztest/child1

create /ztest/child2 c2
# output: Created /ztest/child2

ls /ztest
# output: [child2, child1]

php下獲取子節點


# getChild.php
$zookeeper = new Zookeeper('192.168.1.45:2181');
$path = '/ztest';
$r = $zookeeper->getchildren($path);
if (!empty($r)) {
    foreach ($r as $c)
    {
        var_dump($c);
    }
}

# php getChild.php
string(6) "child2"
string(6) "child1"

創建順序節點


[zk: localhost:2181(CONNECTED) 22] create /stest 1
Created /stest
[zk: localhost:2181(CONNECTED) 23] create -s /stest/seq 1
Created /stest/seq0000000000
[zk: localhost:2181(CONNECTED) 24] create -s /stest/seq 1
Created /stest/seq0000000001
[zk: localhost:2181(CONNECTED) 25] create -s /stest/seq 1
Created /stest/seq0000000002
[zk: localhost:2181(CONNECTED) 26] create -s /stest/seq 1
Created /stest/seq0000000003
[zk: localhost:2181(CONNECTED) 27] create -s /stest/seq 1
Created /stest/seq0000000004
[zk: localhost:2181(CONNECTED) 28] create -s /stest/seq 1
Created /stest/seq0000000005
[zk: localhost:2181(CONNECTED) 29] create -s /stest/seq 1
Created /stest/seq0000000006
[zk: localhost:2181(CONNECTED) 30] create -s /stest/seq 1
Created /stest/seq0000000007
[zk: localhost:2181(CONNECTED) 31] create -s /stest/seq 2
Created /stest/seq0000000008
[zk: localhost:2181(CONNECTED) 32] create -s /stest/seq 2
Created /stest/seq0000000009
[zk: localhost:2181(CONNECTED) 33] create -s /stest/seq 2
Created /stest/seq0000000010
[zk: localhost:2181(CONNECTED) 34] create -s /stest/seq 2
Created /stest/seq0000000011
[zk: localhost:2181(CONNECTED) 35] create -s /stest/seq 2
Created /stest/seq0000000012
[zk: localhost:2181(CONNECTED) 36] create -s /stest/seq 2
Created /stest/seq0000000013
[zk: localhost:2181(CONNECTED) 37] create -s /stest/seq 2
Created /stest/seq0000000014

代碼


$zookeeper = new Zookeeper('192.168.1.45:2181');
$aclArray = array(
    array(
        'perms'  => Zookeeper::PERM_ALL,
        'scheme' => 'world',
        'id'     => 'anyone',
    )
);
$path = '/t3';
//ZOO_EPHEMERAL = 1
//ZOO_SEQUENCE  = 2
//這里這里的flag=NULL,flag=0 表示創建永久節點,=1創建臨時節點,=2創建seq 順序節點
$realPath = $zookeeper->create($path, null, $aclArray, 1);
var_dump($realPath);

worker

一個利用順序臨時節點的leader遷移實現


# worker.php
<?php

/**
 * Created by PhpStorm.
 * User: qkl
 * Date: 2018/8/27
 * Time: 14:25
 */
class Worker
{

    const CONTAINER = '/worker_test';

    protected $acl = [
        [
            'perms' => Zookeeper::PERM_ALL,
            'scheme' => 'world',
            'id' => 'anyone'
        ]
    ];

    private $isLeader = false;

    private $znode = '';
    private $prevNode = '';

    public function __construct($host = '', $watcher_cb = null, $recv_timeout = 10000)
    {
        $this->zk = new Zookeeper($host, $watcher_cb, $recv_timeout);
    }

    public function register()
    {
        if (!$this->zk->exists(self::CONTAINER)) {
            $this->zk->create(self::CONTAINER, null, $this->acl);
        }

        # 創建一個臨時的順序節點
        $this->znode = $this->zk->create(self::CONTAINER . '/w-',
                                        null,
                                        $this->acl,
                                        Zookeeper::EPHEMERAL | Zookeeper::SEQUENCE);

        //獲取順序節點名,去除父路徑
        $this->znode = str_replace(self::CONTAINER . '/', '', $this->znode);

        printf("我創建了節點: %s\n", self::CONTAINER . '/' . $this->znode);

        $this->prevNode = $this->getPrev();

        if (is_null($this->prevNode)) {
            $this->setLeader(true);
        } else {
            $this->zk->get(self::CONTAINER . "/" . $this->prevNode, [$this, 'watcher']);
        }
    }

    public function getPrev()
    {
        $workers = $this->zk->getChildren(self::CONTAINER);
        sort($workers);
        $size = count($workers);
        for ($i = 0; $i < $size; $i++) {
            if ($this->znode == $workers[$i] && $i > 0) {
                return $workers[$i - 1];
            }
        }

        return null;
    }

    public function watcher($type, $state, $key)
    {
        $prevNode = $this->prevNode;
        printf("watchNode-getPrevious:%s\n", self::CONTAINER . "/" . $prevNode);

        if (!is_null($prevNode)) {
            if ($type == 2) {
                printf($prevNode . " had deleted\n");
            }
            if ($type == 3) {
                printf("我重新監控節點:%s\n", self::CONTAINER . "/" . $prevNode);
                $this->zk->get(self::CONTAINER . "/" . $prevNode, [$this, 'watcher']);
            }
        }
    }

    public function isLeader()
    {
        return $this->isLeader;
    }

    public function setLeader($flag)
    {
        $this->isLeader = $flag;
    }

    public function run()
    {
        $this->register();

        while (true) {
            if ($this->isLeader()) {
                $this->leaderJob();
            } else {
                $this->watcherJob();
            }

            sleep(2);
        }
    }

    public function leaderJob()
    {
        echo "現在我是Leader\n";
    }

    public function watcherJob()
    {
        echo "我在監控:".(self::CONTAINER . "/" . $this->prevNode)."\n";
    }

}

$worker = new Worker('192.168.1.45:2181');
$worker->run();

嘗試


# 終端1
php worker.php
# 終端2
php worker.php
# 終端3
php worker.php

# 此處運行不會被節點變化不會被監控到

再次嘗試


# zkCli
create /worker_test/w-
/worker_test/0000000020

php worker.php

# output
我創建了節點: /worker_test/w-0000000022
我在監控:/worker_test/w-0000000020
我在監控:/worker_test/w-0000000020
watchNode-getPrevious:/worker_test/w-0000000020
我重新監控節點:/worker_test/w-0000000020
我在監控:/worker_test/w-0000000020
我在監控:/worker_test/w-0000000020
我在監控:/worker_test/w-0000000020
我在監控:/worker_test/w-0000000020
我在監控:/worker_test/w-0000000020
watchNode-getPrevious:/worker_test/w-0000000020
我重新監控節點:/worker_test/w-0000000020
2018-08-28 02:11:46,684:2486(0x7f28ed0a1700):ZOO_WARN@zookeeper_interest@1570: Exceeded deadline by 15ms
我在監控:/worker_test/w-0000000020
我在監控:/worker_test/w-0000000020
watchNode-getPrevious:/worker_test/w-0000000020
w-0000000020 had deleted
我在監控:/worker_test/w-0000000020

這邊發現一個類似bug問題


如果我們直接運行worker.php
在worker.php運行創建的臨時順序節點是不會被watcher到的
我們必須先首先創建好相關的節點再啟動監控,不知道這里是不是php版本的zookeeper的bug
有了解的小伙伴可以告之下

Watcher通知狀態與事件類型一覽

ZOO_CREATED_EVENT(value=1):節點創建事件,需要watch一個不存在的節點,當節點被創建時觸發,此watch通過zoo_exists()設置
ZOO_DELETED_EVENT(value=2):節點刪除事件,此watch通過zoo_exists()或zoo_get()設置
ZOO_CHANGED_EVENT(value=3):節點數據改變事件,此watch通過zoo_exists()或zoo_get()設置
ZOO_CHILD_EVENT(value=4):子節點列表改變事件,此watch通過zoo_get_children()或zoo_get_children2()設置
ZOO_SESSION_EVENT(value=-1):會話事件,客戶端與服務端斷開或重連時觸發
ZOO_NOTWATCHING_EVENT(value=-2):watch移除事件,服務端出於某些原因不再為客戶端watch節點時觸發

原文地址:https://segmentfault.com/a/1190000016173878


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM