Nginx ソースコード調査における nginx 電流制限モジュールの詳細な説明

Nginx ソースコード調査における nginx 電流制限モジュールの詳細な説明

高並行性システムには、キャッシュ、ダウングレード、電流制限という 3 つの強力なツールがあります。

レート制限の目的は、同時アクセス/リクエストのレートを制限することでシステムを保護することです。レート制限に達すると、システムはサービスを拒否 (エラー ページに直接移動)、キューイング (フラッシュ セール)、またはダウングレード (バックアップ データまたはデフォルト データを返す) することができます。

現在、高同時実行システムで一般的に使用されている制限方法には、同時接続の合計数を制限すること (データベース接続プール)、瞬間同時接続数を制限すること (瞬間同時接続数を制限するために使用される nginx の limit_conn モジュールなど)、および時間枠内の平均レートを制限すること (1 秒あたりの平均レートを制限するために使用される nginx の limit_req モジュール) などがあります。

さらに、ネットワーク接続数、ネットワークトラフィック、CPU またはメモリ負荷などに基づいてフローを制限することもできます。

1. 電流制限アルゴリズム

最も単純かつ最も粗雑な電流制限アルゴリズムはカウンター方式であり、より一般的に使用されるのはリーキー バケット アルゴリズムとトークン バケット アルゴリズムです。

1.1 カウンター

カウンター方式は、電流制限アルゴリズムを実装する最もシンプルで簡単な方法です。たとえば、インターフェース A の場合、1 分あたりの訪問回数は 100 を超えてはならないと規定します。

次に、有効期間が 1 分のカウンターを設定します (つまり、カウンターは 1 分ごとに 0 にリセットされます)。リクエストが来るたびに、カウンターは 1 ずつ増加します。カウンターの値が 100 より大きい場合は、リクエストの数が多すぎることを意味します。

このアルゴリズムは単純ですが、非常に致命的な問題、つまり重大な問題があります。

下の図に示すように、1:00 直前に 100 件のリクエストが到着し、1:00 にカウンターがリセットされ、1:00 直後に 100 件のリクエストが到着します。当然、カウンターは 100 を超えることはなく、すべてのリクエストがブロックされることはありません。

しかし、この期間のリクエスト数は100件をはるかに超える200件に達しました。

1.2 リーキーバケットアルゴリズム

下の図に示すように、容量が固定された漏れバケツがあり、水滴は一定の固定速度で流れ出ます。バケツが空の場合、水滴は流れ出ません。漏れバケツに流入する水の速度は任意です。流入する水がバケツの容量を超えると、流入した水は溢れます(廃棄されます)。

リーキー バケット アルゴリズムは本質的に要求レートを制限し、トラフィック シェーピングや電流制限制御に使用できることがわかります。

1.3 トークンバケットアルゴリズム

トークン バケットは、固定容量のトークンを格納するバケットです。トークンは固定レート r でバケットに追加されます。バケットには最大 b 個のトークンを格納できます。バケットがいっぱいになると、新しく追加されたトークンは破棄されます。

リクエストが到着すると、バケットからトークンを取得しようとします。トークンがある場合は、リクエストの処理を続行します。トークンがない場合は、キューに入れられるか、直接破棄されます。

リーキーバケットアルゴリズムの流出率は一定または 0 であるのに対し、トークンバケットアルゴリズムの流出率は r より大きい場合があることがわかります。

2. nginxの基礎知識

Nginx には現在、接続数による制限 (ngx_http_limit_conn_module) とリクエスト レートによる制限 (ngx_http_limit_req_module) という 2 つの主な制限方法があります。

現在の制限モジュールを学習する前に、nginx の HTTP リクエストの処理、nginx イベント処理フローなども理解する必要があります。

2.1HTTPリクエスト処理

Nginx は、HTTP リクエスト処理フローを 11 のステージに分割します。ほとんどの HTTP モジュールは、特定のステージに独自のハンドラーを追加します (そのうち 4 つはカスタム ハンドラーを追加できません)。HTTP リクエストを処理するとき、Nginx はすべてのハンドラーを 1 つずつ呼び出します。

