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の使用と同期と非同期の使用

推薦する

mysql 簡単な操作例を表示

この記事では、例を挙げて mysql show 操作について説明します。ご参考までに、詳細は以下の通...

WeChatアプレットで数字当てゲームを実装する実際のプロセス

目次機能紹介レンダリング1. ホームページレンダリング用のコード(index03) 2. ゲーム開始...

Dockerコンテナにvimコマンドがない問題を解決する方法

問題を見つける今日、Docker コンテナ内のファイルを変更しようとしたところ、コンテナ内に vim...

MySQL イベント スケジューラに関するよくある話 (必読)

概要MySQL には独自のイベント スケジューラもあり、これは Linux の crontab ジョ...

CentOS 7.5 に Python 3.6.6 を最初からインストールするための詳細なチュートリアル

ps: 環境はタイトル通りです依存関係をインストールする yum インストール openssl-de...

子コンポーネントを通じて親コンポーネントのプロパティを変更するための Vue のさまざまな実装方法

目次序文一般的な方法1. 親コンポーネントを介して子コンポーネントの発行イベントをリッスンしてpro...

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

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

Linux の一般的なハードディスク管理コマンドの紹介

目次1. dfコマンド2. duコマンド3. fsckファイルシステム修復コマンド4. ディスクステ...

MySQL 最適化: キャッシュ最適化

何人かのブロガーが私の記事を評価してくれたのは嬉しいです。マークと知り合ってからは、私は彼をフォロー...

VMware インストール エラー VMware Workstation が VMware 認証サービスを開始できませんでした

背景: SAP ECC サーバーをインストールし、XP をプレインストールしたいと考えています。XP...

Apache Superset を使用して ClickHouse データを視覚化する 2 つの方法

Apache Superset は、データを表示および探索する方法を提供する強力な BI ツールで...

特定のシンボルで複数の行と列に分割するMySQLの例

一部の障害コード テーブルでは、履歴またはパフォーマンス上の理由から、次の設計パターンが使用されます...

HTMLでのラジオ値の取得、割り当て、登録の詳細な説明

1. ラジオのグループ化名前が同じであれば、それらはグループであり、つまり、次のようにグループ内で選...

Zabbixを使用してMySQLを監視する方法

Zabbix 導入ドキュメントzabbix導入後zabbixエージェントの操作1. MySQLを監視...