12.7.2 转发响应的包体

当接收到上游服务器的响应时,将会由ngx_http_upstream_process_non_buffered_upstream方法处理连接上的这个读事件,该方法比较简单,下面直接列举源代码说明其流程。


static void ngx_http_upstream_process_non_buffered_upstream(ngx_http_request_tr,ngx_http_upstream_tu)

{

ngx_connection_t*c;

//获取Nginx与上游服务器间的TCP连接c

c=u->peer.connection;

//如果读取响应超时(超时时间为read_timeout),则需要结束请求

if(c->read->timedout){

//ngx_http_upstream_finalize_request方法可参见12.9.3节

ngx_http_upstream_finalize_request(r,u,0);

return;

}

/这个方法才是真正决定以固定内存块作为缓存时如何转发响应的,注意,传递的第2个参数是0/

ngx_http_upstream_process_non_buffered_request(r,0);

}


可以看到,实际接收上游服务器响应的其实是ngx_http_upstream_process_non_buffered_request方法,先不着急看它的实现,先来看看向下游客户端发送响应时调用的ngx_http_upstream_process_non_buffered_downstream方法是怎样实现的,如下所示。


static void ngx_http_upstream_process_non_buffered_downstream(ngx_http_request_t*r)

{

ngx_event_t*wev;

ngx_connection_t*c;

ngx_http_upstream_t*u;

//注意,这个c是Nginx与客户端之间的TCP连接

c=r->connection;

u=r->upstream;

wev=c->write;

/如果发送超时,那么同样要结束请求,超时时间就是nginx.conf文件中的send_timeout配置项/

if(wev->timedout){

c->timedout=1;

//注意,结束请求时传递的参数是NGX_HTTP_REQUEST_TIME_OUT

ngx_http_upstream_finalize_request(r,u,NGX_HTTP_REQUEST_TIME_OUT);

return;

}

//同样调用该方法向客户端发送响应包体,注意,传递的第2个参数是1

ngx_http_upstream_process_non_buffered_request(r,1);

}


无论是接收上游服务器的响应,还是向下游客户端发送响应,最终调用的方法都是ngx_http_upstream_process_non_buffered_request,唯一的区别是该方法的第2个参数不同,当需要读取上游的响应时传递的是0,当需要向下游发送响应时传递的是1。下面先看看该方法到底做了哪些事情,如图12-8所示。

12.7.2 转发响应的包体 - 图1

图 12-8 ngx_http_upstream_process_non_buffered_request方法的流程图

图12-8中的do_write变量就是ngx_http_upstream_process_non_buffered_request方法中的第2个参数,当然,首先它还会有一个初始化,如下所示。


do_write=do_write||u->length==0;


这里的length变量表示还需要接收的上游包体的长度,当length为0时,说明不再需要接收上游的响应,那只能继续向下游发送响应,因此,do_write只能为1。do_write标志位表示本次是否向下游发送响应。下面详细解释图12-8中的每个步骤。

1)如果do_write标志位为1,则跳到第2步开始向下游发送响应;如果do_write为0,则表示需要由上游读取响应,这时跳到第6步执行。注意,在图12-8中,这一步是在一个大循环中执行的,也就是说,与上、下游间的通信可能反复执行。

2)首先检查缓存中来自上游的响应包体,是否还有未转发给下游的。这个检查过程很简单,因为每当在缓冲区中接收到上游的响应时,都会调用input_filter方法来处理。当HTTP模块没有实现该方法时,我们就会使用12.6.2节介绍过的ngx_http_upstream_non_buffered_filter方法来处理响应,该方法会在out_bufs链表中增加ngx_buf_t缓冲区(没有分配实际的内存)指向buffer中接收到的响应。因此,在向下游发送包体时,直接发送out_bufs缓冲区指向的内容即可,每当发送成功时则会在下面的第4步中更新out_bufs缓冲区,从而将已经发送出去的ngx_buf_t成员回收到free_bufs链表中。

事实上,检查是否有内容需要转发给下游的代码是这样的:


if(u->out_bufs||u->busy_bufs){……}


