nginx パニック問題の解決方法の詳細な説明

nginx パニック問題の解決方法の詳細な説明

nginx パニック問題に関しては、まず nginx の起動プロセス中に、マスター プロセスが構成ファイルで指定されたポートをリッスンし、次にマスター プロセスが fork() メソッドを呼び出して各子プロセスを作成することを理解する必要があります。プロセスの動作原理によれば、子プロセスは親プロセスのすべてのメモリ データとリッスン ポートを継承します。つまり、ワーカー プロセスも起動後に各ポートをリッスンすることになります。集団ショックとは、クライアントが新しい接続を要求すると、各ワーカー プロセスの接続確立イベントがトリガーされますが、そのイベントを正常に処理できるワーカー プロセスは 1 つだけであり、他のワーカー プロセスはイベントの有効期限が切れたことを検出し、待機状態に戻ることを意味します。イベントによってすべてのワーカー プロセスが「驚愕」するこの現象は、集団驚愕問題と呼ばれます。当然ですが、すべてのワーカー プロセスがトリガーされると、大量のリソースが消費されます。この記事では、主に nginx が herd 問題をどのように処理するかについて説明します。

1. 解決策

前回の記事では、各ワーカー プロセスが作成されると、現在のワーカー プロセスを初期化するために ngx_worker_process_init() メソッドが呼び出されることについて説明しました。このプロセスには非常に重要なステップがあります。つまり、各ワーカー プロセスは epoll_create() メソッドを呼び出して、独自の epoll ハンドルを作成します。リッスンする必要がある各ポートには、対応するファイル記述子があります。ワーカー プロセスは、epoll_ctl() メソッドを通じて現在のプロセスの epoll ハンドルにファイル記述子を追加し、accept イベントをリッスンするだけです。その後、クライアントの接続確立イベントによってトリガーされ、イベントを処理します。また、ワーカー プロセスが、リッスンするポートに対応するファイル記述子をプロセスの epoll ハンドルに追加しない場合、対応するイベントをトリガーできないこともここでわかります。この原則に基づいて、nginx は共有ロックを使用して、現在のプロセスが監視対象のポートを現在のプロセスの epoll ハンドルに追加する権限を持っているかどうかを制御します。つまり、ロックを取得したプロセスのみが対象ポートを監視します。この方法では、イベントが発生するたびに 1 つのワーカー プロセスのみがトリガーされることが保証されます。次の図は、ワーカー プロセスの動作サイクルの概略図を示しています。

図のプロセスについて説明しておく必要があるのは、各ワーカー プロセスがループに入った後に共有ロックを取得しようとすることです。ロックの取得に失敗した場合、監視対象ポートのファイル記述子は、現在のプロセスの epoll ハンドルから削除されます (存在しない場合でも)。これを行う主な目的は、クライアント接続イベントの損失を防ぐことです。これにより、多少のパニック問題が発生する可能性がありますが、深刻ではありません。理論上、現在のプロセスがロックを解放すると、監視対象ポートのファイル記述子が epoll ハンドルから削除されるとします。すると、次のワーカー プロセスがロックを取得するまで、この期間中、各ポートに対応するファイル記述子はどの epoll ハンドルでも監視されず、イベントが失われます。一方、図のようにロック取得に失敗した場合にのみ監視対象のファイル記述子を削除する場合は、ロック取得に失敗するということは、すでにこれらのファイル記述子をリッスンしているプロセスが存在するはずなので、この時点で削除しても安全です。ただし、これによって発生する問題の 1 つは、上の図によると、現在のプロセスがループを完了すると、ロックが解放されて他のイベントが処理されることです。このプロセス中は、監視対象のファイル記述子は解放されないことに注意してください。このとき、別のプロセスがロックを取得してファイル記述子をリッスンしている場合、ファイル記述子をリッスンしているプロセスが 2 つあることになります。そのため、クライアント側で接続確立イベントが発生すると、2 つのワーカー プロセスが起動されます。この問題は、主に次の 2 つの理由により許容可能です。

  1. このとき発生する集団ショック現象は、少数のワーカー プロセスのみをトリガーするため、毎回すべてのワーカー プロセスにショックを与えるよりもはるかに優れています。
  2. この集団パニック問題の主な原因は、現在のプロセスがロックを解放したが、監視対象のファイル記述子を解放しなかったことです。ロックを解放した後、ワーカープロセスは主にクライアント接続の読み取りおよび書き込みイベントを処理し、フラグをチェックします。このプロセスは非常に短く、処理後にロックを取得しようとし、監視対象のファイル記述子を解放します。比較すると、ロックを取得したワーカープロセスはクライアントの接続確立イベントを待つのに時間がかかるため、集団パニック問題が発生する可能性は比較的小さくなります。

