nginx限流模块源码分析

这篇文章主要介绍“nginx限流模块源码分析”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“nginx限流模块源码分析”文章能帮助大家解决问题。

高并发系统有三把利器:缓存、降级和限流;

限流的目的是通过对并发访问/请求进行限速来保护系统,一旦达到限制速率则可以拒绝服务(定向到错误页)、排队等待(秒杀)、降级(返回兜底数据或默认数据);

高并发系统常见的限流有:限制总并发数(数据库连接池)、限制瞬时并发数(如nginx的limit_conn模块,用来限制瞬时并发连接数)、限制时间窗口内的平均速率(nginx的limit_req模块,用来限制每秒的平均速率);

另外还可以根据网络连接数、网络流量、cpu或内存负载等来限流。

1.限流算法

最简单粗暴的限流算法就是计数器法了,而比较常用的有漏桶算法和令牌桶算法;

1.1计数器

计数器法是限流算法里最简单也是最容易实现的一种算法。比如我们规定,对于a接口来说,我们1分钟的访问次数不能超过100个。

那么我们我们可以设置一个计数器counter,其有效时间为1分钟(即每分钟计数器会被重置为0),每当一个请求过来的时候,counter就加1,如果counter的值大于100,就说明请求数过多;

这个算法虽然简单,但是有一个十分致命的问题,那就是临界问题。

如下图所示,在1:00前一刻到达100个请求,1:00计数器被重置,1:00后一刻又到达100个请求,显然计数器不会超过100,所有请求都不会被拦截;

然而这一时间段内请求数已经达到200,远超100。

nginx限流模块源码分析

1.2 漏桶算法

如下图所示,有一个固定容量的漏桶,按照常量固定速率流出水滴;如果桶是空的,则不会流出水滴;流入到漏桶的水流速度是随意的;如果流入的水超出了桶的容量,则流入的水会溢出(被丢弃);

可以看到漏桶算法天生就限制了请求的速度,可以用于流量整形和限流控制;

nginx限流模块源码分析

1.3 令牌桶算法

令牌桶是一个存放固定容量令牌的桶,按照固定速率r往桶里添加令牌;桶中最多存放b个令牌,当桶满时,新添加的令牌被丢弃;

当一个请求达到时,会尝试从桶中获取令牌;如果有,则继续处理请求;如果没有则排队等待或者直接丢弃;

可以发现,漏桶算法的流出速率恒定或者为0,而令牌桶算法的流出速率却有可能大于r;

nginx限流模块源码分析

2.nginx基础知识

nginx主要有两种限流方式:按连接数限流(ngx_http_limit_conn_module)、按请求速率限流(ngx_http_limit_req_module);

学习限流模块之前还需要了解nginx对http请求的处理过程,nginx事件处理流程等;

2.1http请求处理过程

nginx将http请求处理流程分为11个阶段,绝大多数http模块都会将自己的handler添加到某个阶段(其中有4个阶段不能添加自定义handler),nginx处理http请求时会挨个调用所有的handler;

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模块上下文结构体如下所示(上下文结构体的字段都是一些函数指针):

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模块为例,postconfiguration方法简单实现如下:

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.2 nginx事件处理简单介绍

假设nginx使用的是epoll。

nginx需要将所有关心的fd注册到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是阻塞调用,最后一个参数timeout是超时时间,即最多阻塞timeout时间如果还是没有事件发生,方法会返回;

nginx在设置超时时间timeout时,会从上面说的记录超时定时器的红黑树中查找最近要到时的节点,以此作为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,并调用事件的handler;

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;

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队列;最新访问的节点总会被插入链表头部,淘汰时从尾部删除节点;

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最终使用以下存储形式存储每条记录;

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;如下图所示

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

关于“nginx限流模块源码分析”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识,可以关注编程之家行业资讯频道,小编每天都会为大家更新不同的知识点。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


