Nginx における accept lock の仕組みと実装の詳細な説明

Nginx における accept lock の仕組みと実装の詳細な説明

序文

nginx はマルチプロセス モデルを使用します。リクエストが届くと、システムはプロセスをロックして、1 つのプロセスだけがリクエストを受け入れるようにします。

この記事はNginx 0.8.55のソースコードとepollメカニズムの分析に基づいています。

1. 受け入れロックの実装

1.1 アクセプトロックとは何ですか?

Accept Lock について話すとき、Thundering Herd 問題について言及する必要があります。

いわゆる herd-throwing 問題は、Nginx のようなマルチプロセス サーバーに当てはまります。フォーク後に同じポートを同時に listen している場合、外部接続が入ると、休止状態の子プロセスがすべて起動され、最終的に 1 つの子プロセスだけが accept イベントを正常に処理でき、他のプロセスはスリープ状態に戻ります。その結果、多くの不要なスケジュールとコンテキストの切り替えが発生しますが、これらのオーバーヘッドはまったく不要です。

Linux カーネルの新しいバージョンでは、accept 呼び出し自体によって引き起こされる thundering herd 問題は解決されています。ただし、Nginx では、accept は epoll メカニズムによって処理され、epoll の accept によって引き起こされる thundering herd 問題は解決されていません (epoll_wait 自体には、読み取りイベントが Listen ソケットからのものであるかどうかを区別する機能がないため、このイベントを listen しているすべてのプロセスがこの epoll_wait によって起動されるはずです)。そのため、Nginx の accept thundering herd 問題には、依然としてカスタマイズされたソリューションが必要です。

accept ロックは nginx のソリューションです。本質的には、これはプロセス間のミューテックス ロックであり、1 つのプロセスのみが accept イベントをリッスンできることを保証します。

受け入れロックは、実際にはプロセス間ロックです。これは Nginx のグローバル変数であり、次のように宣言されます。

ngx_shmtx_t ngx_accept_mutex;

これは、イベント モジュールが初期化されるときに割り当てられ、すべてのプロセスがこのインスタンスにアクセスできるようにするためにプロセス間の共有メモリに配置されるロックです。ロックとロック解除は、Linux アトミック変数を使用して CAS によって行われます。ロックが失敗すると、すぐに戻ります。これは非ブロッキング ロックです。ロック解除コードは次のとおりです。

静的 ngx_inline ngx_uint_t             
ngx_shmtx_trylock(ngx_shmtx_t *mtx)           
{                    
 戻り値 (*mtx->lock == 0 && ngx_atomic_cmp_set(mtx->lock, 0, ngx_pid));  
}                    
                    
#define ngx_shmtx_lock(mtx) ngx_spinlock((mtx)->lock, ngx_pid, 1024)   
                    
#define ngx_shmtx_unlock(mtx) (void) ngx_atomic_cmp_set((mtx)->lock, ngx_pid, 0)

ngx_shmtx_trylock の呼び出しが失敗した後、ブロックせずにすぐに戻ることがわかります。

1.2 受け入れロックはどのようにして 1 つのプロセスだけが新しい接続を処理できるようにするのでしょうか?

epoll によって発生する accept lock 問題を解決することも非常に簡単です。 accept epoll イベントを同時に登録するプロセスが 1 つだけであることを確認するだけです。
Nginx が採用している処理モードは特別なものではありません。ロジックはおおよそ次のようになります。

受け入れロックを取得しようとする
買収が成功した場合:
epollにacceptイベントを登録する
それ以外:
すべてのイベントを処理して受け入れロックを解除するには、epoll で受け入れイベントを登録解除します。

もちろん、遅延イベントの処理はここでは無視されますが、これについては後で説明します。

受け入れロックの処理と、epoll での受け入れイベントの登録とキャンセルはすべて ngx_trylock_accept_mutex で実行されます。この一連のプロセスは、nginx メイン ループで繰り返し呼び出される void ngx_process_events_and_timers(ngx_cycle_t *cycle) で実行されます。

つまり、各イベント処理ラウンドでは、まず accept ロックを競います。競い合いに成功すると、accept イベントが epoll に登録されます。競い合いに失敗すると、accept イベントは登録解除されます。その後、イベントが処理されると、accept ロックが解放されます。この方法では、1 つのプロセスだけが listen ソケットを listen するため、thundering herd 問題を回避できます。

1.3 イベント処理メカニズムは、受け入れロックが長時間占有されるのを防ぐためにどのような努力を払いますか?

