12.9 结束upstream请求

当Nginx与上游服务器的交互出错,或者正常处理完来自上游的响应时,就需要结束请求了。这时当然不能调用第11章中介绍的ngx_http_finalize_request方法来结束请求,这样upstream中使用到的资源(如与上游间建立的TCP连接)将无法释放,事实上,upstream机制提供了一个类似的方法ngx_http_upstream_finalize_request用于结束upstream请求,在12.9.1节中将会详细介绍这个方法。除了直接调用ngx_http_upstream_finalize_request方法结束请求以外,还有两种独特的结束请求方法,分别是ngx_http_upstream_cleanup方法和ngx_http_upstream_next方法。

在启动upstream机制时,ngx_http_upstream_cleanup方法会挂载到请求的cleanup链表中(参见图12-2的第3步),这样,HTTP框架在请求结束时就会调用ngx_http_upstream_cleanup方法(参见11.10.2节ngx_http_free_request方法的流程),这保证了ngx_http_upstream_cleanup一定会被调用。而ngx_http_upstream_cleanup方法实际上还是通过调用ngx_http_upstream_finalize_request来结束请求的,如下所示。


static void ngx_http_upstream_cleanup(void*data)

{

ngx_http_request_t*r=data;

ngx_http_upstream_t*u=r->upstream;

……

/最终还是调用ngx_http_upstream_finalize_request方法来结束请求,注意传递的是NGX_DONE参数/

ngx_http_upstream_finalize_request(r,u,NGX_DONE);

}


当处理请求的流程中出现错误时,往往会调用ngx_http_upstream_next方法。例如,在图12-5中,如果在接收上游服务器的包头时出现错误,接下来就会调用该方法,这是因为upstream机制还提供了一个较为灵活的功能:当与上游的交互出现错误时,Nginx并不想立刻认为这个请求处理失败,而是试图多给上游服务器一些机会,可以重新向这台或者另一台上游服务器发起连接、发送请求、接收响应,以避免网络故障。这个功能可以帮助HTTP模块实现简单的负载均衡机制(如最常见的HTTP反向代理模块)。而该功能正是通过ngx_http_upstream_next方法实现的,因为该方法在结束请求之前,会检查ngx_peer_connection_t结构体的tries成员(参见9.3.2节)。tries成员会初始化为每个连接的最大重试次数,每当这个连接与上游服务器出现错误时就会把tries减1。在出错时ngx_http_upstream_next方法首先会检查tries,如果它减到0,才会真正地调用ngx_http_upstream_finalize_request方法结束请求,否则不会结束请求,而是调用ngx_http_upstream_connect方法重新向上游发起请求,如下所示。


static void ngx_http_upstream_next(ngx_http_request_tr,ngx_http_upstream_tu,ngx_uint_t ft_type)

{

……

/*只有向这台上游服务器的重试次数tries减为0时,才会真正地调用ngx_http_upstream_finalize_request方法结束请求,否则会再次试图重新与上游服务器交互,这个功能将帮助感兴趣的HTTP模块实现简单的负载均衡机制。u->conf->next_upstream表示的含义在12.1.3节中已介绍过,它实际上是一个32位

的错误码组合,表示当出现这些错误码时不能直接结束请求,需要向下一台上游服务器再次重发*/

if(u->peer.tries==0||!(u->conf->next_upstream&ft_type))

{

ngx_http_upstream_finalize_request(r,u,status);

return;

}

//如果与上游间的TCP连接还存在,那么需要关闭

if(u->peer.connection){

ngx_close_connection(u->peer.connection);

u->peer.connection=NULL;

}

//重新发起连接,参见12.3节

ngx_http_upstream_connect(r,u);

}


下面来看一下ngx_http_upstream_finalize_request到底做了些什么工作。

ngx_http_upstream_finalize_request方法还是会通过调用HTTP框架提供的ngx_http_finalize_request方法释放请求,但在这之前需要释放与上游交互时分配的资源,如文件句柄、TCP连接等。它的源代码很简单,下面直接列举其源代码说明它所做的工作。


static void ngx_http_upstream_finalize_request(ngx_http_request_t*r,

ngx_http_upstream_t*u,ngx_int_t rc)

{

ngx_time_t*tp;

//将cleanup指向的清理资源回调方法置为NULL空指针

if(u->cleanup){

*u->cleanup=NULL;

u->cleanup=NULL;

}

//释放解析主机域名时分配的资源

if(u->resolved&&u->resolved->ctx){

ngx_resolve_name_done(u->resolved->ctx);

u->resolved->ctx=NULL;

}

if(u->state&&u->state->response_sec){

//设置当前时间为HTTP响应结束时间

tp=ngx_timeofday();

u->state->response_sec=tp->sec-u->state->response_sec;

u->state->response_msec=tp->msec-u->state->response_msec;

if(u->pipe){

u->state->response_length=u->pipe->read_length;

}

}

/表示调用HTTP模块负责实现的finalize_request方法。HTTP模块可能会在upstream请求结束时执行一些操作/

u->finalize_request(r,rc);

/如果使用了TCP连接池实现了free方法,那么调用free方法(如ngx_http_upstream_free_round_robin_peer)释放连接资源/

if(u->peer.free){

u->peer.free(&u->peer,u->peer.data,0);

}

//如果与上游间的TCP连接还存在,则关闭这个TCP连接

if(u->peer.connection){

ngx_close_connection(u->peer.connection);

}

u->peer.connection=NULL;

if(u->store&&u->pipe&&u->pipe->temp_file

&&u->pipe->temp_file->file.fd!=NGX_INVALID_FILE)

{

/如果使用了磁盘文件作为缓存来向下游转发响应,则需要删除用于缓存响应的临时文件/

if(ngx_delete_file(u->pipe->temp_file->file.name.data)

==NGX_FILE_ERROR)

{

ngx_log_error(NGX_LOG_CRIT,r->connection->log,ngx_errno,ngx_delete_file_n"\"%s\"failed",

u->pipe->temp_file->file.name.data);

}

}

/如果已经向下游客户端发送了HTTP响应头部,却出现了错误,那么将会通过下面的ngx_http_send_special(r,NGX_HTTP_LAST)将头部全部发送完毕/

if(u->header_sent&&rc!=NGX_HTTP_REQUEST_TIME_OUT

&&(rc==NGX_ERROR||rc>=NGX_HTTP_SPECIAL_RESPONSE))

{

rc=0;

}

if(rc==NGX_DECLINED){

return;

}

r->connection->log->action="sending to client";

if(rc==0)

{

rc=ngx_http_send_special(r,NGX_HTTP_LAST);

}

/最后还是通过调用HTTP框架提供的ngx_http_finalize_request方法来结束请求/

ngx_http_finalize_request(r,rc);

}