« Q4M (Queue for MySQL) 0.3 リリース | メイン | Q4M Version 0.4 で高速なクローラを書いてみた »

2008年04月04日

Parallel::Prefork - Perl でマルチプロセスなサーバを書く方法

 Perl でマルチプロセス処理を行う場合は Parallel::ForkManager を使うというのが定番かと思います。しかし、このモジュールはシグナル処理を前提とした作りになっていない注1ため、シグナルを受信するまで動き続けるようなサーバを書きづらい、という問題がありました。

 そこで、Parallel::ForkManager の API は、ほぼそのままに、シグナル処理が可能なプロセス管理モジュールを作ることにしました。それが Parallel::Prefork です。Parallel::Prefork を使うことで、Graceful Shutdown や動的再構成に対応したマルチプロセス型のサーバを簡単に書くことができるようになります。

 たとえば、Apache の prefork MPM のようなサーバは、以下のように書くことができます。

use IO::Socket::INET;
use Parallel::Prefork;

sub MaxRequestsPerChild () { 100 }

my $listen_sock = IO::Socket::INET->new(
    Listen    => 5,
    LocalAddr => '0.0.0.0:80'
    Proto     => 'tcp',
) or die $!;

my $pm = Parallel::Prefork->new({
    max_workers  => 10,                           # ワーカープロセスの個数
    trap_signals => {
        TERM => 'TERM',                           # SIGTERM を受信したらワーカープロセスを SIGTERM
        HUP  => 'TERM',                           # SIGHUP の場合も同様
});

# メインループ
while ($pm->signal_received ne 'TERM') {
    load_config();                                # 設定を読み込み
    $pm->start and next;                          # ワーカープロセス生成処理

    my $reqs_before_exit = MaxRequestsPerChild;   # ここからワーカープロセス内
    $SIG{TERM} = sub { $reqs_before_exit = 0 };
    while ($reqs_before_exit-- > 0) {
        my $s = $listen_sock->accept();           # リクエストを処理

        ...
    }
    $pm->finish;                                  # ワーカープロセスの終了処理
}

$pm->wait_all_children;                           # 子プロセスを待ち受け

 これだけで、以下のような条件を満たしたサーバの完成です。って中身はありませんが。

  • SIGTERM を受信したら、接続中の全クライアントの処理が終わったタイミングで終了
  • SIGHUP を受信したら、設定を再読み込みして、次のリクエストから適用

 また、Q4M のようなメッセージキューと組み合わせてタスクをオフロードしたりする場合等においても便利に使えると思います。興味のある方はお試しいただければと思います。

注: 参考: Perl で並列処理 (using マルチプロセス), perl & EINTR

投稿者 kazuho : 2008年04月04日 17:31 このエントリーを含むはてなブックマーク このエントリーを含むはてなブックマーク

トラックバック

このエントリーのトラックバックURL:
http://labs.cybozu.co.jp/cgi-bin/mt-admin/mt-tbp.cgi/1833

コメント

とても便利に使わせて頂いています。

一点、掲示されているサンプルソースにある$pmの定義で、"{"が閉じられていないような気がします。

my $pm = Parallel::Prefork->new({
~snip~
}});

とすればOKでした。

投稿者 tf0054 : 2009年02月22日 03:00