Javaエッセンシャルスキルのソースコード(Nginxソースコード研究のnginx電流制限モジュール)

概要:

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

現在の制限の目的は、同時アクセス/リクエストのレートを制限することでシステムを保護することです。制限レートに達すると、サービスを拒否(エラーページに移動)、キューに入れ(2回目の強制終了)、ダウングレード(に戻る)できます。ボトムデータまたはデフォルトデータ);

高同時実行システムの一般的な現在の制限には、同時接続の総数の制限(データベース接続プール)、瞬間的な同時接続の数の制限(nginxのlimit_connモジュールなど、瞬間的な同時接続の数の制限に使用される)、平均の制限が含まれます。時間枠内のレート(1秒あたりの平均レートを制限するnginx limit_reqモジュール);

さらに、電流は、ネットワーク接続の数、ネットワークトラフィック、CPUまたはメモリの負荷などに応じて制限できます。

業界の主流の電流制限アルゴリズム

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

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

1.1カウンター

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

次に、カウンターカウンターを設定できます。その有効時間は1分です(つまり、毎分カウンターは0にリセットされます)。要求が来るたびに、

(AtomicLong [一貫性のある単一のJVMを保証]およびvolatile [時間内の最新の値を認識するスレッド]を追加します)counterの値が100より大きい場合、カウンターは1ずつ増加し、要求の数が多すぎることを意味します。

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

次の図に示すように、1:00の直前に100リクエストに到達し、1:00カウンターがリセットされ、1:00の直後に100リクエストに到達します。明らかに、カウンターは100を超えず、すべてのリクエストが100を超えるわけではありません。傍受された;

しかし、この期間のリクエスト数は実際には200に達し、100をはるかに超えています。

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

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

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

 

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

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

リクエストが到着すると、バケットからトークンを取得しようとします。取得している場合は、リクエストの処理を続行できます。到着していない場合は、順番に待機するか、直接破棄します。

リーキーバケットアルゴリズムの流出速度は一定または0(要求が来ない)であるのに対し、トークンバケットアルゴリズムの流出速度は速度rよりも大きい可能性があることがわかります。

カウントセマフォJavaConcurrency Toolkit JUIのセマフォは、このアルゴリズムを使用して電流制限を実現します。

 

2.nginxの基本的な知識

Nginxには、主に2つの電流制限方法があります。接続数に応じた電流制限(ngx_http_limit_conn_module)と要求レートに応じた電流制限(ngx_http_limit_req_module)です。

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

2.1HTTPリクエスト処理プロセス

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

 NGX_HTTP_PREACCESS_PHASE、//アクセス制御、電流制限モジュールはハンドラーをこの段階に登録します(この記事は電流制限に焦点を当てており、他の人を見ないで、一般的なプロセスに精通しているだけです)!

typedef enum {
 NGX_HTTP_POST_READ_PHASE = 0, //目前只有realip模块会注册handler(nginx作为代理服务器时有用,后端以此获取客户端原始ip)
 
 NGX_HTTP_SERVER_REWRITE_PHASE, //server块中配置了rewrite指令,重写url
 
 NGX_HTTP_FIND_CONFIG_PHASE, //查找匹配location;不能自定义handler;
 NGX_HTTP_REWRITE_PHASE,  //location块中配置了rewrite指令,重写url
 NGX_HTTP_POST_REWRITE_PHASE, //检查是否发生了url重写,如果有,重新回到FIND_CONFIG阶段;不能自定义handler;
 
 NGX_HTTP_PREACCESS_PHASE,  //访问控制,限流模块会注册handler到此阶段
 
 NGX_HTTP_ACCESS_PHASE,  //访问权限控制
 NGX_HTTP_POST_ACCESS_PHASE, //根据访问权限控制阶段做相应处理;不能自定义handler;
 
 NGX_HTTP_TRY_FILES_PHASE,  //只有配置了try_files指令,才会有此阶段;不能自定义handler;
 NGX_HTTP_CONTENT_PHASE,  //内容产生阶段,返回响应给客户端
 
 NGX_HTTP_LOG_PHASE   //日志记录
} ngx_http_phases;

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

 この構造は、Nginxモジュラーアーキテクチャ全体の最も基本的なデータ構造です。Nginxプログラムのモジュールに含める必要のある基本的な属性について説明し、構造はtengine / src / core /ngx_conf_file.hで定義されています。

その構造は次のように定義されており、その中のコメントは機能の説明です:( Javaクラスと同様に、c言語の構造タイプに精通している必要があります)