受け入れロックを使用してサンダーリング・ハード問題に対処するという解決策は非常に優れているように見えますが、上記のロジックを完全に使用すると問題が発生します。サーバーが非常にビジーで、処理するイベントが多数ある場合、「すべてのイベントの処理」に非常に長い時間がかかります。つまり、プロセスが受け入れロックを長時間占有し、新しい接続を処理する時間がありません。他のプロセスは受け入れロックを占有せず、新しい接続も処理できません。この時点で、新しい接続は誰も処理していない状態になり、サービスのリアルタイム パフォーマンスにとって間違いなく致命的です。

この問題を解決するために、Nginx はイベント処理を延期します。つまり、ngx_process_events の処理では、イベントは次の 2 つのキューにのみ配置されます。

ngx_thread_volatile ngx_event_t *ngx_posted_accept_events;        
ngx_thread_volatile ngx_event_t *ngx_posted_events;

戻った後、まず ngx_posted_accept_events を処理し、すぐに accept ロックを解除してから、他のイベントをゆっくり処理します。

つまり、ngx_process_events は epoll_wait のみを処理し、イベントの消費は accept ロックが解放された後に配置され、accept を占有する時間を最小限に抑え、他のプロセスが accept イベントを処理するのに十分な時間を確保できるようにします。

では、具体的にはどのように達成されるのでしょうか?実際には、 static ngx_int_t ngx_epoll_process_events(ngx_cycle_t *cycle, ngx_msec_t timer, ngx_uint_t flags)の flags パラメータに NGX_POST_EVENTS フラグを渡し、イベントを処理するときにこのフラグをチェックします。

これは、イベント消費による accept ロックの長期占有を回避するだけなので、epoll_wait 自体に長い時間がかかる場合はどうなるでしょうか?これが起こる可能性はゼロではない。この点の処理も非常に簡単です。epoll_wait 自体にはタイムアウト期間があるため、その値を制限するだけです。このパラメータは、グローバル変数 ngx_accept_mutex_delay に格納されます。

以下は ngx_process_events_and_timers の実装コードです。これにより、関連する処理の概要がわかります。

空所                   
ngx_process_events_and_timers(ngx_cycle_t *サイクル)        
{                    
 ngx_uint_t フラグ;               
 ngx_msec_t タイマー、デルタ;             
    
	
	/* 時間イベントを処理するためのコードを省略*/                    
 // ここで負荷分散ロックを処理し、ロックを受け入れる if (ngx_use_accept_mutex) {            
  // 負荷分散トークンの値が 0 より大きい場合、負荷がいっぱいで accept が処理されなくなったことを意味します。同時に、値は 1 減少します if (ngx_accept_disabled > 0) {           
   ngx_accept_disabled--;            
                    
  } それ以外 {                
   // 受け入れロックを取得しようとする if (ngx_trylock_accept_mutex(cycle) == NGX_ERROR) {    
    戻る;              
   }                 
                    
   // ロックを取得した後、すべてのイベントの処理を延期するためにフラグをポストフラグに追加します // 受け入れロックを長時間占有しないようにします if (ngx_accept_mutex_held) {          
    フラグ |= NGX_POST_EVENTS;          
                    
   } それ以外 {               
    タイマー == NGX_TIMER_INFINITE の場合        
     || タイマー > ngx_accept_mutex_delay)       
    {                
     timer = ngx_accept_mutex_delay; // 受け入れロックが長時間占有されるのを防ぐために、最大で ngx_accept_mutex_delay ミリ秒待機します}                
   }                 
  }                  
 }                    
 デルタ = ngx_current_msec;             
                    
 // イベント処理モジュールの process_events を呼び出して、epoll_wait メソッドを処理します (void) ngx_process_events(cycle, timer, flags);       
                    
 delta = ngx_current_msec - delta; //イベントの処理にかかった時間を計算 ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,       
     "タイマーデルタ: %M", デルタ);         
                    
 // 延期された承諾イベントがある場合は、このイベントの処理を延期します if (ngx_posted_accept_events) {           
  ngx_event_process_posted(サイクル、&ngx_posted_accept_events);   
 }                   
                    
 // accept ロックを解除する if (ngx_accept_mutex_held) {            
  ngx_shmtx_unlock(&ngx_accept_mutex);         
 }                   
                    
 // すべてのタイムアウトイベントを処理する if (delta) {                
  ngx_event_expire_timers();            
 }                   
                    
 ngx_log_debug1(NGX_LOG_DEBUG_EVENT、サイクル->ログ、0、       
     "投稿されたイベント %p", ngx_posted_events);      
                    
 ngx_posted_eventsの場合{             
  ngx_threadedの場合{             
   ngx_wakeup_worker_thread(サイクル);         
                    
  } それ以外 {                
   // すべての遅延イベントを処理する ngx_event_process_posted(cycle, &ngx_posted_events);    
  }                  
 }                   
}

