ホームページ >運用・保守 >安全性 >Gunicorn Arbiter のソースコードを分析する方法

Gunicorn Arbiter のソースコードを分析する方法

PHPz
PHPz転載
2023-05-12 16:28:181476ブラウズ

前述したように、Arbiter はガニコーン マスター プロセスの中核です。 Arbiter は主に、ワーカー プロセスの起動、監視、強制終了などのワーカー プロセスの管理を担当しますが、同時に、特定の信号が発生したときにアプリ アプリケーションのホット アップデート (リロード) やオンラインでの gunicorn のアップグレードも行うことができます。 Arbiter のコア コードは 1 つのファイルに含まれており、コード量はそれほど多くありません。ソース コードはここにあります: https://github.com/benoitc/gunicorn。

Arbiter には主に次のメソッドがあります:

setup:

設定項目の処理。最も重要なものはワーカーの数と作業中のワーカーです。 model

init_signal

信号処理関数の登録

handle_xxx:

それぞれの固有の処理関数signal

kill_worker、kill_workers:

ワーカー プロセスにシグナルを送信します

spawn_worker、spawn_workers:

フォークして新しいワーカー プロセスを作成します

murder_workers:

一定期間応答しないワーカー プロセスを強制終了します

manage_workers:

構成ファイル内のワーカーの数と現在アクティブなワーカーの数に基づいて、ワーカー プロセスをフォークするか強制終了するかを決定します

reexec:

シグナル SIGUSR2 呼び出しを受信した後、gunicorn をオンラインでアップグレードします

#reload:

シグナル SIGHUP 呼び出しを受信した後、ワーカー プロセスは新しい構成に基づいて開始され、以前のワーカー プロセスは強制終了されます

sleep:

信号処理がない場合は、選択タイムアウトを使用してスリープし、ウェイクアップ

#wakeup:

パイプにメッセージを書き込んでプロセスをウェイクアップ

run:

メイン ループ

他のコード (アプリケーション) __init__ によって実際に呼び出されるアービターの唯一の関数とメソッドを 1 行のコードで記述します:

Arbiter(self) .run()

上記のコードの self は Application インスタンスであり、_ _init__ は setup を呼び出して構成項目を設定します。以下は run メソッドの疑似コードです。


def run()
    self.init_signal()
    self.LISTENERS = create_sockets(self.cfg, self.log)
    self.manage_workers()    while True:        if no signal in SIG_QUEUE
            self.sleep()        else:
            handle_signal()


## fork 子プロセスについて

fork 子プロセス コードは spawn_worker にあり、ソースコードは次のとおりです。

Arbiter.spawn_worker如何进行gunicorn Arbiter 源码解析

メインプロセス:

(1) worker_class をロードしてインスタンス化します。 (デフォルトは同期モデル SyncWorker)

(2) 親プロセス (マスタープロセス) は fork 後に戻り、後続のロジックはすべて子プロセスで実行されます

(3) worker.init_process を呼び出しますループに入り、すべての新しいチャネル IELTS トレーニング作業はすべてこのループ内にあります

(4) ループが終了したら、sys.exit(0)

(5) 最後に、最後に、ワーカー プロセスの終了を記録します

以下は私が自分で書いたコードの一部で、メインのフォーク プロセスを簡素化します

#

 1 # prefork.py 2 import sys 3 import socket 4 import select 5 import os 6 import time 7   8 def do_sub_process(): 9     pid = os.fork()10     if pid < 0:11         print &#39;fork error&#39;12         sys.exit(-1)13     elif pid > 0:14         print 'fork sub process %d'  % pid15         return16  17     # must be child process18     time.sleep(1)19     print 'sub process will exit', os.getpid(), os.getppid()20     sys.exit(0)21  22 def main():23     sub_num = 224     for i in range(sub_num):25         do_sub_process()26     time.sleep(10)27     print 'main process will exit', os.getpid()28  29 if __name__ == '__main__':30     main()


テスト環境での出力:

fork sub process 9601

fork sub process 9602

sub process will exit 9601 9600

サブプロセスは終了します 9602 9600

メインプロセスは終了します 9600

20 行目で

sys.exit

を呼び出して、子プロセスが確実に終了するようにし、それ以外の場合はメイン関数の for ループが続行され、その後のロジックが続行されます。 19行目をコメントアウトして再度実行すると、出力を見れば分かります。

子プロセスの強制終了について

マスター プロセスがワーカー プロセスを強制終了するのは非常に簡単です。シグナルを直接送信します。ソース コードは次のとおりです:

 1     def kill_worker(self, pid, sig): 2         """\ 3         Kill a worker 4  5         :attr pid: int, worker pid 6         :attr sig: `signal.SIG*` value 7          """ 8         try: 9             os.kill(pid, sig)10         except OSError as e:11             if e.errno == errno.ESRCH:12                 try:13                     worker = self.WORKERS.pop(pid)14                     worker.tmp.close()15                     self.cfg.worker_exit(self, worker)16                     return17                 except (KeyError, OSError):18                     return19             raise


睡眠と覚醒について

Arbiter の睡眠と覚醒について見てみましょう。 Arbiter は、処理する信号がないときに「スリープ」します。もちろん、実際には time.sleep を呼び出しません。そうしないと、信号が届いてもすぐに処理されません。ここでの実装は、パイプと選択タイムアウトを使用して、より賢明です。コードを見るだけで、

        def sleep(self):        """\
        Sleep until PIPE is readable or we timeout.
        A readable PIPE means a signal occurred.        """
            ready = select.select([self.PIPE[0]], [], [], 1.0) # self.PIPE = os.pipe()
            if not ready[0]: 
                return
            while os.read(self.PIPE[0], 1):                pass

コード内のコメントが非常に明確です。PIPE が読みやすく、すぐに返されるか、待ち時間がタイムアウトします。シグナルが発生したため、パイプは読み取り可能です。ここではパイプ関数を見ていきます

    ## os.
  • pipe()

    パイプを作成するそれぞれ読み取りと書き込みに使用できるファイル記述子のペア
  • (r,w)
  • を返します。

    次に、パイプが読み取り可能になる場合を見てみましょう。パイプに何を書き込むか、これがウェイクアップ関数の機能です
        def wakeup(self):            """
            Wake up the arbiter by writing to the PIPE            """
            os.write(self.PIPE[1], b'.')

最後にアービターの信号処理が取り付けられます

:

Exit, INT: 迅速なシャットダウン

TERM: 正常なシャットダウン。タイムアウトになるまで、ワーカーが現在のリクエストを完了するのを待ちます。

HUP: 構成をリロードし、新しい構成で新しいワーカー プロセスを開始し、古いワーカー プロセスを正常にシャットダウンします。アプリケーションがプリロードされていない場合 (--preload オプションを使用)、Gunicorn は新しいバージョンもロードします。

TTIN: プロセス数を 1 つ増やします

TTOU: プロセス数を 1 つ減らします

USR1: ログ ファイルを再度開きます

USR2: Gunicon をその場でアップグレードします。古いプロセスを終了するには、別の用語シグナルを使用する必要があります。この信号は、プリロードされた新しいバージョンのアプリケーションを使用するためにも使用できます。

Winch: Gunicorn がデーモン化されたときにワーカー プロセスを正常にシャットダウンします。

以上がGunicorn Arbiter のソースコードを分析する方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はyisu.comで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。