typedef列挙型{
 NGX_HTTP_POST_READ_PHASE = 0, // 現在、realip モジュールのみがハンドラを登録します (nginx がプロキシ サーバーとして使用されている場合に便利です。バックエンドはこれを使用してクライアントの元の IP を取得します)
 
 NGX_HTTP_SERVER_REWRITE_PHASE、//URLを書き換えるためにサーバブロックで書き換えディレクティブが設定されている
 
 NGX_HTTP_FIND_CONFIG_PHASE、//一致する場所を検索します。ハンドラーはカスタマイズできません。
 NGX_HTTP_REWRITE_PHASE、// URLを書き換えるために、ロケーションブロックにrewriteディレクティブが設定されている
 NGX_HTTP_POST_REWRITE_PHASE、// URL の書き換えが発生したかどうかを確認します。発生した場合は、FIND_CONFIG フェーズに戻ります。ハンドラーはカスタマイズできません。
 
 NGX_HTTP_PREACCESS_PHASE、//アクセス制御。現在の制限モジュールは、このステージにハンドラーを登録します。NGX_HTTP_ACCESS_PHASE、//アクセス権制御。NGX_HTTP_POST_ACCESS_PHASE、//アクセス権制御ステージに応じて、それに応じて処理します。ハンドラーはカスタマイズできません。
 
 NGX_HTTP_TRY_FILES_PHASE、//このフェーズは try_files ディレクティブが設定されている場合にのみ発生します。ハンドラーはカスタマイズできません。
 NGX_HTTP_CONTENT_PHASE、//コンテンツ生成フェーズ、クライアントに応答を返す NGX_HTTP_LOG_PHASE //ログ レコード} ngx_http_phases;

Nginx はモジュールを表すために構造体 ngx_module_s を使用します。ここで、フィールド ctx はモジュール コンテキスト構造体へのポインターです。nginx の HTTP モジュール コンテキスト構造体は次のとおりです (コンテキスト構造体のフィールドはすべて関数ポインターです)。

typedef構造体{
 ngx_int_t (*事前設定)(ngx_conf_t *cf);
 ngx_int_t (*postconfiguration)(ngx_conf_t *cf); //このメソッドは、対応するステージにハンドラーを登録します void *(*create_main_conf)(ngx_conf_t *cf); //http ブロック内のメイン構成 char *(*init_main_conf)(ngx_conf_t *cf, void *conf);
 
 void *(*create_srv_conf)(ngx_conf_t *cf); //サーバー構成 char *(*merge_srv_conf)(ngx_conf_t *cf, void *prev, void *conf);
 
 void *(*create_loc_conf)(ngx_conf_t *cf); //場所の設定 char *(*merge_loc_conf)(ngx_conf_t *cf, void *prev, void *conf);
} ngx_http_module_t;

ngx_http_limit_req_module モジュールを例にとると、postconfiguration メソッドは次のように簡単に実装されます。

静的 ngx_int_t ngx_http_limit_req_init(ngx_conf_t *cf)
{
 h = ngx_array_push(&cmcf->phases[NGX_HTTP_PREACCESS_PHASE].handlers);
 
 *h = ngx_http_limit_req_handler; // ngx_http_limit_req_module モジュールの現在の制限方法。nginx が HTTP リクエストを処理するときに、このメソッドを呼び出して、リクエストを続行するか拒否するかを決定します。 return NGX_OK;
}

2.2 nginxイベント処理の簡単な紹介

nginx が epoll を使用すると仮定します。

Nginx は、関係するすべての fd を epoll に登録し、次のようにメソッド宣言を追加する必要があります。

静的 ngx_int_t ngx_epoll_add_event(ngx_event_t *ev、ngx_int_t イベント、ngx_uint_t フラグ);

メソッドの最初のパラメータは、関心のある読み取りまたは書き込みイベントを表す ngx_event_t 構造体へのポインタです。nginx は、イベント タイムアウトを処理するためにイベントのタイムアウト タイマーを設定する場合があります。定義は次のとおりです。

構造体ngx_event_s {
 
 ngx_event_handler_pt handler; //関数ポインタ: イベント処理関数 ngx_rbtree_node_t timer; //タイムアウトタイマー、赤黒木に格納されます (ノードのキーはイベントのタイムアウトです)
 
 unsigned timedout:1; //イベントがタイムアウトしたかどうかを記録します};

通常、epoll_wait はループ内で呼び出され、すべての fd を監視し、発生する読み取りおよび書き込みイベントを処理します。epoll_wait はブロッキング呼び出しです。最後のパラメータ timeout はタイムアウト期間です。最大ブロッキング時間内にイベントが発生しない場合、メソッドは戻ります。

タイムアウトを設定すると、nginx は上記のタイムアウト タイマーの赤黒ツリーから期限が切れる最も近いノードを検索し、次のコードに示すように、それを epoll_wait のタイムアウトとして使用します。

ngx_msec_t ngx_event_find_timer(void)
{
 ノード = ngx_rbtree_min(ルート、センチネル);
 タイマー = (ngx_msec_int_t) (node->key - ngx_current_msec);
 
 (ngx_msec_t) を返します (タイマー > 0 ? タイマー: 0);
}

同時に、各ループの終了時に、nginx は赤黒ツリーから期限切れのイベントがあるかどうかを確認します。期限切れの場合は、timeout=1 とマークし、イベント ハンドラーを呼び出します。

ngx_event_expire_timers は void です。
{
 のために ( ;; ) {
  ノード = ngx_rbtree_min(ルート、センチネル);
 
  if ((ngx_msec_int_t) (node->key - ngx_current_msec) <= 0) { //現在のイベントがタイムアウトしました ev = (ngx_event_t *) ((char *) node - offsetof(ngx_event_t, timer));
 
   ev->タイムアウト = 1;
 
   ev->ハンドラ(ev);
 
   続く;
  }
 
  壊す;
 }
}

Nginx は上記の方法を使用して、ソケット イベントと時間指定イベントの処理を実装します。

ngx_http_limit_req_module モジュール分析

ngx_http_limit_req_module モジュールはリクエスト レートを制限します。つまり、一定期間内のユーザーのリクエスト レートを制限し、トークン バケット アルゴリズムを使用します。

3.1 設定手順

ngx_http_limit_req_moduleモジュールは、ユーザーが現在の制限戦略を設定するための次の設定手順を提供します。

//各設定ディレクティブには主に2つのフィールドが含まれます: 名前、解析設定処理メソッド static ngx_command_t ngx_http_limit_req_commands[] = {
 
 //一般的な使用法: limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;
 //$binary_remote_addr はリモート クライアントの IP を表します。
 //zone はストレージ スペースを構成します (各クライアントのアクセス レートを記録するためにスペースを割り当てる必要があり、タイムアウト スペース制限は lru アルゴリズムを使用して排除されます。このスペースは共有メモリに割り当てられ、すべてのワーカー プロセスからアクセスできることに注意してください)
 //rate はレート制限を示します。この場合は 1qps です。
 { ngx_string("limit_req_zone"),
  ngx_http_limit_req_zone、
  },
 
 //使用法: limit_req zone=one burst=5 nodelay;
 //ゾーンは使用する共有スペースを指定します//このレートを超えるリクエストは直接破棄されますか?バースト設定はバースト トラフィックを処理するために使用されます。キューに入れられるリクエストの最大数を示します。クライアントのリクエスト レートが現在の制限を超えると、リクエストはキューに入れられます。バーストを超えるリクエストのみが直接拒否されます。
 //nodelay は burst と一緒に使用する必要があります。この時点で、キューに入れられたリクエストが最初に処理されます。そうでない場合、これらのリクエストが制限されたレートで処理され続けると、サーバーが処理を完了するまでにクライアントがタイムアウトする可能性があります { ngx_string("limit_req"),
  ngx_http_limit_req、
  },
 
 // リクエストが制限されている場合のログ記録レベル。使用法: limit_req_log_level info | notice | warn | error;
 { ngx_string("limit_req_log_level"),
  ngx_conf_set_enum_slot、
  },
 
 // リクエストが制限されている場合、クライアントに返されるステータス コード。使用法: limit_req_status 503
 { ngx_string("limit_req_status"),
  ngx_conf_set_num_slot、
 },
};