struct ngx_module_s {  
    ngx_uint_t            ctx_index;      
    /*分类的模块计数器 
    nginx模块可以分为四种:core、event、http和mail 
    每个模块都会各自计数,ctx_index就是每个模块在其所属类组的计数*/  
      
    ngx_uint_t            index;          
    /*一个模块计数器,按照每个模块在ngx_modules[]数组中的声明顺序,从0开始依次给每个模块赋值*/  
  
    ngx_uint_t            spare0;  
    ngx_uint_t            spare1;  
    ngx_uint_t            spare2;  
    ngx_uint_t            spare3;
      ngx_uint_t            version;      //nginx模块版本  
  
    void                 *ctx;            
    /*模块的上下文,不同种类的模块有不同的上下文,因此实现了四种结构体*/  
      
    ngx_command_t        *commands;  
    /*命令定义地址 
    模块的指令集 
    每一个指令在源码中对应着一个ngx_command_t结构变量*/  
      
    ngx_uint_t            type;         //模块类型,用于区分core event http和mail  
  
    ngx_int_t           (*init_master)(ngx_log_t *log);         //初始化master时执行  
  
    ngx_int_t           (*init_module)(ngx_cycle_t *cycle);     //初始化module时执行
   ngx_int_t           (*init_process)(ngx_cycle_t *cycle);    //初始化process时执行  
    ngx_int_t           (*init_thread)(ngx_cycle_t *cycle);     //初始化thread时执行  
    void                (*exit_thread)(ngx_cycle_t *cycle);     //退出thread时执行  
    void                (*exit_process)(ngx_cycle_t *cycle);    //退出process时执行  
  
    void                (*exit_master)(ngx_cycle_t *cycle);     //退出master时执行  
  
//以下功能不明  
    uintptr_t             spare_hook0;  
    uintptr_t             spare_hook1;  
    uintptr_t             spare_hook2;  
    uintptr_t             spare_hook3;  
    uintptr_t             spare_hook4;  
    uintptr_t             spare_hook5;  
    uintptr_t             spare_hook6;  
    uintptr_t             spare_hook7;  
};  
  
typedef struct ngx_module_s      ngx_module_t;  

Nginxは多くのモジュールを定義します。各モジュールには独自の型があります。独自の操作関数を定義できます。これらの関数を対応する型構造の関数ポインタに割り当てることで、さまざまなコールバック関数インターフェイスを登録できます。さまざまな機能を実現するために関数、java多態性に少し似ています

typedef struct {
 ngx_int_t (*preconfiguration)(ngx_conf_t *cf);
 ngx_int_t (*postconfiguration)(ngx_conf_t *cf); //此方法注册handler到相应阶段
 
 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); //server配置
 char  *(*merge_srv_conf)(ngx_conf_t *cf, void *prev, void *conf);
 
 void  *(*create_loc_conf)(ngx_conf_t *cf); //location配置
 char  *(*merge_loc_conf)(ngx_conf_t *cf, void *prev, void *conf);
} ngx_http_module_t;

ngx_http_limit_req_moduleモジュールを例にとると、構成後のメソッドは次のように簡単に実装されます:(この記事では電流制限に焦点を当てています)

static 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.2nginxイベント処理の簡単な紹介

nginxがepollを使用すると仮定すると[Linuxディストリビューションが異なれば、centosはepollのIOモデルです]。

Nginxは、関係するすべてのfd(ファイル記述ファイル記述子[ネットワークioリソースを表すファイルに基づくLinux])をepollに登録し、次のようにメソッドステートメントを追加する必要があります。

static ngx_int_t ngx_epoll_add_event(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags);

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

struct ngx_event_s {
 
 ngx_event_handler_pt handler; //函数指针:事件的处理函数
 
 ngx_rbtree_node_t timer;  //超时定时器,存储在红黑树中(节点的key即为事件的超时时间)
 
 unsigned   timedout:1; //记录事件是否超时
 
};

一般に、epoll_waitは、すべてのfdをリッスンし、発生する読み取りおよび書き込みイベントを処理するために周期的に呼び出されます。epoll_waitはブロッキング呼び出しであり、最後のパラメータータイムアウトはタイムアウト時間、つまり、イベントが発生しない場合の最大ブロッキングタイムアウト時間です。メソッドは戻ります。

nginxがタイムアウトタイムアウトを設定すると、次のコードに示すように、上記のタイムアウトタイマーを記録している赤黒木から最近古くなったノードを見つけ、epoll_waitのタイムアウト時間として使用します。

