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_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;
staticngx_int_tngx_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请求时,都会调用此方法判断应该继续执行还是拒绝请求
}
2.2nginx事件处理简单介绍
nginx需要将所有关心的fd注册到epoll,添加方法生命如下:
staticngx_int_tngx_epoll_add_event(ngx_event_t*ev,ngx_int_tevent,ngx_uint_tflags);
方法第一个参数是ngx_event_t结构体指针,代表关心的一个读或者写事件;nginx为事件可能会设置一个超时定时器,从而能够处理事件超时情况;定义如下:
ngx_event_handler_pthandler;//函数指针:事件的处理函数
ngx_rbtree_node_ttimer;//超时定时器,存储在红黑树中(节点的key即为事件的超时时间)
unsignedtimedout:1;//记录事件是否超时
};
一般都会循环调用epoll_wait监听所有fd,处理发生的读写事件;epoll_wait是阻塞调用,最后一个参数timeout是超时时间,即最多阻塞timeout时间如果还是没有事件发生,方法会返回;
nginx在设置超时时间timeout时,会从上面说的记录超时定时器的红黑树中查找最近要到时的节点,以此作为epoll_wait的超时时间,如下面代码所示;
ngx_msec_tngx_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;
voidngx_event_expire_timers(void)
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));
}
//每个配置指令主要包含两个字段:名称,解析配置的处理方法
staticngx_command_tngx_http_limit_req_commands[]={
//一般用法:limit_req_zone$binary_remote_addrzone=one:10mrate=1r/s;
//$binary_remote_addr表示远程客户端ip;
//zone配置一个存储空间(需要分配空间记录每个客户端的访问速率,超时空间限制使用lru算法淘汰;注意此空间是在共享内存分配的,所有worker进程都能访问)
//rate表示限制速率,此例为1qps
{ngx_string("limit_req_zone"),
//用法:limit_reqzone=oneburst=5nodelay;
//zone指定使用哪一个共享空间
//超出此速率的请求是直接丢弃吗?burst配置用于处理突发流量,表示最大排队请求数目,当客户端请求速率超过限流速率时,请求会排队等待;而超出burst的才会被直接拒绝;
//nodelay必须与burst一起使用;此时排队等待的请求会被优先处理;否则假如这些请求依然按照限流速度处理,可能等到服务器处理完成后,客户端早已超时
{ngx_string("limit_req"),
//当请求被限流时,日志记录级别;用法:limit_req_log_levelinfo|notice|warn|error;
{ngx_string("limit_req_log_level"),
//当请求被限流时,给客户端返回的状态码;用法:limit_req_status503
{ngx_string("limit_req_status"),
};
staticngx_http_variable_tngx_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},
}
当用户第一次请求时,会新增一条记录(主要记录访问计数、访问时间),以客户端ip地址(配置$binary_remote_addr)的hash值作为key存储在红黑树中(快速查找),同时存储在lru队列中(存储空间不够时,淘汰记录,每次都是从尾部删除);当用户再次请求时,会从红黑树中查找这条记录并更新,同时移动记录到lru队列首部;
3.2.1数据结构
limit_req_zone配置限流算法所需的存储空间(名称及大小),限流速度,限流变量(客户端ip等),结构如下:
ngx_http_limit_req_shctx_t*sh;
ngx_slab_pool_t*shpool;//内存池
ngx_uint_trate;//限流速度(qps乘以1000存储)
ngx_int_tindex;//变量索引(nginx提供了一系列变量,用户配置的限流变量索引)
ngx_str_tvar;//限流变量名称
ngx_http_limit_req_node_t*node;
}ngx_http_limit_req_ctx_t;
//同时会初始化共享存储空间
void*data;//data指向ngx_http_limit_req_ctx_t结构
ngx_shm_zone_init_ptinit;//初始化方法函数指针
void*tag;//指向ngx_http_limit_req_module结构体
};
limit_req配置限流使用的存储空间,排队队列大小,是否紧急处理,结构如下:
ngx_shm_zone_t*shm_zone;//共享存储空间
ngx_uint_tburst;//队列大小
ngx_uint_tnodelay;//有请求排队时是否紧急处理,与burst配合使用(如果配置,则会紧急处理排队请求,否则依然按照限流速度处理)
}ngx_http_limit_req_limit_t;
前面说过用户访问记录会同时存储在红黑树与lru队列中,结构如下:
ngx_msec_tlast;//上次访问时间
ngx_uint_texcess;//当前剩余待处理的请求数(nginx用此实现令牌桶限流算法)
ngx_uint_tcount;//此类记录请求的总数
u_chardata[1];//数据内容(先按照key(hash值)查找,再比较数据内容是否相等)
}ngx_http_limit_req_node_t;
//红黑树节点,key为用户配置限流变量的hash值;
ngx_rbtree_node_t*parent;
ngx_rbtree_trbtree;//红黑树
ngx_rbtree_node_tsentinel;//nil节点
ngx_queue_tqueue;//lru队列
}ngx_http_limit_req_shctx_t;
};
ngx_http_limit_req_ctx_t*ctx;