注: $binary_remote_addr は nginx によって提供される変数であり、設定ファイルで直接使用できます。また、nginx は多くの変数も提供しており、ngx_http_variable.c ファイルの ngx_http_core_variables 配列で見つけることができます。

静的 ngx_http_variable_t ngx_http_core_variables[] = {
 
 { ngx_string("http_host"), NULL, ngx_http_variable_header,
  offsetof(ngx_http_request_t, headers_in.host), 0, 0 },
 
 { ngx_string("http_user_agent"), NULL, ngx_http_variable_header,
  offsetof(ngx_http_request_t, headers_in.user_agent), 0, 0 },
 …………
}

3.2 ソースコード分析

ngx_http_limit_req_module は、構成後のプロセス中に、ngx_http_limit_req_handler メソッドを HTTP 処理の NGX_HTTP_PREACCESS_PHASE フェーズに登録します。

ngx_http_limit_req_handler は、リーキー バケット アルゴリズムを実行して、設定された現在の制限レートを超えているかどうかを判断し、破棄、キュー、またはパスします。

ユーザーが最初のリクエストを行うと、新しいレコードが追加され(主にアクセス回数とアクセス時間を記録)、クライアント IP アドレスのハッシュ値(構成 $binary_remote_addr)がキーとして使用され、赤黒木(クイック検索用)と LRU キュー(ストレージスペースが不足している場合はレコードが排除され、そのたびに末尾から削除されます)に保存されます。ユーザーが次のリクエストを行うと、レコードは赤黒木で検索されて更新され、レコードは LRU キューの先頭に移動されます。

3.2.1 データ構造

limit_req_zone は、現在の制限アルゴリズムに必要なストレージ スペース (名前とサイズ)、制限速度、制限変数 (クライアント IP など) を構成します。構造は次のとおりです。

typedef構造体{
 ngx_http_limit_req_shctx_t *sh;
 ngx_slab_pool_t *shpool; //メモリプール ngx_uint_t rate; //現在の制限速度(ストレージ用にqpsを1000倍したもの)
 ngx_int_t index; //変数インデックス(nginxは一連の変数を提供しますが、現在の制限変数インデックスはユーザーが設定します)
 ngx_str_t var; //現在の制限変数名 ngx_http_limit_req_node_t *node;
} ngx_http_limit_req_ctx_t;
 
//同時に、共有ストレージスペース struct ngx_shm_zone_s {
 void *data; //data は ngx_http_limit_req_ctx_t 構造体を指します ngx_shm_t shm; //共有スペース ngx_shm_zone_init_pt init; //初期化メソッド関数ポインター void *tag; //ngx_http_limit_req_module 構造体を指します };

limit_req は、電流制限に使用するストレージスペース、キューのサイズ、緊急処理するかどうかを設定します。構造は次のとおりです。

typedef構造体{
 ngx_shm_zone_t *shm_zone; //共有ストレージスペース ngx_uint_t burst; //キューサイズ ngx_uint_t nodelay; //キューにリクエストがある場合に緊急に処理するかどうか、バーストで使用される (設定されている場合、キューに入れられたリクエストは緊急に処理されます。そうでない場合は、現在の制限速度に従って処理されます)
} ngx_http_limit_req_limit_t; 

前述のように、ユーザー アクセス レコードは赤黒木と LRU キューの両方に保存されます。構造は次のとおりです。

