最近更新: 2010-08-18

PHP D-Bus 與 Gearman 之比較

我先前介紹 php-dbus 時,曾捎帶提及 PHP 還支援 Gearman 與 WebSphere MQ Series 兩種訊息匯流排架構。前幾日,便有位同事問我關於 Gearman 的事,他參考的文章則是 jaceju 的《Gearman 心得 》。 我們公司主要是使用 D-Bus 架構,他也熟悉 D-Bus 。我們公司運用 D-Bus 的途徑,大致如《D-Bus 用途說明》所述,將其作為 API 機制。他在試用 Gearman 後,說 Gearman 不能傳 object 、方法的調用敘述不像一般函數,用起來不方便。

我看了 jaceju 的《Gearman 心得 》後,就手癢用 php-dbus 寫了一個相同的範例程式。有興趣的人,可以看看這篇文章和 jaceju 那篇,比較一下 D-Bus 與 Gearman 的程式風格差異。

安裝 php-dbus

php-dbus 擴充元件的安裝方式,參閱《Write a PHP DBus client 》,在此不複述。如同 Gearman 要啟動一個 job server 居中協調, DBus 也有一個 dbus-daemon 負責訊息匯流排的牽線工作。而 dbus-daemon 目前是 Debian/Ubuntu/Fedora Linux 的必裝套件,所以不必另行安裝。

Client程式

仿 jaceju 的 client.php 。但我的 ResizeImage() 模擬非同步作業,它將直接回傳「處理中」的訊息。所以調用 ResizeImage() 之後,還會去傾聽信號 ResizeDone,等待處理完成。

t_client.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?php
//t_client.php
define('SERVICE_NAME', 'blog.rock');
define('OBJECT_PATH',  '/blog/rock');
define('MAIN_INTERFACE','blog.rock');

//$bus = new Dbus( Dbus::BUS_SESSION );
$bus = new Dbus( Dbus::BUS_SYSTEM );

$client = $bus->createProxy(SERVICE_NAME, OBJECT_PATH, MAIN_INTERFACE);

$result = $client->SendMail("rock", "web@example.com",
    "Hello\n  world\nsincerely, \nrock\n");
echo $result, "\n";

$result = $client->ResizeImage("/home/rock/test.png", 300, 200);
echo $result, "\n";

$bus->addWatch( MAIN_INTERFACE );
while (true) {
    echo "I could do something else.\n";
    $s = $bus->waitLoop(1000);
    if (!$s)
        continue;
    if ($s->matches(MAIN_INTERFACE, 'ResizeDone')) {
        echo "Resize really done\n";
        break;
    }
}
?>

Worker程式

D-Bus 並不使用 worker 這個字眼。在 D-Bus 架構中,負責維護訊息匯流排(message bus)的稱為 dbus daemon ;而接上 D-Bus 並提供方法給其他行程調用的行程,才稱為 service 或 server 。所以這個 worker 程式,按 D-Bus 的術語,其實應該叫 server 程式。

仿 jaceju 的 worker.php 。但我的 ResizeImage() 方法將返回「處理中」的訊息,等5秒後,再發出「處理完成」的信號。此在模擬非同步作業的情形。

t_worker.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
<?php
// t_worker.php
class MyDbusService {
    static function SendMail($name, $email, $message) {
        echo "Name: $name\n";
        echo "EMail: $email\n";
        echo "===========\n";
        echo $message;
        echo "===========\n";
        return "Email sending is done.";
    }

    static $processing_seconds = 0;
    static function ResizeImage($filepath, $width, $height) {
        self::$processing_seconds = 5; //模擬處理時間
        echo "I am resizing image $filepath to ${width}x${height}.\n";
        return "Image resizing is doning...";
    }

    static function emitResizeDone() {
        echo "emit signal\n";
        self::$ResizeDone->send();
    }

    static $ResizeDone;
}

//$bus = new Dbus( Dbus::BUS_SESSION, true);
$bus = new Dbus( Dbus::BUS_SYSTEM, true);

define('SERVICE_NAME', 'blog.rock');
define('OBJECT_PATH',  '/blog/rock');
define('MAIN_INTERFACE','blog.rock');