ngx_msec_t ngx_event_find_timer(void)
{
 node = ngx_rbtree_min(root, sentinel);
 timer = (ngx_msec_int_t) (node->key - ngx_current_msec);
 
 return (ngx_msec_t) (timer > 0 ? timer : 0);
}

同時に、各サイクルの終わりに、nginxは赤黒木からイベントが期限切れになっていないかどうかを確認します。期限切れの場合は、timeout = 1をマークして、イベントハンドラーを呼び出します。

void ngx_event_expire_timers(void)
{
 for ( ;; ) {
  node = ngx_rbtree_min(root, sentinel);
 
  if ((ngx_msec_int_t) (node->key - ngx_current_msec) <= 0) { //当前事件已经超时
   ev = (ngx_event_t *) ((char *) node - offsetof(ngx_event_t, timer));
 
   ev->timedout = 1;
 
   ev->handler(ev);
 
   continue;
  }
 
  break;
 }
}

Nginxは、上記の方法でソケットイベントとタイミングイベントの処理を実現します。

ngx_http_limit_req_moduleモジュール分析

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

3.1構成手順

ngx_http_limit_req_moduleモジュールは、ユーザーが現在の制限戦略を構成するためのいくつかの構成手順を提供します

//每个配置指令主要包含两个字段:名称,解析配置的处理方法
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算法淘汰;注意此空间是在共享内存分配的,所有worker进程都能访问)
 //rate表示限制速率,此例为1qps
 { ngx_string("limit_req_zone"),
  ngx_http_limit_req_zone,
  },
 
 //用法:limit_req zone=one burst=5 nodelay;
 //zone指定使用哪一个共享空间
 //超出此速率的请求是直接丢弃吗?burst配置用于处理突发流量,表示最大排队请求数目,当客户端请求速率超过限流速率时,请求会排队等待;而超出burst的才会被直接拒绝;
 //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配列を探します。

static 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 struct {
 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 struct {
 ngx_shm_zone_t    *shm_zone; //共享存储空间
  
 ngx_uint_t     burst;  //队列大小
 ngx_uint_t     nodelay; //有请求排队时是否紧急处理,与burst配合使用(如果配置,则会紧急处理排队请求,否则依然按照限流速度处理)
} ngx_http_limit_req_limit_t;

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

//记录结构体
typedef struct {
 u_char      color;
 u_char      dummy;
 u_short      len; //数据长度
 ngx_queue_t     queue; 
 ngx_msec_t     last; //上次访问时间
  
 ngx_uint_t     excess; //当前剩余待处理的请求数(nginx用此实现令牌桶限流算法)
 ngx_uint_t     count; //此类记录请求的总数
 u_char      data[1];//数据内容(先按照key(hash值)查找,再比较数据内容是否相等)
} ngx_http_limit_req_node_t;
 
//红黑树节点,key为用户配置限流变量的hash值;
struct ngx_rbtree_node_s {
 ngx_rbtree_key_t  key;
 ngx_rbtree_node_t  *left;
 ngx_rbtree_node_t  *right;
 ngx_rbtree_node_t  *parent;
 u_char     color;
 u_char     data;
};
 
 
typedef struct {
 ngx_rbtree_t     rbtree; //红黑树
 ngx_rbtree_node_t    sentinel; //NIL节点
 ngx_queue_t     queue; //LRU队列
} ngx_http_limit_req_shctx_t;
 
//队列只有prev和next指针
struct ngx_queue_s {
 ngx_queue_t *prev;
 ngx_queue_t *next;
};

 

思考1:ngx_http_limit_req_node_tレコードは、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)) //queue字段地址减去其在结构体中偏移,为结构体首地址

思考2​​:現在の制限アルゴリズムは、最初にキーを使用して赤黒木ノードを検索し、対応するレコードを検索します。赤黒木ノードは、レコードngx_http_limit_req_node_t構造とどのように関連していますか。次のコードは、ngx_http_limit_req_moduleモジュールにあります。

 

size = offsetof(ngx_rbtree_node_t, color) //新建记录分配内存,计算所需空间大小
  + offsetof(ngx_http_limit_req_node_t, data)
  + len;
 
node = ngx_slab_alloc_locked(ctx->shpool, size);
 
node->key = hash;
 
lr = (ngx_http_limit_req_node_t *) &node->color; //color为u_char类型,为什么能强制转换为ngx_http_limit_req_node_t指针类型呢?
 