//レコード構造 typedef struct {
 u_char 色;
 u_char ダミー;
 u_short len; //データ長 ngx_queue_t queue; 
 ngx_msec_t last; //最終アクセス時間 ngx_uint_t extra; //現在処理が残っているリクエスト数(nginxはこれを使用してトークンバケットの現在の制限アルゴリズムを実装します)
 ngx_uint_t count; //そのようなレコード要求の合計数 u_char data[1]; //データ内容(最初にキー(ハッシュ値)で検索し、次にデータ内容が等しいかどうかを比較します)
} ngx_http_limit_req_node_t;
 
//赤黒木ノード、キーはユーザーが設定した現在の制限変数のハッシュ値です。
構造体ngx_rbtree_node_s {
 ngx_rbtree_key_t キー;
 ngx_rbtree_node_t *左;
 ngx_rbtree_node_t *右;
 ngx_rbtree_node_t *親;
 u_char 色;
 u_char データ;
};
 
 
typedef構造体{
 ngx_rbtree_t rbtree; //赤黒木 ngx_rbtree_node_t sentinel; //NIL ノード ngx_queue_t queue; //LRU キュー } ngx_http_limit_req_shctx_t;
 
//キューには前と次のポインターのみが含まれます struct ngx_queue_s {
 ngx_queue_t *前;
 ngx_queue_t *次へ;
};

考え方 1: ngx_http_limit_req_node_t レコードは、prev ポインターと next ポインターを介して双方向のリンク リストを形成し、LRU キューを実装します。最後にアクセスされたノードは常にリンク リストの先頭に挿入され、ノードが削除されると末尾から削除されます。

ngx_http_limit_req_ctx_t *ctx;
ngx_queue_t *q;
 
q = ngx_queue_last(&ctx->sh->queue);
 
lr = ngx_queue_data(q, ngx_http_limit_req_node_t, queue);//このメソッドは、ngx_queue_t から ngx_http_limit_req_node_t 構造体の最初のアドレスを取得し、次のように実装されます。
 
#define ngx_queue_data(q, type, link) (type *) ((u_char *) q - offsetof(type, link)) //キューフィールドのアドレスから構造体内のオフセットを引いたものが構造体の最初のアドレスになります

考え 2: 現在の制限アルゴリズムは、まずキーを使用して赤黒木ノードを見つけ、対応するレコードを見つけます。赤黒木ノードは、レコード ngx_http_limit_req_node_t 構造とどのように関連付けられるのでしょうか。 ngx_http_limit_req_module モジュールには、次のコードがあります。

size = offsetof(ngx_rbtree_node_t, color) // 新しいレコードにメモリを割り当て、必要なスペースを計算します size + offsetof(ngx_http_limit_req_node_t, data)
  + 長さ;
 
ノード = ngx_slab_alloc_locked(ctx->shpool, サイズ);
 
ノード->キー = ハッシュ;
 
lr = (ngx_http_limit_req_node_t *) &node->color; //color は u_char 型ですが、なぜ強制的に ngx_http_limit_req_node_t ポインタ型に変換できるのでしょうか?
 
lr->len = (u_char)len;
lr->過剰 = 0;
 
ngx_memcpy(lr->data, データ, len);
 
ngx_rbtree_insert(&ctx->sh->rbtree, ノード);
 
ngx_queue_insert_head(&ctx->sh->queue, &lr->queue);

上記のコードを分析すると、ngx_rbtree_node_s 構造体の color フィールドと data フィールドは実際には意味がありません。構造体のライフ フォームは最終的なストレージ フォームとは異なります。Nginx は最終的に次のストレージ フォームを使用して各レコードを保存します。

3.2.2 電流制限アルゴリズム

前述のように、ngx_http_limit_req_handler メソッドは、構成後のプロセス中に HTTP 処理の NGX_HTTP_PREACCESS_PHASE フェーズに登録されます。

したがって、HTTP リクエストを処理するときに、ngx_http_limit_req_handler メソッドが実行され、現在の制限が必要かどうかが判断されます。

3.2.2.1 リーキーバケットアルゴリズムの実装

ユーザーは複数の電流制限を同時に設定できます。したがって、HTTP リクエストの場合、nginx はすべての電流制限ポリシーを調べて、電流制限が必要かどうかを判断する必要があります。

ngx_http_limit_req_lookup メソッドは、リーキー バケット アルゴリズムを実装し、次の 3 つの結果を返します。

  • NGX_BUSY: リクエスト レートが現在の制限構成を超えているため、リクエストは拒否されます。
  • NGX_AGAIN: リクエストは現在の電流制限戦略の検証に合格し、次の電流制限戦略の検証を続行します。
  • NGX_OK: リクエストはすべての現在の制限戦略の検証に合格し、次のステージに進むことができます。
  • NGX_ERROR: エラー
