Rumah  >  Artikel  >  Operasi dan penyelenggaraan  >  analisis kod sumber modul mengehadkan semasa nginx

analisis kod sumber modul mengehadkan semasa nginx

WBOY
WBOYke hadapan
2023-05-11 18:16:121431semak imbas

Sistem konkurensi tinggi mempunyai tiga alat: caching, penurunan taraf dan pengehadan semasa;

Tujuan pengehadan semasa adalah untuk melindungi sistem dengan mengehadkan kadar akses/permintaan serentak had tercapai Kadar boleh penafian perkhidmatan (dihalakan ke halaman ralat), menunggu dalam talian (pembunuhan cepat), turun taraf (kembali ke bahagian bawah data atau data lalai); dalam sistem konkurensi tinggi termasuk: mengehadkan jumlah bilangan konkurensi (Kolam sambungan pangkalan data), hadkan bilangan konkurensi serta-merta (seperti modul limit_conn nginx, digunakan untuk mengehadkan bilangan sambungan serentak serta-merta), hadkan kadar purata dalam masa tetingkap (modul limit_req nginx, digunakan untuk mengehadkan kadar purata sesaat);

1. Algoritma pengehad semasa

Algoritma pengehad semasa yang paling mudah dan paling kasar ialah kaedah kaunter, dan yang lebih biasa digunakan ialah algoritma baldi bocor dan baldi token algoritma;

1.1 Pembilang

Kaedah pembilang adalah yang paling mudah dan paling mudah untuk dilaksanakan antara algoritma pengehadan semasa. Sebagai contoh, kami menetapkan bahawa untuk antara muka a, bilangan akses dalam satu minit tidak boleh melebihi 100. Kemudian kita boleh menetapkan pembilang dengan masa yang sah selama 1 minit (iaitu, pembilang akan ditetapkan semula kepada 0 setiap minit apabila permintaan datang, pembilang akan meningkat sebanyak 1. Jika pembilang Jika nilai lebih daripada 100, ia bermakna terdapat terlalu banyak permintaan;

Walaupun algoritma ini mudah, ia mempunyai masalah yang sangat mematikan, iaitu masalah kritikal.

Seperti yang ditunjukkan dalam rajah di bawah, 100 permintaan tiba pada saat sebelum 1:00, kaunter 1:00 ditetapkan semula dan 100 permintaan tiba pada saat selepas 1:00 Jelas sekali kaunter tidak akan melebihi 100, semua permintaan Tiada akan dipintas;

Walau bagaimanapun, bilangan permintaan dalam tempoh ini telah mencapai 200, jauh melebihi 100.

1.2 Algoritma Baldi Bocor analisis kod sumber modul mengehadkan semasa nginx

Seperti yang ditunjukkan dalam rajah di bawah, terdapat baldi bocor dengan kapasiti tetap dan air titisan mengalir keluar pada kadar tetap yang tetap; Jika baldi kosong, tiada titisan air akan mengalir keluar; limpahan (dibuang); 1.3 Algoritma Baldi Token

Timba token ialah baldi yang menyimpan token dengan kapasiti tetap ditambahkan pada baldi pada kadar maksimum b token disimpan dalam baldi. Apabila baldi penuh, token yang baru ditambah Dibuang; jika tidak, ia akan beratur atau dibuang terus;

boleh didapati, Kadar aliran keluar algoritma baldi bocor adalah malar atau 0, manakala kadar aliran keluar algoritma baldi token mungkin lebih besar daripada r

analisis kod sumber modul mengehadkan semasa nginx

2. Pengetahuan asas nginx

nginx terutamanya mempunyai dua kaedah pengehadan semasa: mengehadkan berdasarkan bilangan sambungan (ngx_http_limit_conn_module) dan mengehadkan berdasarkan kadar permintaan (ngx_http_limit_req_module);

Sebelum mempelajari modul pengehad semasa, anda juga perlu memahami proses pemprosesan permintaan http nginx, proses pemprosesan acara nginx, dll.; 🎜>2.1 Proses pemprosesan permintaan http

nginx membahagikan proses pemprosesan permintaan http kepada 11 peringkat, kebanyakannya Modul http akan menambah pengendalinya sendiri ke peringkat tertentu (empat peringkat tidak boleh menambah pengendali tersuai) . Apabila nginx memproses permintaan http, ia akan memanggil semua pengendali satu demi satu; struktur konteks adalah seperti berikut (medan struktur konteks ialah penunjuk fungsi): analisis kod sumber modul mengehadkan semasa nginx

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;

mengambil modul ngx_http_limit_req_module Sebagai contoh, kaedah pascakonfigurasi hanya dilaksanakan seperti berikut:

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;
2.2 Pengenalan ringkas kepada pemprosesan acara nginx

Andaikan bahawa nginx menggunakan epoll.

nginx perlu mendaftarkan semua FD yang penting untuk diepoll. Tambahkan hayat kaedah seperti berikut:

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;
}
Parameter pertama kaedah ialah penunjuk struktur ngx_event_t, yang mewakili bacaan atau. tulis peristiwa yang membimbangkan; tulis peristiwa yang berlaku; epoll_wait ialah panggilan menyekat, dan yang terakhir Tamat masa parameter ialah masa tamat masa penyekatan maksimum tamat masa, ia akan mencari dari pokok merah-hitam yang merekodkan pemasa tamat masa yang dinyatakan di atas Nod yang akan tamat tempoh baru-baru ini digunakan sebagai masa tamat epoll_wait, seperti yang ditunjukkan dalam kod
static ngx_int_t ngx_epoll_add_event(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags);