// request service name.
$bus->requestName( SERVICE_NAME );

// register object path and interface.
$bus->registerObject( OBJECT_PATH, MAIN_INTERFACE, 'MyDbusService');

// add signal
MyDbusService::$ResizeDone = new DbusSignal(
    $bus, OBJECT_PATH, MAIN_INTERFACE, 'ResizeDone'/*signal name*/);

while (true) {
    try {
        $bus->waitLoop(1000);
        if (MyDbusService::$processing_seconds > 0) {
            --MyDbusService::$processing_seconds;
            if (MyDbusService::$processing_seconds == 0)
                MyDbusService::emitResizeDone();
        }
        //echo '.';
    }
    catch (Exception $e) {
        echo $e->getMessage();
    }
}
?>

D-Bus 將訊息匯流排分為兩種,即 System Bus 與 Session Bus ,分別採用不同的安全控管政策。本文範例的 worker 程式將把訊息管道接在 System Bus 上,根據 D-Bus 的控管政策,你必須準備下列的組態文件(只有 D-Bus service 程式才需要準備此一組態文件。 D-Bus client 程式不需要),並將它儲存在 /etc/dbus-1/system.d/blog.rock.conf (此文件的主檔名必須與 service name 相同)。

blog.rock.conf
<!DOCTYPE busconfig PUBLIC
 "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
 "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
<busconfig>
  <!-- Only rock can own the service -->
  <policy user="rock">
    <allow own="blog.rock"/>
  </policy>

  <!-- Allow anyone to invoke methods (further constrained by
       PolicyKit privileges -->
  <policy context="default">
    <allow send_destination="blog.rock"
           send_interface="blog.rock"/>
  </policy>

</busconfig>

如果把訊息管道接在 Session Bus 上,則不須準備此一文件。但接在 Session Bus 的條件是你的 DBus 程式是在登入桌面環境後執行的,因為 Session Bus 接受 X window session 管制。由於大多數的 PHP 程式是由 Apache 啟動,並不是處於桌面環境的 session 內,所以不能接到 Session Bus 。故實務上,大多數 PHP 程式只能使用連接到 System Bus 的服務端。

執行結果

先執行 t_worker.php ,它會開始等待客戶端的要求。執行前,請先注意是否已經按上述要求,準備好 blog.rock.conf 文件了。

rock@desktop:~$ php t_worker.php
Name: rock
EMail: web@example.com
===========
Hello
  world
sincerely,
rock
===========
I am resizing image /home/rock/test.png to 300x200.
emit signal

再執行 t_client.php ,調用 t_worker 提供的方法,並等待它的完成信號(ResizeDone)。

rock@desktop:~$ php t_client.php
Email sending is done.
Image resizing is doning...
I could do something else.
I could do something else.
I could do something else.
I could do something else.
I could do something else.
Resize really done
rock@desktop:~$

其實在你執行 t_client.php 前,並不需要自己先啟動 t_worker.php 。只要設定好下列內容,說明如何執行你的 worker 程式,並將下列內容儲存在 /usr/share/dbus-1/system-services/blog.rock.service ,DBus daemon 就會幫你啟動 t_worker.php。

blog.rock.service
[D-BUS Service]
Name=blog.rock
Exec=/usr/bin/php /home/rock/t_worker.php
User=rock

關於 .service 的細節,請參閱《D-Bus service activation 》。

結語

D-Bus 與 Gearman 相比較,D-Bus 具有下列優點:

  • 可用性高: D-Bus 是目前主要 Linux 散佈版本的必裝套件。而且有許多現存的系統服務同時提供 D-Bus 介面。
  • 抽象性高: D-Bus 服務的調用動作,抽象到 API 的層級。因此傳遞參數與回傳值的處理方式,皆與呼叫一般函數呼叫無異。
  • 擴展性高: 支援 D-Bus 的程式語言數目,比 Gearman 多。

但是在 PHP 中,D-Bus 有一個主要缺點,就是它的 php-dbus 的成熟度比不上 php-gearman 。目前的 php-dbus 還只發展到 0.1.0 版,要避開某些情況才不會觸發錯誤。

相關文章
樂多舊網址: http://blog.roodo.com/rocksaying/archives/13478419.html