//limit、現在の制限戦略。hash、キーのハッシュ値を記録。data、キーのデータ内容を記録。len、キーのデータ長を記録。ep、保留中のリクエストの数。account、これが最後の現在の制限戦略であるかどうか static ngx_int_t ngx_http_limit_req_limit_t *limit、ngx_uint_t hash、u_char *data、size_t len、ngx_uint_t *ep、ngx_uint_t account)
{
 //指定された境界の赤黒木検索 while (node ​​!= sentinel) {
 
  if (ハッシュ < ノード-> キー) {
   ノード = ノード->左;
   続く;
  }
 
  if (ハッシュ > ノード->キー) {
   ノード = ノード->right;
   続く;
  }
 
  //ハッシュ値が等しいか、データが等しいか比較します lr = (ngx_http_limit_req_node_t *) &node->color;
 
  rc = ngx_memn2cmp(データ、lr->data、len、(size_t) lr->len);
  // (rc == 0) かどうかを調べる {
   ngx_queue_remove(&lr->queue);
   ngx_queue_insert_head(&ctx->sh->queue, &lr->queue); //レコードをLRUキューの先頭に移動します ms = (ngx_msec_int_t) (now - lr->last); //現在の時刻から最後のアクセス時刻を引いた値 extrasecess = lr->excess - ctx->rate * ngx_abs(ms) / 1000 + 1000; //処理対象のリクエスト - 現在の制限レート * 期間 + 1 リクエスト (レート、リクエスト数などは 1000 倍になります)
 
   (超過<0)の場合{
    超過 = 0;
   }
 
   *ep = 過剰;
 
   // 保留中のリクエストの数がバースト(待機キューのサイズ)を超え、リクエストを拒否するために NGX_BUSY が返されます(バーストが設定されていない場合、値は 0 です)
   if ((ngx_uint_t) 超過 > 制限->バースト) {
    NGX_BUSY を返します。
   }
 
   if (account) { //これが現在の最後の制限ポリシーである場合、最終アクセス時刻と保留中のリクエストの数を更新し、NGX_OKを返します。
    lr->excess = 過剰;
    lr->last = 今;
    NGX_OK を返します。
   }
   //訪問回数を増やす lr->count++;
 
   ctx->node = lr;
 
   return NGX_AGAIN; // 最後の電流制限戦略ではないので、NGX_AGAIN を返し、次の電流制限戦略のチェックを続行します}
 
  ノード = (rc < 0) ? ノード->left : ノード->right;
 }
 
 //ノードが見つからない場合は、新しいレコードを作成する必要があります *ep = 0;
 //ストレージスペースサイズの計算方法については、セクション3.2.1のデータ構造を参照してください。size = offsetof(ngx_rbtree_node_t, color)
   + offsetof(ngx_http_limit_req_node_t、データ)
   + 長さ;
 //レコードの削除を試みる (LRU)
 ngx_http_limit_req_expire(ctx, 1);
 
  
 node = ngx_slab_alloc_locked(ctx->shpool, size); //スペースを割り当てる if (node ​​== NULL) { //スペースが不足しているため、割り当てに失敗しました ngx_http_limit_req_expire(ctx, 0); //レコードの強制削除 node = ngx_slab_alloc_locked(ctx->shpool, size); //スペースを割り当てる if (node ​​== NULL) { //割り当てに失敗しました。NGX_ERRORを返します
   NGX_ERROR を返します。
  }
 }
 
 node->key = hash; //値を割り当てる lr = (ngx_http_limit_req_node_t *) &node->color;
 lr->len = (u_char)len;
 lr->過剰 = 0;
 ngx_memcpy(lr->data, データ, len);
 
 ngx_rbtree_insert(&ctx->sh->rbtree, node); //赤黒木とLRUキューにレコードを挿入します ngx_queue_insert_head(&ctx->sh->queue, &lr->queue);
 
 if (account) { //これが現在の最後の制限ポリシーである場合、最終アクセス時刻と保留中のリクエストの数を更新し、NGX_OKを返します。
  lr->last = 今;
  lr->カウント = 0;
  NGX_OK を返します。
 }
 
 lr->last = 0;
 lr->カウント = 1;
 
 ctx->node = lr;
 
 return NGX_AGAIN; // 最後の電流制限戦略ではないので、NGX_AGAIN を返し、次の電流制限戦略のチェックを続行します}

たとえば、バーストが0に設定されている場合、保留中のリクエストの数は最初は超過しており、トークン生成期間はTです。次の図に示すように

3.2.2.2LRU除去戦略

ペインノッキング アルゴリズムの前のセクションでは、レコードを削除するために ngx_http_limit_req_expire が実行され、そのたびに LRU キューの最後からレコードが削除されます。

2 番目のパラメータ n は、n==0 の場合、最後のレコードを強制的に削除し、次に 1 つまたは 2 つのレコードを削除しようとします。n==1 の場合、1 つまたは 2 つのレコードを削除しようとします。コード実装は次のとおりです。

静的 void ngx_http_limit_req_expire(ngx_http_limit_req_ctx_t *ctx, ngx_uint_t n)
{
 // 最大 3 つのレコードを削除します while (n < 3) {
  // 末尾ノード q = ngx_queue_last(&ctx->sh->queue);
  //レコードを取得 lr = ngx_queue_data(q, ngx_http_limit_req_node_t, queue);
   
  //注: n が 0 の場合、if コード ブロックに入ることができないため、末尾のノードが削除されます。n が 0 でない場合は、if コード ブロックに入り、削除できるかどうかを確認します。if (n++ != 0) {
 
   ms = (ngx_msec_int_t) (現在 - lr->last);
   ms = ngx_abs(ms);
   // 短時間でアクセスされたため削除できず、直接戻ります if (ms < 60000) {
    戻る;
   }
    
   // 削除できない保留中のリクエストがあります。直接返します。excess = lr->excess - ctx->rate * ms / 1000;
   (超過>0)の場合{
    戻る;
   }
  }
 
  // ngx_queue_remove(q) を削除します。
 
  ノード = (ngx_rbtree_node_t *)
     ((u_char *) lr - offsetof(ngx_rbtree_node_t, color));
 
  ngx_rbtree_delete(&ctx->sh->rbtree、ノード);
 
  ngx_slab_free_locked(ctx->shpool、ノード);
 }
}