ngx_epoll_process_events の関連処理を見てみましょう。

  // イベントを読み取る if ((revents & EPOLLIN) && rev->active) {
   if ((フラグ & NGX_POST_THREAD_EVENTS) && !rev->accept) {
    rev->posted_ready = 1;

   } それ以外 {
    回転->準備完了 = 1;
   }                                        
   if (フラグ & NGX_POST_EVENTS) {
    キュー = (ngx_event_t **) (rev->accept ?
        &ngx_posted_accept_events : &ngx_posted_events);
    ngx_locked_post_event(rev、キュー);
   } それ以外 {
    rev->ハンドラ(rev);
   }
  }                                         
  wev = c->書き込み;

  // イベントを書き込む if ((revents & EPOLLOUT) && wev->active) {
   if (フラグ & NGX_POST_THREAD_EVENTS) {
    wev->posted_ready = 1;
   } それ以外 {
    wev->準備完了 = 1;
   }

   if (フラグ & NGX_POST_EVENTS) {
    ngx_locked_post_event(wev、&ngx_posted_events);
   } それ以外 {
    wev->ハンドラ(wev);
   }
  }

処理も比較的単純です。受け入れロックが取得されると、NGX_POST_EVENTS フラグが立てられ、対応するキューに配置されます。そうでない場合、イベントは直接処理されます。

要約する

上記はこの記事の全内容です。この記事の内容が皆さんの勉強や仕事に一定の参考学習価値を持つことを願っています。ご質問があれば、メッセージを残してコミュニケーションしてください。123WORDPRESS.COM を応援していただきありがとうございます。

以下もご興味があるかもしれません:
  • proxy_pass を設定した後に Nginx が 404 を返す問題を解決する
  • Nginx SSL証明書設定エラーの解決策
  • Nginx 502 Bad Gateway エラーの原因と解決策
  • nginx の場所に複数の Proxy_pass メソッドがある
  • ファイルをダウンロードするための Nginx 設定サンプルコード
  • リクエストを転送したり、静的リソースファイルにアクセスしたりする複数の場所への nginx の実装
  • nginx 設定ファイルパスとリソースファイルパスを表示する方法
  • nginxプロセスロックの実装の詳細な説明

<<:  MySQLテーブル内の重複データをクエリする方法

>>:  ReactでのsetStateの使用と同期と非同期の使用

推薦する

HTMLページの文字セットを指定する2つの方法

1. HTMLページの文字セットを指定する2つの方法方法1: <メタ文字セット="u...

デザイン参考 WordPressウェブサイト構築成功事例

これら 16 のサイトはそれぞれ注意深く読む価値があり、どのサイトでも推奨されている Web サイト...

要素テーブルテーブルコンポーネントの複数フィールド(複数列)ソート方法

目次必要:発生した問題:解決する:必要:要素テーブル内の複数の列を並べ替えるには、日付の並べ替えをク...

Docker コンテナ アプリケーションで避けるべき 10 の悪い習慣

コンテナが企業の IT インフラストラクチャに欠かせない要素となっていることは間違いありません。コン...

HTML テーブルタグチュートリアル (25): 垂直配置属性 VALIGN

垂直方向では、行の配置を上、中央、下に設定できます。基本的な構文<TR VALIGN=&quo...

CSSブロッキングマージとその他の効果についての簡単な説明

非直交マージンマージンを使用するとマージが発生します次のプロパティはマージンの結合を防止します。国境...

CentOS 7 に PHP5 用の suPHP をインストールする方法 (Peng Ge)

デフォルトでは、CentOS 7 上の PHP は apache または nobody として実行さ...

Windows ホストと Docker コンテナに共有フォルダを設定してマウントする手順

Docker コンテナ内のプログラムは、ホスト ディレクトリ内のデータにアクセスして呼び出す必要があ...

nginx keepaliveの具体的な使い方

http1.1 プロトコルのデフォルトのリクエスト ヘッダーでは、図に示すように、デフォルトで ke...

docker-compose を使用して Clickhouse をすばやくデプロイする方法のチュートリアル

ClickHouse は、オープンソースの列指向 DBMS (Yandex によって開発) です。 ...

DockerでNginxサーバーを作成する方法

動作環境: MAC Docker バージョン: Docker version 17.12.0-ce,...

MySQL のスローログ監視の誤報問題の分析と解決

以前は、さまざまな理由により、一部のアラームは真剣に受け止められませんでした。最近、休暇中に、すぐに...

CSS3は遷移を高速化し、遅延させる

1. 速度制御機能を使用して、トランジション効果(加速、減速など)の速度曲線を制御します。速度制御機...

ul リスト タグ デザイン ウェブ ページ 複数列レイアウト

数日前、CSS で 3 列レイアウトを書いていたときに、突然この方法を思いつきました。このアイデアは...

Vueプロジェクトでvuexを使用する方法

目次Vuex とは何ですか? Vuex 使用サイクル図私のストアディレクトリvuexの例の実装要約す...