12.7 以下游网速优先来转发响应

转发上游服务器的响应到下游客户端,这项工作必然是由上游事件来驱动的。因此,以下游网速优先实际上只是意味着需要开辟一块固定长度的内存作为缓冲区。在图12-5的第9步中会调用ngx_http_upstream_send_response方法向客户端转发响应,在该方法中将会判断buffering标志位,如果buffering为1,则表明需要打开缓冲区,这时将会优先考虑上游网速,尽可能多地接收上游服务器的响应到内存或者磁盘文件中;而如果buffering为0,则只开辟固定大小的缓冲区内存,在接收上游服务器的响应时如果缓冲区已满则暂停接收,等待缓冲区中的响应发送给客户端后缓冲区会自然清空,于是就可以继续接收上游服务器的响应了。这种设计的好处是没有使用大量内存,这对提高并发连接是有好处的,同时也没有使用磁盘文件,这对降低服务器负载、某些情况下提高请求处理能力也是有益的。

本节我们讨论的正是buffering标志位为0时的转发响应方式,事实上,这时使用的缓冲区也是接收上游服务器头部时所用的内存,其大小由ngx_http_upstream_conf_t配置结构体中的buffer_size配置指定。

注意 buffering标志位的值其实可以根据上游服务器的响应头部而改变。在12.1.3节中我们介绍过change_buffering标志位,当它的值为1时,如果process_header方法解析出X-Accel-Buffering头部并设置到headers_in结构体中后,将根据该头部的值改变buffering标志位。当X-Accel-Buffering头部值为yes时,对于本次请求而言,buffering相当于重设为1,如果头部值为no,则相当于buffering改为0,除此以外的头部值将不产生作用(参见ngx_http_upstream_process_buffering方法)。因此,转发响应时究竟是否需要打开缓存,可以在运行时根据请求的不同而灵活变换。

12.7.1 转发响应的包头

转发响应包头这一动作是在ngx_http_upstream_send_response方法中完成的,无论buffering标志位是否为0,都会使用该方法来发送响应的包头,图12-7和图12-9共同构成了ngx_http_upstream_send_response方法的完整流程。先来看一下图12-7,它描述了单一缓冲区下是如何转发包头到客户端,以及为转发包体做准备的。

12.7 以下游网速优先来转发响应 - 图1

图 12-7 buffering标志位为0时发送响应包头的流程

因为转发响应包头这一过程并不存在反复调用的问题,所以图12-7中主要完成了两项工作:将12.5节中解析出的包头发送给下游的客户端、设置转发包体的处理方法。下面详细解释图12-7描述的11个步骤。

1)调用ngx_http_send_header方法向下游的客户端发送HTTP包头。在接收上游服务器的响应包头时,在图12-5的第6步中,HTTP模块会通过process_header方法解析包头,并将解析出的值设置到ngx_http_upstream_t结构体的headers_in成员中,而在第8步中,ngx_http_upstream_process_headers方法则会把headers_in中的头部设置到将要发送给客户端的headers_out结构体中,ngx_http_send_header方法就是用来把这些包头发送给客户端的。这一步同时会将header_sent标志位置为1(header_sent标志位在12.4节中发送请求到上游服务器时会使用)。

2)如果客户端的请求中有HTTP包体,而且曾经调用过11.8.1节中的ngx_http_read_client_request_body方法接收HTTP包体并把包体存放在了临时文件中,这时就会调用ngx_pool_run_cleanup_file方法清理临时文件。为什么要在这一步清理临时文件呢?因为上游服务器发送响应时可能会使用到临时文件,之后收到响应解析响应包头时也不可以清理临时文件,而一旦开始向下游客户端转发HTTP响应时,则意味着肯定不会再需要客户端请求的包体了,这时可以关闭、转移或者删除临时文件,具体动作由HTTP模块实现的hander回调方法决定。

3)如果HTTP模块没有实现过滤包体的input_filter方法,则再把12.6.2节介绍过的默认的ngx_http_upstream_non_buffered_filter方法作为处理包体的方法,它的工作就在于使用out_bufs链表指向接收到的buffer缓冲区内容。在12.7.2节中将会综合介绍它的作用。

4)设置读取上游服务器响应的方法为ngx_http_upstream_process_non_buffered_upstream

5)将ngx_http_upstream_process_non_buffered_downstream设置为向下游客户端发送包体的方法,也就是把请求ngx_http_request_t中的write_event_handler设置为这个方法,这样,一旦TCP连接上可以向下游客户端发送数据时,会通过ngx_http_handler方法最终调用到ngx_http_upstream_process_non_buffered_downstream来发送响应包体。

6)调用HTTP模块实现的input_filter_init方法(当HTTP模块没有实现input_filter方法时,它是默认任何事情也不做的ngx_http_upstream_non_buffered_filter_init方法),为input_filter方法处理包体做初始化准备。

检测buffer缓冲区在解析完包头后,是否还有已经接收到的包体(实际上就是检查buffer缓冲区中的last指针是否等于pos指针)。如果已经接收到包体,则跳到第7步执行;如果没有接收到包体,则跳到第9步执行。

7)调用input_filter方法处理包体。

8)调用ngx_http_upstream_process_non_buffered_downstream方法把本次接收到的包体向下游客户端发送。

9)将buffer缓冲区清空,其实就是执行下面两行语句:


u->buffer.pos=u->buffer.start;

u->buffer.last=u->buffer.start;


pos指针一般指向未经处理的响应,而last指针一般指向刚接收到的响应,这时把它们全部设为指向缓冲区起始地址的start指针,即表示清空缓冲区。

10)调用ngx_http_send_special方法,如下所示。


if(ngx_http_send_special(r,NGX_HTTP_FLUSH)==NGX_ERROR){

ngx_http_upstream_finalize_request(r,u,0);

return;

}


NGX_HTTP_FLUSH标志位意味着如果请求r的out缓冲区中依然有等待发送的响应,则“催促”着发送出它们。

11)如果与上游服务器的连接上有可读事件,则调用ngx_http_upstream_process_non_buffered_upstream方法处理响应;否则,当前流程结束,将控制权交还给Nginx框架。

以上步骤提到的下游处理方法ngx_http_upstream_process_non_buffered_downstream和上游处理方法ngx_http_upstream_process_non_buffered_upstream都将在下文中介绍。