lr->len = (u_char) len;
lr->excess = 0;
 
ngx_memcpy(lr->data, data, len);
 
ngx_rbtree_insert(&ctx->sh->rbtree, node);
 
ngx_queue_insert_head(&ctx->sh->queue, &lr->queue);

上記のコードを分析すると、ngx_rbtree_node_s構造体の色とデータフィールドは実際には無意味です。構造体のライフフォームは最終的なストレージフォームとは異なります。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,记录key的hash值;data,记录key的数据内容;len,记录key的数据长度;ep,待处理请求数目;account,是否是最后一条限流策略
static ngx_int_t ngx_http_limit_req_lookup(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 (hash < node->key) {
   node = node->left;
   continue;
  }
 
  if (hash > node->key) {
   node = node->right;
   continue;
  }
 
  //hash值相等,比较数据是否相等
  lr = (ngx_http_limit_req_node_t *) &node->color;
 
  rc = ngx_memn2cmp(data, lr->data, len, (size_t) lr->len);
  //查找到
  if (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); //当前时间减去上次访问时间
 
   excess = lr->excess - ctx->rate * ngx_abs(ms) / 1000 + 1000; //待处理请求数-限流速率*时间段+1个请求(速率,请求数等都乘以1000了)
 
   if (excess < 0) {
    excess = 0;
   }
 
   *ep = excess;
 
   //待处理数目超过burst(等待队列大小),返回NGX_BUSY拒绝请求(没有配置burst时,值为0)
   if ((ngx_uint_t) excess > limit->burst) {
    return NGX_BUSY;
   }
 
   if (account) { //如果是最后一条限流策略,则更新上次访问时间,待处理请求数目,返回NGX_OK
    lr->excess = excess;
    lr->last = now;
    return NGX_OK;
   }
   //访问次数递增
   lr->count++;
 
   ctx->node = lr;
 
   return NGX_AGAIN; //非最后一条限流策略,返回NGX_AGAIN,继续校验下一条限流策略
  }
 
  node = (rc < 0) ? node->left : node->right;
 }
 
 //假如没有查找到节点,需要新建一条记录
 *ep = 0;
 //存储空间大小计算方法参照3.2.1节数据结构
 size = offsetof(ngx_rbtree_node_t, color)
   + offsetof(ngx_http_limit_req_node_t, data)
   + len;
 //尝试淘汰记录(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
   return NGX_ERROR;
  }
 }
 
 node->key = hash; //赋值
 lr = (ngx_http_limit_req_node_t *) &node->color;
 lr->len = (u_char) len;
 lr->excess = 0;
 ngx_memcpy(lr->data, 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 = now;
  lr->count = 0;
  return NGX_OK;
 }
 
 lr->last = 0;
 lr->count = 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つのレコードを削除しようとします。コードの実装は次のとおりです。

 

 

 

static 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);
   
  //注意:当为0时,无法进入if代码块,因此一定会删除尾部节点;当n不为0时,进入if代码块,校验是否可以删除
  if (n++ != 0) {
 
   ms = (ngx_msec_int_t) (now - lr->last);
   ms = ngx_abs(ms);
   //短时间内被访问,不能删除,直接返回
   if (ms < 60000) {
    return;
   }
    
   //有待处理请求,不能删除,直接返回
   excess = lr->excess - ctx->rate * ms / 1000;
   if (excess > 0) {
    return;
   }
  }
 
  //删除
  ngx_queue_remove(q);
 
  node = (ngx_rbtree_node_t *)
     ((u_char *) lr - offsetof(ngx_rbtree_node_t, color));
 
  ngx_rbtree_delete(&ctx->sh->rbtree, node);
 
  ngx_slab_free_locked(ctx->shpool, node);
 }
}

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) {
 return 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时,请求不会被延时处理,delay为0
 continue;
}
 
delay = excess * 1000 / ctx->rate;
 
if (delay > max_delay) {
 max_delay = delay;
 *ep = excess;
 *limit = &limits[n];
}

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

 

static void ngx_http_limit_req_delay(ngx_http_request_t *r)
{
 
 wev = r->connection->write;
 
 if (!wev->timedout) { //没有超时不会处理
 
  if (ngx_handle_write_event(wev, 0) != NGX_OK) {
   ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
  }
 
  return;
 }
 
 wev->timedout = 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请求
}

上記は、Nginx電流制限モジュールのソースコード実装の詳細です

 

おすすめ

転載: blog.csdn.net/Coder_Boy_/article/details/110479206