前の記事では、FastCGIアプリケーションを悪魔化する方法が示されました。 結果のデーモンは要求を正常に処理しますが、重大な欠点が1つあります。複数の要求を同時に処理する方法がわかりません。
さらに、複数の同時リクエストを処理するという点では、デーモンの状況は通常のCGIアプリケーションの状況よりもさらに悪化しています。 CGIアプリケーションを必要な数のインスタンスで(たとえば、着信要求ごとに)Webサーバーで起動できる場合、1つのインスタンスで動作するデーモンは着信要求をキューに入れます。 前の要求が完了するまで、他のすべての要求は待機する必要があります。
FastCGIアプリケーションが複数の要求を同時に処理できるようにするには、独自のコピーを作成できる必要があります。 この場合、同時に受信したリクエストは、複数のアプリケーションインスタンスによって同時に処理されます。
コピーを作成する方法は?
実際、プロセスのコピーをコピーするのは難しい作業ではありません。 作成されたコピーのホストを管理することがタスクです。
FCGI :: ProcManager moduleを使用して、デーモンをスミスのエージェントに
変えます 。
FCGI :: ProcManagerモジュールは、3つの主要なタスクを実行します。
1)デーモンの作業コピーを作成します-ハンドラーまたはワーカー(またはモジュール自体の用語ではサーバー)
2)操作中のハンドラーの状態を監視します
3)外部干渉の場合のハンドラーの動作を制御します
ハンドラープロセスに加えて、FCGI :: ProcManagerはプロセスマネージャーも起動します。 プロセスマネージャはクライアント要求を処理しません。そのタスクはハンドラを管理することです。
並列化を組み込む前に、この点に注意を向けたいと思います。 前の記事で、悪魔化プロセスを2つの部分に分割する必要があると言われました。そうしないと、不快な驚きが待っているかもしれません。 次に、塩が何であるかを説明します。
前の記事のコードのセクション(短縮形)を考えてみましょう。
#悪魔{
#...ここで切り取ります...
POSIX :: setuid(65534)またはdie "Ca n't set uid:$!";
reopen_std();
#}
my $ socket = FCGI :: OpenSocket( ":9000"、5);
my $ request = FCGI :: Request(\ * STDIN、\ * STDOUT、\ * STDERR、\%ENV、$ socket);
while($ request-> Accept()> = 0){
reopen_stdコマンドは、コンソールから標準記述子を切断します。 これは、特に、このコマンドの実行後に発生する可能性のあるすべてのエラーに関するメッセージ(たとえば、OpenSocket関数内)がどこにも送信されず、アプリケーションが単に黙って終了することを意味します。 これは、私が話した非常に不快な驚きです-アプリケーションは正常に起動したようですが、突然プロセスのリストに魔法のように表示されません。
並列化が失敗すると、状況はさらに悪化します。 エラーの原因となったハンドラーは強制終了されますが、その代わりに、プロセスマネージャーはすぐに別のハンドラーを起動します。 彼にエラーが再び発生し、彼は再び殺されるなど、輪になっていきます。 外見上、これらはすべて完全に無害に見えます-デーモンが起動し、エラーが表示されず、特定の数のハンドラーの存在がプロセスのリストに明確に表示されます。 ただし、デーモンは要求に応答せず、さらに悪いことに、しばらくすると、システムが容赦なく遅くなり、プロセッサが100%ビジーであり、負荷平均が容赦なく増大していることに突然気づくでしょう。
システムは驚きです! -絶え間なく、死にかけ、新たにプロセスを開始するために途方もない速度でディスパッチすることに忙しくなります。 ハンドラーのPIDが常に変化していることに気づき、これが何を意味するのかを理解し、行動を起こすには、多くの注意が必要です。
このような驚きを避けるために、reopen_std関数の呼び出しは、悪魔化ブロックの残りのコードから分離する必要があります。 要求処理サイクルの直前にこの関数を呼び出します。
コードの同じセクションを取り、変更を加えます。
#悪魔{
#...ここで切り取ります...
POSIX :: setuid(65534)またはdie "Ca n't set uid:$!";
#}
my $ socket = FCGI :: OpenSocket( ":9000"、5);
my $ request = FCGI :: Request(\ * STDIN、\ * STDOUT、\ * STDERR、\%ENV、$ socket);
#悪魔{
reopen_std();
#}
while($ request-> Accept()> = 0){
ご覧のとおり、この場合、OpenSocketコマンドとRequestコマンドは、悪魔化プロセスの「内部」にあるように見えます。 言い換えれば、悪魔化のプロセスは2つの部分に分割され、既成のモジュールを悪魔化に使用した場合には不可能になります。
これで、reopen_stdコマンドが呼び出される前に発生したエラーに関するメッセージがコンソールに表示されます。 したがって、何かがうまくいかないことがすぐにわかります。
それでは、前の記事のデーモンコードを使用して、並列化を統合してみましょう。
#!/ usr / bin / perl
#物を整理する
厳格な使用;
警告を使用します。
#このモジュールはFastCGIプロトコルを実装します
FCGIを使用します。
#このモジュールは、概念についてOSと話すためのものです:)
POSIXを使用します。
#並列化{
#このモジュールはリクエストの並列処理を提供します
FCGIを使用:: ProcManager qw(pm_manage pm_pre_dispatch pm_post_dispatch);
#}
#フォーク
#親を取り除く
fork_proc()&& exit 0;
#新しいセッションを開始
#私たちの悪魔は新しいセッションの祖先になります
POSIX :: setsid()またはdie "sidを設定できません:$!";
#ルートディレクトリに移動
#ファイルシステムのアンマウントを妨げないように
chdir '/'またはdie "Ca n't chdir:$!";
#ユーザーをnobodyに変更
#私たちは妄想ですよね?
POSIX :: setuid(65534)またはdie "Ca n't set uid:$!";
#ソケットを開く
#デーモンはポート9000でリッスンします
#要求キューの長さ-5個
my $ socket = FCGI :: OpenSocket( ":9000"、5);
#聞き始める
#デーモンは標準記述子をインターセプトします
my $ request = FCGI :: Request(\ * STDIN、\ * STDOUT、\ * STDERR、\%ENV、$ socket);
#並列化
#ハンドラーの起動
#指定された数のハンドラーが起動されます(この場合は2)
pm_manage(n_processes => 2);
#}
#詳細{
#特定のハンドラーごとに固有のコードが必要です
#たとえば、データベースへの接続を開く
#}
#/ dev / nullで標準記述子を再度開きます
#ユーザーと会話しなくなった
reopen_std();
私の$カウント= 1;
#無限ループ
#受信したリクエストごとに、サイクルの1つの「革命」が実行されます。
while($ request-> Accept()> = 0){
#並列化
#ハンドラー管理
#外部干渉に反応する
pm_pre_dispatch();
#}
#ループ内で、必要なすべてのアクションが実行されます
print "Content-Type:text / plain \ r \ n \ r \ n";
「$$:」を印刷します。
#並列化
#ハンドラー管理
#外部干渉に反応する
pm_post_dispatch();
#}
};
#フォーク
sub fork_proc {
私の$ pid;
フォーク:{
if(defined($ pid = fork)){
return pid;
}
elsif($!=〜/これ以上のプロセスはありません/){
睡眠5;
REDO FORK;
}
その他{
die "フォークできません:$!";
};
};
};
#/ dev / nullで標準記述子を再度開きます
sub reopen_std {
open(STDIN、 "+> / dev / null")またはdie "Ca n't open STDIN:$!";
open(STDOUT、 "+>&STDIN")またはdie "Ca n't open STDOUT:$!";
open(STDERR、 "+>&STDIN")またはdie "Ca n't open STDERR:$!";
};
どんな特徴的な機能がありますか?
まず、pm_manageコマンドの場所に注意してください。
一方では、ハンドラーを起動する前に、すべてのFastCGIアプリケーションに共通のコマンド(ソケットの作成や盗聴の開始など)を実行する必要があります。 ハンドラーを開始できず、複数のプロセスが1つのソケットにバインドすると、エラーが発生します。
一方、特定の各ハンドラーに固有のコマンド(データベース接続を開くなど)は、ハンドラーの起動後に配置する必要があります。 データベースへの接続を作成し、それをプロセスと共有することはできません。これにより、不必要な問題が発生します。
さて、reopen_stdコマンドは、サイクルの開始直前に、すべての準備コマンドの後に配置する必要があることをもう一度思い出します。
サイクルは、pm_pre_dispatchコマンドとpm_post_dispatchコマンドでそれぞれ開始および終了する必要があります。 これらの2つのコマンドは、外部干渉が発生した場合のハンドラーの動作を制御します。 外部干渉とは、たとえば、
killコマンドからFastCGIアプリケーションによって信号を受信することを意味します。 それらがないと、ハンドラーはシグナルに正しく応答しません。
悪魔を開始します。 起動すると、デーモンはおよそ次のようにコンソールに出力します。
#./test.plFastCGI:マネージャー(pid 1858):初期化
FastCGI:マネージャー(pid 1858):サーバー(pid 1859)が開始されました
FastCGI:サーバー(pid 1859):初期化
FastCGI:マネージャー(pid 1858):サーバー(pid 1860)が開始されました
FastCGI:サーバー(pid 1860):初期化
ここでは、プロセスマネージャー(pid 1858)と2つのハンドラー(pid 1859および1860)が起動したというメッセージが表示されます。
プロセスのリストを見てみましょう。
#ps -aux | grep perl誰も1858 0.0 0.2 5852 3816 ?? 21:09 0:00 perl-fcgi-pm(perl5.8.8)
誰も1859 0.0 0.2 5852 3848 ?? 私21:09 0:00 perl-fcgi(perl5.8.8)
誰も1860 0.0 0.2 5852 3848 ?? 私21:09 0:00 perl-fcgi(perl5.8.8)
ここで、プロセスマネージャはプロセッサと単純な接尾辞「pm」で異なることがわかります。
FastCGIアプリケーションを制御するには、ハンドラーではなくプロセスマネージャーに信号を送信する必要があります。 プロセスマネージャは、HUPとTERMの2つの信号を解決します。 彼は次のようにします。
HUPシグナルを受信すると、プロセスマネージャーはTERMシグナルをすべてのハンドラーに送信します。 ハンドラは死に、プロセスマネージャは新しいハンドラを起動します。 このようにして、ハングしたハンドラーの問題が解決されます。
TERMシグナルを受信すると、プロセスマネージャはすべてのハンドラにTERMシグナルを送信し、ハンドラが停止するのを待ってから、自身を停止します。 プロセッサが自発的に死ぬことを望まない場合、プロセスマネージャはKILLシグナルを送信し、そこから出られなくなります。
興味深い点:どちらの場合も、ハンドラーはすぐに死ぬことはなく、処理された要求を最後まで処理できます。 TERMシグナルを受け取ったハンドラーは、有用な作業を完了し、クライアントに答えを与え、それが終了した後にのみ。
この動作は、Request関数の呼び出しに別のパラメーターを追加することで変更できます-FCGI :: FAIL_ACCEPT_ON_INTR(これは、FCGIモジュールによってエクスポートされる定数です):
my $ request = FCGI :: Request(\ * STDIN、\ * STDOUT、\ * STDERR、\%ENV、$ socket、FCGI :: FAIL_ACCEPT_ON_INTR);
このパラメーターを追加した後、シグナルを受信するとすぐにハンドラーが停止します。 このパラメーターは、すべてのハンドラーが作業を完了するまで待機しないように、デバッグ段階で使用すると便利です。
これで、PerlでFastCGIアプリケーションを作成することに関する3部作が完成しました。
(
オリジナル記事 )