2. ソースコードの説明

ワーカー プロセスのイベントを初期化する方法は、主に ngx_process_events_and_timers() メソッドで実行されます。このメソッドがプロセス全体をどのように処理するかを見てみましょう。以下はこのメソッドのソース コードです。

ngx_process_events_and_timers は ngx_cycle_t *cycle を呼び出します。
 ngx_uint_t フラグ;
 ngx_msec_t タイマー、デルタ;

 ngx_trylock_accept_mutex(cycle) == NGX_ERROR の場合 {
  戻る;
 }

 // ここでイベントの処理を開始します。kqueue モデルの場合、ngx_kqueue_process_events() メソッドを指します。
 // epoll モデルの場合、ngx_epoll_process_events() メソッドを指します // このメソッドの主な機能は、対応するイベント モデルのイベント リストを取得し、イベントを ngx_posted_accept_events に追加することです
 // キューまたは ngx_posted_events キュー (void) ngx_process_events(cycle, timer, flags);

 // ここで accept イベントの処理を開始し、ngx_event_accept.c の ngx_event_accept() メソッドに渡します。
 ngx_event_process_posted(サイクル、&ngx_posted_accept_events);

 // ロックの解放を開始する if (ngx_accept_mutex_held) {
  ngx_shmtx_unlock(&ngx_accept_mutex);
 }

 // イベント キューで処理する必要がない場合は、イベントを直接処理します // イベント処理では、accept イベントの場合は、ngx_event_accept.c の ngx_event_accept() メソッドに渡されて処理されます。
 // 読み取りイベントの場合は、ngx_http_request.c の ngx_http_wait_request_handler() メソッドによって処理されます。
 // 処理されたイベントは、最終的には ngx_http_request.c の ngx_http_keepalive_handler() メソッドによって処理されます。

 // イベント受け入れ以外の他のイベントの処理を開始します ngx_event_process_posted(cycle, &ngx_posted_events);
}

上記のコードでは、チェック作業のほとんどを省略し、スケルトンコードのみを残しました。まず、ワーカー プロセスは ngx_trylock_accept_mutex() メソッドを呼び出してロックを取得します。ロックが取得されると、各ポートに対応するファイル記述子をリッスンします。次に、 ngx_process_events() メソッドが呼び出され、 epoll ハンドルで監視されているイベントが処理されます。その後、共有ロックが解放され、最後に接続したクライアントの読み取りおよび書き込みイベントが処理されます。 ngx_trylock_accept_mutex() メソッドが共有ロックを取得する方法を見てみましょう。

ngx_int_t ngx_trylock_accept_mutex(ngx_cycle_t *cycle) {
 // CASアルゴリズムを使用して共有ロックを取得しようとする if (ngx_shmtx_trylock(&ngx_accept_mutex)) {

  // ngx_accept_mutex_held は 1 で、現在のプロセスがロックを取得したことを示します if (ngx_accept_mutex_held && ngx_accept_events == 0) {
   NGX_OK を返します。
  }

  // ここでは、現在の接続のファイル記述子が、kqueue モデルの change_list 配列などの対応するイベントのキューに登録されます。 // nginx が各ワーカー プロセスを有効にすると、デフォルトでは、ワーカー プロセスはマスター プロセスによって監視されるソケット ハンドルを継承します。
  // これにより問題が発生します。つまり、ポートにクライアント イベントが発生すると、そのポートをリッスンしているすべてのプロセスが起動されます。
  // ただし、イベントを正常に処理できるワーカー プロセスは 1 つだけであり、他のプロセスは起動後にイベントの有効期限が切れていることを検出します。
  // そのため、待機状態に入り続けることになります。これを「群集ショック」現象と呼びます。
  // nginx が集団パニック現象を解決する方法は、ここでは共有ロック方式です。つまり、ロックを取得したワーカー プロセスのみがクライアント イベントを処理できますが、実際には、ワーカー プロセスはロックを取得するプロセスで、現在のワーカー プロセスの各ポートのリスニング イベントを再追加します。
  // 他のワーカープロセスは監視しません。つまり、各ポートを同時にリッスンするのは 1 つのワーカー プロセスだけです。
  // これにより、「集団ショック」の問題を回避できます。
  // ここでの ngx_enable_accept_events() メソッドは、現在のプロセスの各ポートのリスニング イベントを再度追加します。
  ngx_enable_accept_events(cycle) == NGX_ERROR の場合 {
   ngx_shmtx_unlock(&ngx_accept_mutex);
   NGX_ERROR を返します。
  }

  // フラグはロックが正常に取得されたことを示します ngx_accept_events = 0;
  ngx_accept_mutex_held = 1;

  NGX_OK を返します。
 }

 // 以前にロックの取得に失敗しました。そのため、ngx_accept_mutex_held の状態をリセットし、現在の接続のイベントをクリアする必要があります。if (ngx_accept_mutex_held) {
  // 現在のプロセスの ngx_accept_mutex_held が 1 の場合、それを 0 にリセットし、各ポートの現在のプロセスの監視イベントを削除します if (ngx_disable_accept_events(cycle, 0) == NGX_ERROR) {
   NGX_ERROR を返します。
  }

  ngx_accept_mutex_held = 0;
 }

 NGX_OK を返します。
}