Pada masa yang sama, nginx akan menyemak sama ada sebarang peristiwa telah tamat tempoh daripada pokok merah-hitam pada penghujung setiap kitaran Jika tamat tempoh, tandai tamat masa=1 dan hubungi pengendali acara;
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就是通过上面的方法实现了socket事件的处理,定时事件的处理;

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在postconfiguration过程会注册ngx_http_limit_req_handler方法到http处理的ngx_http_preaccess_phase阶段;

ngx_http_limit_req_handler会执行漏桶算法,判断是否超出配置的限流速率,从而进行丢弃或者排队或者通过;

当用户第一次请求时,会新增一条记录(主要记录访问计数、访问时间),以客户端ip地址(配置$binary_remote_addr)的hash值作为key存储在红黑树中(快速查找),同时存储在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;

analisis kod sumber modul mengehadkan semasa nginx

前面说过用户访问记录会同时存储在红黑树与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记录通过prev和next指针形成双向链表,实现lru队列;最新访问的节点总会被插入链表头部,淘汰时从尾部删除节点;

analisis kod sumber modul mengehadkan semasa nginx

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:限流算法首先使用key查找红黑树节点,从而找到对应的记录,红黑树节点如何与记录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结构体的color与data字段其实是无意义的,结构体的生命形式与最终存储形式是不同的,nginx最终使用以下存储形式存储每条记录;

analisis kod sumber modul mengehadkan semasa nginx

3.2.2限流算法

上面提到在postconfiguration过程会注册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,继续校验下一条限流策略
  
}

举个例子,假如burst配置为0,待处理请求数初始为excess;令牌产生周期为t;如下图所示

analisis kod sumber modul mengehadkan semasa nginx

3.2.2.2lru淘汰策略

上一节叩痛算法中,会执行ngx_http_limit_req_expire淘汰一条记录,每次都是从lru队列末尾删除;

第二个参数n,当n==0时,强制删除末尾一条记录,之后再尝试删除一条或两条记录;n==1时,会尝试删除一条或两条记录;代码实现如下:

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 burst实现

burst是为了应对突发流量的,偶然间的突发流量到达时,应该允许服务端多处理一些请求才行;

当burst为0时,请求只要超出限流速率就会被拒绝;当burst大于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); //可写事件添加定时器(超时之前是不能往客户端返回的)

计算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请求
}

4.实战

4.1测试普通限流

1)配置nginx限流速率为1qps,针对客户端ip地址限流(返回状态码默认为503),如下:

http{
 limit_req_zone $binary_remote_addr zone=test:10m rate=1r/s;
 
 server {
  listen  80;
  server_name localhost;
  location / {
   limit_req zone=test;
   root html;
   index index.html index.htm;
  }
}

2)连续并发发起若干请求;3)查看服务端access日志,可以看到22秒连续到达3个请求,只处理1个请求;23秒到达两个请求,第一个请求处理,第二个请求被拒绝

xx.xx.xx.xxx - - [22/sep/2018:23:33:22 +0800] "get / http/1.0" 200 612 "-" "apachebench/2.3"
xx.xx.xx.xxx - - [22/sep/2018:23:33:22 +0800] "get / http/1.0" 503 537 "-" "apachebench/2.3"
xx.xx.xx.xxx - - [22/sep/2018:23:33:22 +0800] "get / http/1.0" 503 537 "-" "apachebench/2.3"
xx.xx.xx.xxx - - [22/sep/2018:23:33:23 +0800] "get / http/1.0" 200 612 "-" "apachebench/2.3"
xx.xx.xx.xxx - - [22/sep/2018:23:33:23 +0800] "get / http/1.0" 503 537 "-" "apachebench/2.3"

4.2测试burst

1)限速1qps时,超过请求会被直接拒绝,为了应对突发流量,应该允许请求被排队处理;因此配置burst=5,即最多允许5个请求排队等待处理;

http{
 limit_req_zone $binary_remote_addr zone=test:10m rate=1r/s;
 
 server {
  listen  80;
  server_name localhost;
  location / {
   limit_req zone=test burst=5;
   root html;
   index index.html index.htm;
  }
}

2)使用ab并发发起10个请求,ab -n 10 -c 10 http://xxxxx;

3)查看服务端access日志;根据日志显示第一个请求被处理,2到5四个请求拒绝,6到10五个请求被处理;为什么会是这样的结果呢?

查看ngx_http_log_module,注册handler到ngx_http_log_phase阶段(http请求处理最后一个阶段);

