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

推薦する

VMware 仮想マシンのネットワークの問題の解決方法

目次1. 問題の説明2. 問題解決1. 仮想マシンシステムのインストール時にネットワークがない場合2...

スタイル属性 (element.style) で定義されたインライン スタイルを削除する方法

Magento を頻繁に変更する場合、element.style に遭遇することがあります。 これは...

nginxの基礎を学ぶ

目次1. nginx とは何ですか? 2. nginx で何ができるのか? 2.1 フォワードプロキ...

Linux で CPU 使用率が高くなる原因をトラブルシューティングするプロセスの詳細な説明

目次序文始めるステップトラブルシューティング序文CPU 使用率が高くなるのは、オンラインでよくある問...

CentOS7.x のアンインストールとインストール MySQL5.7 の操作手順とエンコード形式の変更方法

1. MySQL 5.7 のアンインストール1.1查看yum是否安裝過mysql CD yum li...

innerHTML アプリケーション

ブランクのブログ: http://www.planabc.net/ innerHTML プロパティは...

MySQL データベースで UTF-8 エンコードを設定する方法

/etc/my.cnf または /etc/mysql/my.cnf ファイルを変更する [クライアン...

DockerでMongoDBコンテナをデプロイする方法

目次Dockerとは展開する1. イメージをプルする2. 画像を表示する3. コンテナを実行する4....

Js の継承とプロトタイプチェーンを理解するのに役立つ記事

目次継承とプロトタイプチェーン継承されたプロパティ継承されたメソッドJavaScript でのプロト...

jQuery Ajax チャットボットの実装事例

チャットボットは多くの手作業を省くことができ、顧客サービス、天気予報対応など、さまざまな状況で使用で...

Centos7 で NIS を構成する詳細な手順

目次原理ネットワーク環境の準備インストール前の準備NIS サーバー操作NIS クライアント操作原理N...

CSSレイアウトにおけるフローティング問題に対する4つの解決策の詳細な説明

1. 原因:サブボックスをフロートに設定した後の効果: 青いボックスをフロートに設定すると、標準のド...

HTMLフォームアプリケーションにはチェックボックスとラジオボタンの使用が含まれます

チェックボックスやラジオボタンの使用を含むコードをコピーコードは次のとおりです。 <!DOCT...

現在のMySQL接続数を表示する方法の詳細な説明

1. 現在のすべての接続の詳細情報を表示します。 ./mysqladmin -uadmin -p -...

CentOS6.9+Mysql5.7.18 ソースコードのインストール詳細チュートリアル

CentOS6.9+Mysql5.7.18 ソースコードのインストールでは、以下の操作を root ...