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 }, ………… }