lr=ngx_queue_data(q,ngx_http_limit_req_node_t,queue);//此方法由ngx_queue_t获取ngx_http_limit_req_node_t结构首地址,实现如下:
#definengx_queue_data(q,type,link)(type*)((u_char*)q-offsetof(type,link))//queue字段地址减去其在结构体中偏移,为结构体首地址
size=offsetof(ngx_rbtree_node_t,color)//新建记录分配内存,计算所需空间大小
+offsetof(ngx_http_limit_req_node_t,data)
node=ngx_slab_alloc_locked(ctx->shpool,size);
lr=(ngx_http_limit_req_node_t*)&node->color;//color为u_char类型,为什么能强制转换为ngx_http_limit_req_node_t指针类型呢?
ngx_memcpy(lr->data,data,len);
ngx_rbtree_insert(&ctx->sh->rbtree,node);
ngx_queue_insert_head(&ctx->sh->queue,&lr->queue);
ngx_busy:请求速率超出限流配置,拒绝请求;
ngx_again:请求通过了当前限流策略校验,继续校验下一个限流策略;
ngx_ok:请求已经通过了所有限流策略的校验,可以执行下一阶段;
//limit,限流策略;hash,记录key的hash值;data,记录key的数据内容;len,记录key的数据长度;ep,待处理请求数目;account,是否是最后一条限流策略
staticngx_int_tngx_http_limit_req_lookup(ngx_http_limit_req_limit_t*limit,ngx_uint_thash,u_char*data,size_tlen,ngx_uint_t*ep,ngx_uint_taccount)
if(hash<node->key){
if(hash>node->key){
//hash值相等,比较数据是否相等
lr=(ngx_http_limit_req_node_t*)&node->color;
rc=ngx_memn2cmp(data,lr->data,len,(size_t)lr->len);
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了)
//待处理数目超过burst(等待队列大小),返回ngx_busy拒绝请求(没有配置burst时,值为0)
if((ngx_uint_t)excess>limit->burst){
if(account){//如果是最后一条限流策略,则更新上次访问时间,待处理请求数目,返回ngx_ok
returnngx_again;//非最后一条限流策略,返回ngx_again,继续校验下一条限流策略
node=(rc<0)?node->left:node->right;
//假如没有查找到节点,需要新建一条记录
//存储空间大小计算方法参照3.2.1节数据结构
size=offsetof(ngx_rbtree_node_t,color)
+offsetof(ngx_http_limit_req_node_t,data)
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
lr=(ngx_http_limit_req_node_t*)&node->color;
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
returnngx_again;//非最后一条限流策略,返回ngx_again,继续校验下一条限流策略
}
第二个参数n,当n==0时,强制删除末尾一条记录,之后再尝试删除一条或两条记录;n==1时,会尝试删除一条或两条记录;代码实现如下:
staticvoidngx_http_limit_req_expire(ngx_http_limit_req_ctx_t*ctx,ngx_uint_tn)
q=ngx_queue_last(&ctx->sh->queue);
lr=ngx_queue_data(q,ngx_http_limit_req_node_t,queue);
//注意:当为0时,无法进入if代码块,因此一定会删除尾部节点;当n不为0时,进入if代码块,校验是否可以删除
ms=(ngx_msec_int_t)(now-lr->last);
//短时间内被访问,不能删除,直接返回
//有待处理请求,不能删除,直接返回
excess=lr->excess-ctx->rate*ms/1000;
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);
}
//计算当前请求还需要排队多久才能处理
delay=ngx_http_limit_req_account(limits,n,&excess,&limit);
if(ngx_handle_read_event(r->connection->read,0)!=ngx_ok){
returnngx_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
delay=excess*1000/ctx->rate;
}
staticvoidngx_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);
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),如下:
limit_req_zone$binary_remote_addrzone=test:10mrate=1r/s;
indexindex.htmlindex.htm;
}
4.2测试burst
1)限速1qps时,超过请求会被直接拒绝,为了应对突发流量,应该允许请求被排队处理;因此配置burst=5,即最多允许5个请求排队等待处理;
limit_req_zone$binary_remote_addrzone=test:10mrate=1r/s;
limit_reqzone=testburst=5;
indexindex.htmlindex.htm;
}
4)ab统计的响应时间见下面,最小响应时间87ms,最大响应时间5128ms,平均响应时间为1609ms:
processing:4615661916.610935084
waiting:4615651916.710925084
total:8716091916.211355128
4.3测试nodelay
1)4.2显示,配置burst后,虽然突发请求会被排队处理,但是响应时间过长,客户端可能早已超时;因此添加配置nodelay,使得nginx紧急处理等待请求,以减小响应时间:
limit_req_zone$binary_remote_addrzone=test:10mrate=1r/s;
limit_reqzone=testburst=5nodelay;
indexindex.htmlindex.htm;
}
4)ab统计的响应时间见下面,最小响应时间85ms,最大响应时间92ms,平均响应时间为88ms:
total:85882.89092
以上就是nginx限流模块源码分析的详细内容,更多请关注主机测评网其它相关文章!
本文来源:国外服务器--nginx限流模块源码分析(nginx限速限流)
本文地址:https://www.idcbaba.com/guowai/4888.html
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 1919100645@qq.com 举报,一经查实,本站将立刻删除。