上記のコードでは、基本的に次の 3 つの処理が行われます。

  1. CAS メソッドを使用して、ngx_shmtx_trylock() メソッドを通じて共有ロックを取得してみてください。
  2. ロックを取得した後、ngx_enable_accept_events() メソッドが呼び出され、ターゲット ポートに対応するファイル記述子をリッスンします。
  3. ロックが取得されない場合は、ngx_disable_accept_events() メソッドを呼び出して、監視対象のファイル記述子を解放します。

3. まとめ

この記事では、まず集団パニック現象の原因を説明し、次に nginx が集団パニック問題を解決する方法を紹介し、最後にソース コードの観点から nginx が集団パニック問題を処理する方法を説明します。

以上がこの記事の全内容です。皆様の勉強のお役に立てれば幸いです。また、123WORDPRESS.COM を応援していただければ幸いです。

<<:  計算機機能を実装するミニプログラム

>>:  MySQL インデックスがソートに与える影響の分析例

推薦する

優れたグラフィックデザイナーが習得すべき7つのスキル

1》ウェブデザインが得意であること2》Webページのデザイン方法を知る3》計画する4. SEOを理解...

Vue3 Reactivityの実装方法を教えます

目次序文始めるちょっとした考えコードの実装真似する実装トラックトリガーの実装観察の実装計算の実装序文...

JS オブジェクトのコピー (ディープ コピーとシャロー コピー)

目次1. 浅いコピー1. Object.assign(ターゲット、ソース、ソース...) 2. スプ...

Ubuntu 19でdockerソースをインストールできない問題を共有する

主要な Web サイトと個人的な習慣に従って、Docker ソースを追加するには次の方法を使用します...

Docker での Redis の最も詳細なインストールと構成 (画像とテキスト付き)

1. Dockerに適したRedisのバージョンを見つけるdocker hubで見つけることができ...

vue3 のコンポーネントの互換性のない変更の詳細な説明

目次機能コンポーネント非同期コンポーネントの書き方とdefineAsyncComponentメソッド...

Docker を使って LEMP 環境を素早く構築する方法の例

LEMP(Linux + Nginx + MySQL + PHP)は、基本的に今日のWeb開発者にと...

Nginx プロセス管理とリロードの原則の詳細な説明

プロセス構造図Nginx はマルチプロセス構造です。マルチプロセス構造は、次のような Nginx の...

Vue.js フロントエンドフレームワークにおけるイベント処理の概要

1. v-onイベント監視DOM イベントをリッスンするには、v-on ディレクティブを使用します。...

ウェブサイトがhttpsを有効にした後のSSLのセキュリティ構成と検出

最近のウェブサイトでは SSL を有効にするのが標準となっています。ただし、SSL を設定した後も、...

例によるMySql CURRENT_TIMESTAMP関数の分析

時間フィールドを作成するときデフォルトのCURRENT_TIMESTAMPデータを挿入する際、このフ...

MySQL エラー コード 1862 の解決方法: パスワードの有効期限が切れています

ブロガーは 1 ~ 2 か月間 MySQL を使用していませんでしたが、今日この問題に遭遇しました。...

よく知られているブラウザのDOCTYPEモード選択メカニズム

ドキュメントの範囲この記事では、Firefox やその他の Gecko ベースのブラウザ、Safar...

要素複数フォーム検証の実装

プロジェクトでは、フォーム テストが頻繁に発生します。単一のフォーム テストについては、詳細な紹介が...

変換を使用して純粋な CSS ポップアップ メニューを実装するためのサンプル コード

序文トップメニューを作成する場合、ポップアップのセカンダリメニューを作成する必要があります。 以前の...