可能有人会奇怪,为什么除了out_bufs缓冲区链表以外还要检查busy_bufs呢?这是因为在第3步向下游发送out_bufs指向的响应时,未必可以一次发送完。这时,在第4步中,会使用busy_bufs指向out_bufs中的内容,同时将out_bufs置为空,使得它在继续处理接收到的响应包体的ngx_http_upstream_non_buffered_filter方法中指向新收到的响应。因此,只有out_bufs和busy_bufs链表都为空时,才表示没有响应需要转发到下游,这时跳到第5步执行,否则跳到第2步向下游发送响应。

3)调用ngx_http_output_filter方法向下游发送out_bufs指向的内容,其代码如下。


rc=ngx_http_output_filter(r,u->out_bufs);


读者在这里可能会有疑问,在busy_bufs不为空时,不是也有内容要发送吗?注意,busy_bufs指向的是上一次ngx_http_output_filter未发送完的缓存,这时请求ngx_http_request_t结构体中的out缓冲区已经保存了它的内容,不需要再次发送busy_bufs了。

4)调用ngx_chain_update_chains方法更新上文说过的free_bufs、busy_bufs、out_bufs这3个缓冲区链表,它们实际上做了以下3件事情。

❑清空out_bufs链表。

❑把out_bufs中已经发送完的ngx_buf_t结构体清空重置(即把pos和last成员指向start),同时把它们追加到free_bufs链表中。

❑如果out_bufs中还有未发送完的ngx_buf_t结构体,那么添加到busy_bufs链表中。这一步与ngx_http_upstream_non_buffered_filter方法的执行是对应的。

5)当busy_bufs链表为空时,表示到目前为止需要向下游转发的响应包体都已经全部发送完了(也就是说,ngx_http_request_t结构体中的out缓冲区都发送完了),这时将把buffer接收缓冲区清空(pos和last成员指向start),这样,buffer接收缓冲区中的内容释放后,才能继续接收更多的响应包体。

6)获取buffer缓冲区中还有多少剩余空间,即:


size=u->buffer.end-u->buffer.last;


这里获取的size就是第7步recv方法能够接收的最大字节数。

当size大于0,且与上游的连接上确实有可读事件时(检查读事件的ready标志位),就会跳到第7步开始接收响应,否则直接跳到10步准备结束本次调度中的转发动作。

7)调用recv方法将上游的响应接收到buffer缓冲区中。检查recv的返回值,如果返回正数,则表示确实接收到响应,跳到第8步处理接收到的包体;如果返回NGX_AGAIN,则表示期待epoll下次有读事件时再继续调度,这时跳到第10步执行;如果返回0,则表示上游服务器关闭了连接,跳到第9步执行。

8)调用input_filter方法处理包体(参考12.6.2节的默认处理方法)。

9)执行到这一步表示读取到了来自上游的响应,这时设置do_write标志位为1,同时跳到第1步准备向下游转发刚收到的响应。

10)调用ngx_handle_write_event方法将Nginx与下游之间连接上的写事件添加到epoll中。

11)调用ngx_add_timer方法将Nginx与下游之间连接上的写事件添加到定时器中,超时时间就是配置文件中的send_timeout配置项。

12)调用ngx_handle_read_event方法将Nginx与上游服务器之间的连接上的读事件添加到epoll中。

13)调用ngx_add_timer方法将Nginx与上游服务器之间连接上的读事件添加到定时器中,超时时间就是ngx_http_upstream_conf_t配置结构体中的read_timeout成员。

阅读完第11章,读者应该很熟悉Nginx读/写事件的处理过程了。另外,理解转发包体这一过程最关键的是弄清楚缓冲区的用法,特别是分配了实际内存的buffer缓冲区与仅仅负责指向buffer缓冲区内容的3个链表(out_bufs、busy_bufs、free_bufs)之间的关系,这样就对这种转发过程的优缺点非常清楚了。如果下游网速慢,那么有限的buffer缓冲区就会降低上游的发送响应速度,可能对上游服务器带来高并发压力。