3.2.2.3 バースト実装

バーストは突然のトラフィックに対処するためのものです。突然のトラフィックが到着した場合、サーバーはより多くのリクエストを処理できるようにする必要があります。

バーストが 0 の場合、現在の制限を超える要求は拒否されます。バーストが 0 より大きい場合、現在の制限を超える要求は直接拒否されるのではなく、処理のためにキューに入れられます。

キューイングプロセスはどのように実装されますか?また、nginx はキューに入れられたリクエストを定期的に処理する必要もあります。

セクション 2.2 では、各イベントにタイマーがあることを説明しました。Nginx は、イベントとタイマーを使用して、リクエストのキューイングとタイミング処理を実装します。

ngx_http_limit_req_handler メソッドには次のコードがあります。

//現在のリクエストが処理されるまでに待機する必要がある時間を計算します。delay = ngx_http_limit_req_account(limits, n, &excess, &limit);

//読み取り可能なイベントを追加します if (ngx_handle_read_event(r->connection->read, 0) != NGX_OK) {
 NGX_HTTP_INTERNAL_SERVER_ERROR を返します。
}

r->read_event_handler = ngx_http_test_reading;
r->write_event_handler = ngx_http_limit_req_delay; //書き込み可能なイベント処理関数 ngx_add_timer(r->connection->write, delay); //書き込み可能なイベント追加タイマー(タイムアウト前にクライアントに戻ることはできません)

遅延を計算する方法は非常に簡単で、現在のすべての制限戦略を走査し、保留中のすべての要求を処理するために必要な時間を計算し、最大値を返します。

if (limits[n].nodelay) { //nodelayが設定されている場合、リクエストは遅延されず、遅延は0になります
 続く;
}
 
遅延 = 超過 * 1000 / ctx->rate;
 
(遅延>最大遅延)の場合{
 max_delay = 遅延;
 *ep = 過剰;
 *制限 = &limits[n];
}

書き込み可能なイベント処理関数 ngx_http_limit_req_delay の実装を簡単に見てみましょう。

静的 void ngx_http_limit_req_delay(ngx_http_request_t *r)
{
 
 wev = r->接続->書き込み;
 
 if (!wev->timedout) { //タイムアウトなし、処理なし if (ngx_handle_write_event(wev, 0) != NGX_OK) {
   ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
  }
 
  戻る;
 }
 
 wev->タイムアウト = 0;
 
 r->read_event_handler = ngx_http_block_reading;
 r->write_event_handler = ngx_http_core_run_phases;
 
 ngx_http_core_run_phases(r); //タイムアウト、HTTPリクエストの処理を続行}

4. 実戦

4.1 通常の電流制限のテスト

1) 次のように、レートを 1qps に制限し、クライアント IP アドレスに基づいてレートを制限するように nginx を設定します (デフォルトの戻りステータス コードは 503 です)。

http{
 limit_req_zone $binary_remote_addr ゾーン=テスト:10m レート=1r/s;
 
 サーバー{
  聞く 80;
  server_name ローカルホスト;
  位置 / {
   limit_req ゾーン=テスト;
   ルートhtml;
   インデックス index.html index.htm;
  }
}

2) 複数のリクエストを同時に開始します。3) サーバーのアクセス ログを確認すると、22 秒以内に 3 つのリクエストが到着しますが、処理されるのは 1 つのリクエストのみであり、23 秒以内に 2 つのリクエストが到着し、最初のリクエストが処理され、2 番目のリクエストが拒否されていることがわかります。

xx.xx.xx.xxx - - [2018/09/22:23:33:22 +0800] "GET / HTTP/1.0" 200 612 "-" "ApacheBench/2.3"
xx.xx.xx.xxx - - [2018/09/22:23:33:22 +0800] "GET / HTTP/1.0" 503 537 "-" "ApacheBench/2.3"
xx.xx.xx.xxx - - [2018/09/22:23:33:22 +0800] "GET / HTTP/1.0" 503 537 "-" "ApacheBench/2.3"
xx.xx.xx.xxx - - [2018/09/22:23:33:23 +0800] "GET / HTTP/1.0" 200 612 "-" "ApacheBench/2.3"
xx.xx.xx.xxx - - [2018/09/22:23:33:23 +0800] "GET / HTTP/1.0" 503 537 "-" "ApacheBench/2.3"

4.2 テストバースト

1) 速度制限が 1qps の場合、制限を超えるリクエストは直接拒否されます。バースト トラフィックに対処するには、リクエストをキューに入れて処理できるようにする必要があります。そのため、burst=5 が設定されています。これは、最大 5 つのリクエストをキューに入れて処理できるようにすることを意味します。

