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);
}