这里讲计算nginx的首包响应时间,那首先首包是什么意思呢?它表示从nginx收到GET请求到发送第一个数据包的这段时间。这段时间包括了nginx协议分析到准备数据的这段时间,在CDN系统上它可作为一项质量监控指标。例如有可能服务器磁盘io过高导致准备数据的时间过长,也有可能本地缓存MISS需要回源等。那么,我们怎么设计一个HTTP模块来计算这个首包响应时间呢?NGINX HTTP框架依据常见的处理流程划分了11个HTTP请求处理阶段,我们自己设计模块的时候通常是在相应的阶段添加自己的请求处理方法,这里也不例外。当HTTP框架在建立的TCP连接上接收到客户发送的完整HTTP请求头部时,首先会执行NGX_HTTP_POST_READ_PHASE阶段的checker方法。任意HTTP模块需要在NGX_HTTP_POST_READ_PHASE阶段处理HTTP请求时,必然首先在ngx_http_core_main_conf_t结构体中的phases[NGX_HTTP_POST_READ_PHASE]动态数组中添加自己实现的ngx_http_handler_pt方法。例如,nginx realip模块的ngx_http_realip_handlers方法就是添加在这个阶段,在nginx作为反向代理时,它会获取用户的真实ip,并将该ip添加到HTTP头部,这样proxy后端的服务器通过解析HTTP头部字段就能拿到用户的真实ip了。nginx realip它在postconfiguration方法中是这样将自己定义的ngx_http_handle_pt处理方法添加到HTTP框架中去的,如下所示:
//该方法就是postfiguration接口的实现
static ngx_int_t
ngx_http_realip_init(ngx_conf_t *cf)
{
ngx_http_handler_pt *h;
ngx_http_core_main_conf_t *cmcf;
cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
h = ngx_array_push(&cmcf->phases[NGX_HTTP_POST_READ_PHASE].handlers);
if (h == NULL) {
return NGX_ERROR;
}
*h = ngx_http_realip_handler;
h = ngx_array_push(&cmcf->phases[NGX_HTTP_PREACCESS_PHASE].handlers);
if (h == NULL) {
return NGX_ERROR;
}
*h = ngx_http_realip_handler;
return NGX_OK;
}
我们要计算nginx首包响应的时间,也就是要计算收到完整请求到发送首包的这段时间,因此可以考虑在NGX_HTTP_POST_READ_PHASE阶段添加自定义方法,先初始化一个时间,这就是开始时间。那么,我们在哪里去标记发送首包的这个时间呢?这个需要先了解一下nginx发送响应的流程。nginx明确地将HTTP响应分为两个部分:HTTP头部和HTTP包体,它调用ngx_http_send_header发送HTTP头部,如果还有包体要发送将调用ngx_http_output_filter发送HTTP包体。我们标记首包发送时间理应在发送头部响应的逻辑里,此时普通HTTP模块已经处理请求完毕。以下是ngx_http_send_header代码:
ngx_int_t
ngx_http_send_header(ngx_http_request_t *r)
{
if (r->post_action) {
return NGX_OK;
}
if (r->header_sent) {
ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
"header already sent");
return NGX_ERROR;
}
if (r->err_status) {
r->headers_out.status = r->err_status;
r->headers_out.status_line.len = 0;
}
return ngx_http_top_header_filter(r);
}
发送头部时调用了ngx_http_top_header_filter,HTTP框架对其定义如下:
extern ngx_http_output_header_filter_pt ngx_http_top_header_filter;
typedef ngx_int_t (*ngx_http_output_header_filter_pt) (ngx_http_request_t *r);
ngx_http_output_header_filter_pt是HTTP过滤模块处理HTTP头部的方法原型,HTTP框架将所有过滤模块组成一个过滤链表,而ngx_http_top_header_filter为该链表头。在发送HTTP头部时,从ngx_http_top_header_filter指针指向的过滤模块开始执行。我们要做的就是新增一个过滤模块,目的在于记录首包发送的时间。计算首包响应的模块代码如下:
#include
#include
#include
typedef struct {
ngx_flag_t onoff;
} ngx_http_first_byte_loc_conf_t;
typedef struct {
time_t first_byte_sec; /* head filter phase */
ngx_msec_t first_byte_msec; /* head filter phase */
} ngx_http_first_byte_ctx_t;
static ngx_command_t ngx_http_first_byte_commands[] = {
{
ngx_string("first_byte"),
NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1,
ngx_conf_set_flag_slot,
NGX_HTTP_LOC_CONF_OFFSET,
offsetof(ngx_http_first_byte_loc_conf_t, onoff),
NULL
},
ngx_null_command
};
static ngx_int_t ngx_http_first_byte_init(ngx_conf_t* cf);
static void* ngx_http_first_byte_create_loc_conf(ngx_conf_t* cf);
static char* ngx_http_first_byte_merge_loc_conf(ngx_conf_t* cf, void* parent, void* child);
static ngx_int_t ngx_http_first_byte_add_variable(ngx_conf_t* cf);
static ngx_int_t ngx_http_first_byte_var_get_handler(ngx_http_request_t* r,
ngx_http_variable_value_t* v, uintptr_t data);
static ngx_http_output_header_filter_pt ngx_http_next_header_filter;
static ngx_int_t ngx_http_first_byte_post_read_phase_handler(ngx_http_request_t* r);
static ngx_http_module_t ngx_http_first_byte_module_ctx = {
ngx_http_first_byte_add_variable, /* preconfiguration */
ngx_http_first_byte_init, /* postconfiguration */
NULL, /* create main configuration */
NULL, /* init main configuration */
NULL, /* create server configuration */
NULL, /* merge server configuration */
ngx_http_first_byte_create_loc_conf, /* create location configuration */
ngx_http_first_byte_merge_loc_conf, /* merge location configuration */
};
ngx_module_t ngx_http_first_byte_module = {
NGX_MODULE_V1,
&ngx_http_first_byte_module_ctx, /* module context */
ngx_http_first_byte_commands, /* module directives */
NGX_HTTP_MODULE, /* module type */
NULL, /* init master */
NULL, /* init module */
NULL, /* init process */
NULL, /* init thread */
NULL, /* exit thread */
NULL, /* exit process */
NULL, /* exit master */
NGX_MODULE_V1_PADDING
};
static ngx_int_t ngx_http_first_byte_add_variable(ngx_conf_t* cf) {
ngx_http_variable_t* var = NULL;
ngx_str_t str = ngx_string("first_byte");
var = ngx_http_add_variable(cf, &str, NGX_HTTP_VAR_CHANGEABLE);
if (var == NULL) {
return NGX_ERROR;
}
var->get_handler = ngx_http_first_byte_var_get_handler;
return NGX_OK;
}
static ngx_int_t ngx_http_first_byte_var_get_handler(ngx_http_request_t* r,
ngx_http_variable_value_t* v, uintptr_t data) {
ngx_http_first_byte_ctx_t* ctx = ngx_http_get_module_ctx(r, ngx_http_first_byte_module);
if (!ctx || ctx->first_byte_sec == 0) {
v->valid = 0;
v->not_found = 1;
return NGX_OK;
}
u_char* p = ngx_pcalloc(r->pool, 20);
v->len = ngx_sprintf(p, "%l", (ctx->first_byte_sec - r->start_sec) * 1000 +
(ctx->first_byte_msec - r->start_msec)) - p;
v->data = p;
v->valid = 1;
v->not_found = 0;
return NGX_OK;
}
/*
响应头过滤方法中设置first_byte_sec,first_byte_msec时间
*/
static ngx_int_t ngx_http_first_byte_header_filter(ngx_http_request_t* r) {
ngx_http_first_byte_loc_conf_t* fblcf = ngx_http_get_module_loc_conf(r,
ngx_http_first_byte_module);
if (!fblcf->onoff) {
return ngx_http_next_header_filter(r);
}
ngx_time_t* tp = NULL;
tp = ngx_timeofday();
ngx_http_first_byte_ctx_t* ctx = ngx_http_get_module_ctx(r, ngx_http_first_byte_module);
if (!ctx) {
return ngx_http_next_header_filter(r);
}
ctx->first_byte_sec = tp->sec; //开始发送请求响应头,计时
ctx->first_byte_msec = tp->msec;
return ngx_http_next_header_filter(r);
}
static ngx_int_t ngx_http_first_byte_post_read_phase_handler(ngx_http_request_t* r) {
// get config value
ngx_http_first_byte_loc_conf_t* fblcf = ngx_http_get_module_loc_conf(r, ngx_http_first_byte_module);
// if == 0 return next handler
if (!fblcf->onoff) {
return NGX_DECLINED; //当前处理方法执行完毕,按照顺序执行下一个ngx_http_handler_pt方法
}
ngx_time_t* tp = NULL;
tp = ngx_timeofday();
// maollc memory form current ngx_http_request mem pool
// so when the request is closed,the mem is GCed too
//必须在当前请求的内存池r->pool中分配上下文结构体,这样请求结束的时候占用的结构体才会释放
ngx_http_first_byte_ctx_t* ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_first_byte_ctx_t));
ctx->first_byte_sec = 0;
ctx->first_byte_msec = 0;
ngx_http_set_ctx(r, ctx, ngx_http_first_byte_module);
return NGX_DECLINED;
}
static ngx_int_t ngx_http_first_byte_init(ngx_conf_t* cf) {
ngx_http_handler_pt* h = NULL;
ngx_http_core_main_conf_t* cmcf = NULL;
ngx_http_next_header_filter = ngx_http_top_header_filter;
ngx_http_top_header_filter = ngx_http_first_byte_header_filter;
cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
h = ngx_array_push(&cmcf->phases[NGX_HTTP_POST_READ_PHASE].handlers);
if (h == NULL) {
return NGX_ERROR;
}
*h = ngx_http_first_byte_post_read_phase_handler;
return NGX_OK;
}
static void* ngx_http_first_byte_create_loc_conf(ngx_conf_t* cf) {
ngx_http_first_byte_loc_conf_t* fblcf = ngx_pcalloc(cf->pool,
sizeof(ngx_http_first_byte_loc_conf_t));
fblcf->onoff = NGX_CONF_UNSET;
return fblcf;
}
static char* ngx_http_first_byte_merge_loc_conf(ngx_conf_t* cf, void* parent, void* child) {
ngx_http_first_byte_loc_conf_t* prev = (ngx_http_first_byte_loc_conf_t*) parent;
ngx_http_first_byte_loc_conf_t* conf = (ngx_http_first_byte_loc_conf_t*) child;
ngx_conf_merge_value(conf->onoff, prev->onoff, 0);
return NGX_CONF_OK;
}