http{
 limit_req_zone $binary_remote_addr ゾーン=テスト:10m レート=1r/s;
 
 サーバー{
  聞く 80;
  server_name ローカルホスト;
  位置 / {
   limit_req ゾーン=テストバースト=5;
   ルートhtml;
   インデックス index.html index.htm;
  }
}

2) ab を使用して 10 件のリクエストを同時に開始します (ab -n 10 -c 10 http://xxxxx)。

3) サーバー アクセス ログを確認します。ログによると、最初のリクエストは処理され、リクエスト 2 ~ 5 は拒否され、リクエスト 6 ~ 10 は処理されていますが、なぜこのような結果になるのでしょうか。

ngx_http_log_module をチェックし、ハンドラーを NGX_HTTP_LOG_PHASE フェーズ (HTTP リクエスト処理の最後のフェーズ) に登録します。

したがって、実際の状況は次のようになります。10 件のリクエストが同時に到着し、最初のリクエストは直接処理され、2 番目から 6 番目のリクエストが到着してキューに入れられ、処理されます (1 秒あたり 1 つのリクエストが処理されます)。7 番目から 10 番目のリクエストは直接拒否されるため、アクセス ログが最初に印刷されます。

2 番目から 6 番目のリクエストは 1 秒ごとに 1 つ処理され、処理後にアクセス ログが出力されます。つまり、49 秒から 53 秒までは 1 秒ごとに 1 つのリクエストが処理されます。

xx.xx.xx.xxx - - [2018/09/22:23:41:48 +0800] "GET / HTTP/1.0" 200 612 "-" "ApacheBench/2.3"
xx.xx.xx.xxx - - [2018/09/22:23:41:48 +0800] "GET / HTTP/1.0" 503 537 "-" "ApacheBench/2.3"
xx.xx.xx.xxx - - [2018/09/22:23:41:48 +0800] "GET / HTTP/1.0" 503 537 "-" "ApacheBench/2.3"
xx.xx.xx.xxx - - [2018/09/22:23:41:48 +0800] "GET / HTTP/1.0" 503 537 "-" "ApacheBench/2.3"
xx.xx.xx.xxx - - [2018/09/22:23:41:48 +0800] "GET / HTTP/1.0" 503 537 "-" "ApacheBench/2.3"
xx.xx.xx.xxx - - [2018/09/22:23:41:49 +0800] "GET / HTTP/1.0" 200 612 "-" "ApacheBench/2.3"
xx.xx.xx.xxx - - [2018/09/22:23:41:50 +0800] "GET / HTTP/1.0" 200 612 "-" "ApacheBench/2.3"
xx.xx.xx.xxx - - [2018/09/22:23:41:51 +0800] "GET / HTTP/1.0" 200 612 "-" "ApacheBench/2.3"
xx.xx.xx.xxx - - [2018/09/22:23:41:52 +0800] "GET / HTTP/1.0" 200 612 "-" "ApacheBench/2.3"
xx.xx.xx.xxx - - [2018/09/22:23:41:53 +0800] "GET / HTTP/1.0" 200 612 "-" "ApacheBench/2.3"

4) ab 統計の応答時間は以下のとおりです。最小応答時間は 87 ミリ秒、最大応答時間は 5128 ミリ秒、平均応答時間は 1609 ミリ秒です。

    最小 平均[+/- 標準偏差] 中央値 最大値
接続: 41 44 1.7 44 46
処理: 46 1566 1916.6 1093 5084
待機中: 46 1565 1916.7 1092 5084
合計: 87 1609 1916.2 1135 5128

4.3 Nodelayのテスト

1) 4.2 は、バーストを設定した後、バースト リクエストは処理のためにキューに入れられますが、応答時間が長すぎて、クライアントがすでにタイムアウトしている可能性があることを示しています。そのため、nodelay 構成を追加して、nginx が待機中のリクエストを緊急に処理し、応答時間を短縮するようにします。

http{
 limit_req_zone $binary_remote_addr ゾーン=テスト:10m レート=1r/s;
 
 サーバー{
  聞く 80;
  server_name ローカルホスト;
  位置 / {
   limit_req ゾーン=テスト バースト=5 ノードレイ;
   ルートhtml;
   インデックス index.html index.htm;
  }
}

2) ab を使用して 10 件のリクエストを同時に開始します (ab -n 10 -c 10 http://xxxx/)。

3) サーバーのアクセスログを確認します。最初のリクエストは直接処理され、2番目から6番目のリクエストは処理のためにキューに入れられ(nodelay、nginxの緊急処理を設定)、7番目から10番目のリクエストは拒否されます。