文章浏览阅读3.7k次,点赞2次,收藏5次。Nginx学习笔记一、Nginx 简介1. 什么是Nginx2. 反向代理3. 负载均衡4. 动静分离二、Nginx基本使用1. Nginx常用的操作命令2. Nginx的配置文件提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档文章目录一、Nginx 简介1. 什么是Nginx2. 反向代理3. 负载均衡4. 动静分离二、Nginx基本使用1. Nginx常用的操作命令2. Nginx的配置文件一、Nginx 简介1. 什么是Nginx  Nginx(“engine x”)是一个_nginx代理
文章浏览阅读1.7w次,点赞14次,收藏61次。我们在使用容器的过程中需,有时候需要对容器中的文件进行修改管理,如果不做文件映射的化,我们使用docker exec -it 容器ID/容器名 /bin/bash 才能进入nginx中的文件里面如图。架设在客户机与目标主机之间,只用于代理内部网络对Internet的连接请求,客户机必须指定代理服务器,并将原本要直接发送到web服务器上的http请求发送到代理服务器中。A想要组C的房子,但是A并不认识C所以租不到,但是B认识C,A找B帮忙租到了C的房子。客户端代理服务器服务器。_docker nginx 配置
文章浏览阅读1.4k次。当用户在访问网站的过程中遇到404错误时,通常情况下应该显示一个友好的错误页面,而不是仅仅显示一个简单的错误提示。在Nginx中,可以通过配置来实现404错误自动跳转到首页的功能。如果您的网站使用动态内容生成页面(如PHP或其他服务器端语言),则应相应地修改配置以适应您的网站架构。这样,当用户访问一个不存在的页面时,Nginx会自动将其重定向到首页。为了使配置生效,需要重新加载Nginx配置。首先,需要打开Nginx的配置文件。现在,当用户访问一个不存在的页面时,Nginx会自动将其重定向到首页。_nginx 404 重定向
文章浏览阅读2.7k次。docker 和 docker-compose 部署 nginx+mysql+wordpress 实战_docker wordpress mariadb
文章浏览阅读1.3k次。5:再次启动nginx,可以正常启动,可以在任务管理器中查看到nginx的进程。重新启动下 直接访问8090端口 ok 访问成功。1 :查看80端口占用情况,pid的值为3960。3:在运行中输入regedit打开注册表编辑器。2: 通过以下命令查看3960所对应的服务名称。4:找到Start,右键修改将其制改为4。_nginx80端口无法访问
文章浏览阅读3.1w次,点赞105次,收藏182次。高性能:Nginx 被设计为能够处理大量并发连接而不显著增加系统负担。它采用异步事件驱动的架构,可以有效地处理高流量的 Web 请求。负载均衡:Nginx 支持负载均衡,可以将请求分发到多个后端服务器,以提高网站性能和可用性。反向代理:Nginx 可以充当反向代理,将客户端请求转发到后端服务器,隐藏后端服务器的真实 IP 地址,增加安全性和可扩展性。静态文件服务:Nginx 可以高效地提供静态文件(如 HTML、CSS、JavaScript、图像等)的服务,减轻应用服务器的负担。
文章浏览阅读976次。nginx作为常用的web代理服务器,某些场景下对于性能要求还是蛮高的,所以本片文章会基于操作系统调度以及网络通信两个角度来讨论一下Nginx性能的优化思路。我们的大学教程大部分讲述七层模型,实际上现代网络协议使用的都是四层模型,如下图,应用层报文经过四层的首部封装到对端。对端链路层拆开首部查看mac地址是自己在网上,拆开ip首部查看目的地址是不是自己,然后到达传输层应用层完成报文接收。文章是基于原有个人知识基础上,对旧知识进行巩固,以及新知识实践学习。
文章浏览阅读5.4k次,点赞9次,收藏15次。最后再说一种情况,就是后端处理了跨域,就不需要自己在处理了(这里吐槽下,某些后端工程师自己改服务端代码解决跨域,但是又不理解其中原理,网上随便找段代码黏贴,导致响应信息可能处理不完全,如method没添加全,headers没加到点上,自己用的那个可能复制过来的并不包含实际项目所用到的,没有添加options请求返回状态码等,导致Nginx再用通用的配置就会可能报以下异常)里面的就好了,因为这里如果是预检请求直接就ruturn了,请求不会再转发到59200服务,如果也删除了,就会报和情况1一样的错误。_nginx 允许跨域
文章浏览阅读2.5k次。项目配置了多个域名,如下,php 代码中有获取的值。当访问a.demo.com时,其获取的值是符合预期的。但是当访问b.demo.com时,其获取的值还是a.demo.com,导致代码中的判断出现错误。_nginxservername多个域名
文章浏览阅读1k次,点赞2次,收藏5次。采用YAML manifest的方式来安装ingress-nginx,用registry.lank8s.cn镜像库来替换 registry.k8s.io的库。_ingress-nginx安装
文章浏览阅读1.6k次,点赞2次,收藏2次。在windows平台编译nginx_windows 编译nginx
文章浏览阅读5.8k次,点赞2次,收藏18次。nginx [engine x] 是 HTTP 和反向代理服务器、邮件代理服务器和通用 TCP/UDP 代理服务器。nginx 的特点是占有内存少,并发能力强,事实上 nginx 的并发能力确实在同类型的网页服务器中表现较好,中国大陆使用nginx网站用户有:百度、京东、新浪、网易、腾讯、淘宝等。在高连接并发的情况下,nginx是Apache服务器不错的替代品,能够支持高达50000个并发连接数的响应。使用epoll and kqueue作为开发模型。_nginx
文章浏览阅读2k次。Linux启动(systemctl start nginx)nginx服务时出现:Failed to start nginx.service: Unit not found._为什么nginx的systemctl start nginx.service不能使用
文章浏览阅读1.3k次。重启之后,打开浏览器,输入http://localhost:8900/myBaidu,这时候就会自动的跳转到百度的页面。按照我们不同的需求修改nginx文件夹中的nginx-1.16.1conf里面的nginx.conf文件。启动nginx:打开nginx的文件夹,然后双击nginx.exe文件,启动nginx。打开之后假设我们需要跳转到百度则在配置文件nginx.conf中的下面加上。1、打开任务管理器关闭nginx的进程。端口在配置文件的(下图)进行查看nginx端口。_nginx 代理百度
文章浏览阅读5.7k次,点赞5次,收藏3次。nginx重定向问题解决(rewrite or internal redirection cycle)_rewrite or internal redirection cycle while internally redirecting to "/inde
文章浏览阅读1.3k次。请注意,上述命令假设 Nginx 已经在系统的 PATH 环境变量中配置。如果没有,请提供正确的 Nginx 安装路径,或者在命令中使用完整的路径来替换。将该命令与所有 Nginx 进程的 PID 一起使用,以终止所有正在运行的 Nginx 进程。此命令将启动一个新的 Nginx 进程来重新加载配置文件并重新启动服务器。使用以下命令来终止所有 Nginx 进程(使用上面的 PID 替换。的进程以及它们的 PID。打开命令提示符(CMD)。此命令将列出所有名为。选项来强制终止进程。_windows 怎么关闭nginx
文章浏览阅读2.7k次,点赞2次,收藏7次。包括 Netflix、GitHub 和 WordPress。Nginx 可以用作 Web 服务器、负载均衡器、反向代理和 HTTP 缓存等。_ubuntu安装nginx
文章浏览阅读915次。轻松搭建短域名短链接服务系统,可选权限认证,并自动生成证书认证把nginx的http访问转换为https加密访问,完整步骤和代码。_nginx 短链代理
文章浏览阅读1.1k次,点赞35次,收藏24次。流媒体方案之Nginx——实现物联网视频监控项目Nginx是什么Nginx在流媒体方案中的位置软硬件准备移植编译Nginx运行Ngnix测试流媒体方案浏览器播放_nginx-rtmp-module
文章浏览阅读1.9k次。nginx 配置 wss 协议转发 ws 服务器_nginx 配置wss