因此实际情况应该是这样的:10个请求同时到达,第一个请求到达直接被处理,第2到6个请求到达,排队延迟处理(每秒处理一个);第7到10个请求被直接拒绝,因此先打印access日志;

第2到6个请求米诶秒处理一个,处理完成打印access日志,即49到53秒每秒处理一个;

xx.xx.xx.xxx - - [22/sep/2018:23:41:48 +0800] "get / http/1.0" 200 612 "-" "apachebench/2.3"
xx.xx.xx.xxx - - [22/sep/2018:23:41:48 +0800] "get / http/1.0" 503 537 "-" "apachebench/2.3"
xx.xx.xx.xxx - - [22/sep/2018:23:41:48 +0800] "get / http/1.0" 503 537 "-" "apachebench/2.3"
xx.xx.xx.xxx - - [22/sep/2018:23:41:48 +0800] "get / http/1.0" 503 537 "-" "apachebench/2.3"
xx.xx.xx.xxx - - [22/sep/2018:23:41:48 +0800] "get / http/1.0" 503 537 "-" "apachebench/2.3"
xx.xx.xx.xxx - - [22/sep/2018:23:41:49 +0800] "get / http/1.0" 200 612 "-" "apachebench/2.3"
xx.xx.xx.xxx - - [22/sep/2018:23:41:50 +0800] "get / http/1.0" 200 612 "-" "apachebench/2.3"
xx.xx.xx.xxx - - [22/sep/2018:23:41:51 +0800] "get / http/1.0" 200 612 "-" "apachebench/2.3"
xx.xx.xx.xxx - - [22/sep/2018:23:41:52 +0800] "get / http/1.0" 200 612 "-" "apachebench/2.3"
xx.xx.xx.xxx - - [22/sep/2018:23:41:53 +0800] "get / http/1.0" 200 612 "-" "apachebench/2.3"

4)ab统计的响应时间见下面,最小响应时间87ms,最大响应时间5128ms,平均响应时间为1609ms:

min mean[+/-sd] median max
connect:  41 44 1.7  44  46
processing: 46 1566 1916.6 1093 5084
waiting:  46 1565 1916.7 1092 5084
total:   87 1609 1916.2 1135 5128

4.3测试nodelay

1)4.2显示,配置burst后,虽然突发请求会被排队处理,但是响应时间过长,客户端可能早已超时;因此添加配置nodelay,使得nginx紧急处理等待请求,以减小响应时间:

http{
 limit_req_zone $binary_remote_addr zone=test:10m rate=1r/s;
 
 server {
  listen  80;
  server_name localhost;
  location / {
   limit_req zone=test burst=5 nodelay;
   root html;
   index index.html index.htm;
  }
}

2)使用ab并发发起10个请求,ab -n 10 -c 10 http://xxxx/;

3)查看服务端access日志;第一个请求直接处理,第2到6个五个请求排队处理(配置nodelay,nginx紧急处理),第7到10四个请求被拒绝

xx.xx.xx.xxx - - [23/sep/2018:00:04:47 +0800] "get / http/1.0" 200 612 "-" "apachebench/2.3"
xx.xx.xx.xxx - - [23/sep/2018:00:04:47 +0800] "get / http/1.0" 200 612 "-" "apachebench/2.3"
xx.xx.xx.xxx - - [23/sep/2018:00:04:47 +0800] "get / http/1.0" 200 612 "-" "apachebench/2.3"
xx.xx.xx.xxx - - [23/sep/2018:00:04:47 +0800] "get / http/1.0" 200 612 "-" "apachebench/2.3"
xx.xx.xx.xxx - - [23/sep/2018:00:04:47 +0800] "get / http/1.0" 200 612 "-" "apachebench/2.3"
xx.xx.xx.xxx - - [23/sep/2018:00:04:47 +0800] "get / http/1.0" 200 612 "-" "apachebench/2.3"
xx.xx.xx.xxx - - [23/sep/2018:00:04:47 +0800] "get / http/1.0" 503 537 "-" "apachebench/2.3"
xx.xx.xx.xxx - - [23/sep/2018:00:04:47 +0800] "get / http/1.0" 503 537 "-" "apachebench/2.3"
xx.xx.xx.xxx - - [23/sep/2018:00:04:47 +0800] "get / http/1.0" 503 537 "-" "apachebench/2.3"
xx.xx.xx.xxx - - [23/sep/2018:00:04:47 +0800] "get / http/1.0" 503 537 "-" "apachebench/2.3"

4)ab统计的响应时间见下面,最小响应时间85ms,最大响应时间92ms,平均响应时间为88ms:

min mean[+/-sd] median max
connect:  42 43 0.5  43  43
processing: 43 46 2.4  47  49
waiting:  42 45 2.5  46  49
total:   85 88 2.8  90  92

Atas ialah kandungan terperinci analisis kod sumber modul mengehadkan semasa nginx. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Kenyataan:
Artikel ini dikembalikan pada:yisu.com. Jika ada pelanggaran, sila hubungi admin@php.cn Padam