xx.xx.xx.xxx - - [2018/09/23:00:04:47 +0800] "GET / HTTP/1.0" 200 612 "-" "ApacheBench/2.3"
xx.xx.xx.xxx - - [2018/09/23:00:04:47 +0800] "GET / HTTP/1.0" 200 612 "-" "ApacheBench/2.3"
xx.xx.xx.xxx - - [2018/09/23:00:04:47 +0800] "GET / HTTP/1.0" 200 612 "-" "ApacheBench/2.3"
xx.xx.xx.xxx - - [2018/09/23:00:04:47 +0800] "GET / HTTP/1.0" 200 612 "-" "ApacheBench/2.3"
xx.xx.xx.xxx - - [2018/09/23:00:04:47 +0800] "GET / HTTP/1.0" 200 612 "-" "ApacheBench/2.3"
xx.xx.xx.xxx - - [2018/09/23:00:04:47 +0800] "GET / HTTP/1.0" 200 612 "-" "ApacheBench/2.3"
xx.xx.xx.xxx - - [2018/09/23:00:04:47 +0800] "GET / HTTP/1.0" 503 537 "-" "ApacheBench/2.3"
xx.xx.xx.xxx - - [2018/09/23:00:04:47 +0800] "GET / HTTP/1.0" 503 537 "-" "ApacheBench/2.3"
xx.xx.xx.xxx - - [2018/09/23:00:04:47 +0800] "GET / HTTP/1.0" 503 537 "-" "ApacheBench/2.3"
xx.xx.xx.xxx - - [2018/09/23:00:04:47 +0800] "GET / HTTP/1.0" 503 537 "-" "ApacheBench/2.3"

4) ab 統計の応答時間は以下のとおりです。最小応答時間は 85 ミリ秒、最大応答時間は 92 ミリ秒、平均応答時間は 88 ミリ秒です。

    最小 平均[+/- 標準偏差] 中央値 最大値
接続: 42 43 0.5 43 43
処理: 43 46 2.4 47 49
待機中: 42 45 2.5 46 49
合計: 85 88 2.8 90 92

要約する

この記事では、まず、一般的に使用されている電流制限アルゴリズム (リーキー バケット アルゴリズムとトークン バケット アルゴリズム) を分析し、nginx が HTTP リクエストを処理するプロセスと nginx タイムド イベントの実装について簡単に紹介します。次に、ngx_http_limit_req_module モジュールの基本データ構造とその電流制限プロセスを詳細に分析し、例を使用して、読者が nginx 電流制限の構成と結果を理解できるようにします。もうひとつのモジュール ngx_http_limit_conn_module は、接続数を制限するためのものです。比較的わかりやすいので、ここでは詳しく紹介しません。

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

以下もご興味があるかもしれません:
  • 1 つの記事で Nginx の現在の制限を理解する (簡単な実装)
  • ネットワークセグメント内の IP アドレスに対する Nginx の接続制限設定の詳細な説明
  • nginx 電流制限ソリューションの実装 (3 つの方法)
  • nginx を使用して分散電流制限を実装する方法
  • nginx で読み取りと書き込みの電流制限を実装する方法
  • Nginx 急ぎ購入 電流制限構成 実装分析

<<:  MySQLログシステムの詳細情報共有

>>:  Nodeはリクエスト追跡にasync_hooksモジュールを使用します

推薦する

MySQL を暗号化および復号化するいくつかの方法 (要約)

目次前面に書かれた双方向暗号化エンコード/デコードAES_ENCRYPT/AES_DECRYPT D...

MySQL のロードバランサーとして nginx を使用する方法

注意: nginxのバージョンは1.9以上である必要があります。nginxをコンパイルするときに、-...

Zabbix 5.0 ディスク自動検出と読み取り/書き込み監視の問題を分析する

ディスクを自動的に検出する構成キーの値注: このキー値は Linux プラットフォームでのみサポート...

nginx での書き換えジャンプの実装

1. 新旧ドメイン名のジャンプ適用シナリオ: ドメイン名ベースのリダイレクト。会社の古いドメイン名は...

Dockerは指定されたメモリで操作を実行します

次のように: -m, --memory メモリ制限。形式は数値と単位です。単位は b、k、m、g の...

nginxの基礎を学ぶ

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

vue3 コンポーネントでの v-model の使用と詳細な説明

目次v-model 入力で双方向バインディングデータを使用するコンポーネント内の v-model他の...

JavaでTomcatサーバーを起動/停止する方法

1. プロジェクト構造 2.Tomcat.javaを呼び出す パッケージ com.calltomca...

Vueはタブを切り替えてデータの状態を維持する3つの方法を実装します

Vue でタブ切り替えを実装する 3 つの方法1. v-showはコンテンツの切り替えを制御します1...

Node.jsサービスDockerコンテナアプリケーション実践のまとめ

この記事では、Docker コマンドの使用とインストールについては説明しません。Docker を基礎...

Reactを使用する際の7つの落とし穴のまとめ

目次1. コンポーネントの肥大化2. 状態を直接変更する3. プロパティは数値を渡す必要があるが文字...

Hadoop を使用せずに Linux 環境に Spark のスタンドアロン バージョンをインストールする方法

ビッグデータはますます注目を集めており、ビッグデータのいくつかの構成要素に精通していないと、自慢でき...

WeChatアプレットが計算機機能を実装

この記事では、WeChatアプレットの計算機機能を実装するための具体的なコードを参考までに紹介します...

Windows の MySQL net start mysql MySQL サービスの起動エラーが発生する システムエラーの解決

目次1- エラーの詳細2-シングルソリューション2.1-ディレクトリ C:\Windows\Syst...

Linux でのログ サーバーの設定に関するグラフィック チュートリアル

序文この記事では、Linux 構成ログ サーバーに関する関連コンテンツを主に紹介し、参考と学習のため...