12.8 以上游网速优先来转发响应

如果上游服务器向Nginx发送响应的速度远快于下游客户端接收Nginx转发响应时的速度,这时可以通过将ngx_http_upstream_conf_t配置结构体中的buffering标志位设为1,允许upstream机制打开更大的缓冲区来缓存那些来不及向下游转发的响应,允许当达到内存构成的缓冲区上限时以磁盘文件的形式来缓存来不及向下游转发的响应。什么是更大的缓冲区呢?由12.7节我们知道,当buffering标志位为0时,将使用ngx_http_upstream_conf_t配置结构体中的buffer_size指定的一块固定大小的缓冲区来转发响应,而当buffering为1时,则使用bufs成员指定的内存缓冲区(最多拥有bufs.num个,每个缓冲区大小固定为bufs.size字节)来转发响应,当上游响应占满所有缓冲区时,使用最大不超过max_temp_file_size字节的临时文件来缓存响应。

事实上,官方发布的ngx_http_proxy_module反向代理模块默认配置下就是使用这种方式来转发上游服务器响应的,由于它涉及了多个内存缓冲区的配合问题,以及临时磁盘文件的使用,导致它的实现方式异常复杂,12.8.1节介绍的ngx_event_pipe_t结构体是该转发方式的核心结构体,需要基于它来理解转发流程。

这种转发响应方式集成了Nginx的文件缓存功能,本节将只讨论纯粹转发响应的流程,不会涉及文件缓存部分(以临时文件缓存响应并不属于文件缓存,因为临时文件在请求结束后会被删除)。

12.8.1 ngx_event_pipe_t结构体的意义

如果将ngx_http_upstream_conf_t配置结构体的buffering标志位设置为1,那么ngx_event_pipe_t结构体必须要由HTTP模块创建。

注意 upstream中的pipe成员默认指向NULL空指针,而且upstream机制永远不会为它自动实例化,因此,必须由使用upstream的HTTP模块为pipe分配内存。

ngx_event_pipe_t结构体维护着上下游间转发的响应包体,它相当复杂。例如,缓冲区链表ngx_chain_t类型的成员就定义了6个(包括free_raw_bufs、in、out、free、busy、preread_bufs),为什么要用如此复杂的数据结构支撑看似简单的转发过程呢?这是因为Nginx的宗旨就是高效率,所以它绝不会把相同内容复制到两块内存中,而同一块内存如果既要用于接收上游发来的响应,又要准备向下游发送,很可能还要准备写入临时文件中,这就带来了很高的复杂度,ngx_event_pipe_t结构体的任务就在于解决这个问题。

理解这个结构体中各个成员的含义将会帮助我们弄清楚buffering为1时转发响应的流程,特别是可以弄清楚Nginx绝不复制重复内存的高效做法是如何实现的。当然,我们也可以先跳到12.8.2节综合理解这种转发方式下的运行机制,再针对流程中遇到的ngx_event_pipe_t结构体中的成员返回到本节来查询其意义。下面看一下它各个成员的意义。


typedef struct ngx_event_pipe_s ngx_event_pipe_t;

//处理接收自上游的包体的回调方法原型

typedef ngx_int_t(ngx_event_pipe_input_filter_pt)(ngx_event_pipe_tp,ngx_buf_t*buf);

//向下游发送响应的回调方法原型

typedef ngx_int_t(ngx_event_pipe_output_filter_pt)(voiddata,ngx_chain_t*chain);

struct ngx_event_pipe_s{

//Nginx与上游服务器间的连接

ngx_connection_t*upstream;

//Nginx与下游客户端间的连接

ngx_connection_t*downstream;

/直接接收自上游服务器的缓冲区链表,注意,这个链表中的顺序是逆序的,也就是说,链表前端的ngx_buf_t缓冲区指向的是后接收到的响应,而后端的ngx_buf_t缓冲区指向的是先接收到的响应。因此,free_raw_bufs链表仅在接收响应时使用/

ngx_chain_t*free_raw_bufs;

/表示接收到的上游响应缓冲区。通常,in链表是在input_filter方法中设置的,可参考ngx_event_pipe_copy_input_filter方法,它会将接收到的缓冲区设置到in链表中/

ngx_chain_t*in;

//指向刚刚接收到的一个缓冲区

ngx_chain_t**last_in;

/保存着将要发送给客户端的缓冲区链表。在写入临时文件成功时,会把in链表中写入文件的缓冲区添加到out链表中/

ngx_chain_t*out;

//指向刚加入out链表的缓冲区,暂无实际意义

ngx_chain_t**last_out;

//等待释放的缓冲区

ngx_chain_t*free;

/设置busy缓冲区中待发送的响应长度触发值,当达到busy_size长度时,必须等待busy缓冲区发送了足够的内容,才能继续发送out和in缓冲区中的内容/

ssize_t busy_size;

/*表示上次调用ngx_http_output_filter方法发送响应时没有发送完的缓冲区链表。这个链表中的缓

冲区已经保存到请求的out链表中,busy仅用于记录还有多大的响应正等待发送*/

ngx_chain_t*busy;

/处理接收到的来自上游服务器的缓冲区。一般使用upstream机制默认提供的ngx_event_pipe_copy_input_filter方法作为input_filter/

ngx_event_pipe_input_filter_pt input_filter;

/用于input_filter方法的成员,一般将它设置为ngx_http_request_t结构体的地址/

void*input_ctx;

/表示向下游发送响应的方法,默认使用ngx_http_output_filter方法作为output_filter/

ngx_event_pipe_output_filter_pt output_filter;

//指向ngx_http_request_t结构体

void*output_ctx;

//标志位,read为1时表示当前已经读取到上游的响应

unsigned read:1;

/标志位,为1时表示启用文件缓存。本章描述的场景都忽略了文件缓存,也就是默认cacheable值为0/

unsigned cacheable:1;

//标志位,为1时表示接收上游响应时一次只能接收一个ngx_buf_t缓冲区

unsigned single_buf:1;

/标志位,为1时一旦不再接收上游响应包体,将尽可能地立刻释放缓冲区。所谓尽可能是指,一旦这个缓冲区没有被引用,如没有用于写入临时文件或者用于向下游客户端释放,就把缓冲区指向的内存释放给pool内存池/

unsigned free_bufs:1;

/提供给HTTP模块在input_filter方法中使用的标志位,表示Nginx与上游间的交互已结束。如果HTTP模块在解析包体时,认为从业务上需要结束与上游间的连接,那么可以把upstream_done标志位置为1/

unsigned upstream_done:1;

/Nginx与上游服务器之间的连接出现错误时,upstream_error标志位为1,一般当接收上游响应超时,或者调用recv接收出现错误时,就会把该标志位置为1/

unsigned upstream_error:1;

/表示与上游的连接状态。当Nginx与上游的连接已经关闭时,upstream_eof标志位为1/

unsigned upstream_eof:1;

/表示暂时阻塞住读取上游响应的流程,期待通过向下游发送响应来清理出空闲的缓冲区,再用空出的缓冲区接收响应。也就是说,upstream_blocked标志位为1时会在ngx_event_pipe方法的循环中先调用ngx_event_pipe_write_to_downstream方法发送响应,然后再次调用ngx_event_pipe_read_upstream方法读取上游响应/

unsigned upstream_blocked:1;

//downstream_done标志位为1时表示与下游间的交互已经结束,目前无意义

unsigned downstream_done:1;

/*Nginx与下游客户端间的连接出现错误时,downstream_error标志位为1。在代码中,一般是向

下游发送响应超时,或者使用ngx_http_output_filter方法发送响应却返回NGX_ERROR时,把downstream_error标志位设为1*/

unsigned downstream_error:1;

/cyclic_temp_file标志位为1时会试图复用临时文件中曾经使用过的空间。不建议将cyclic_temp_file设为1。它是由ngx_http_upstream_conf_t配置结构体中的同名成员赋值的/

unsigned cyclic_temp_file:1;

//表示已经分配的缓冲区数目,allocated受到bufs.num成员的限制

ngx_int_t allocated;

/bufs记录了接收上游响应的内存缓冲区大小,其中bufs.size表示每个内存缓冲区的大小,而bufs.num表示最多可以有num个接收缓冲区/

ngx_bufs_t bufs;

//用于设置、比较缓冲区链表中ngx_buf_t结构体的tag标志位

ngx_buf_tag_t tag;

//已经接收到的上游响应包体长度

off_t read_length;

/与ngx_http_upstream_conf_t配置结构体中的max_temp_file_size含义相同,同时它们的值也是相等的,表示临时文件的最大长度/

off_t max_temp_file_size;

/与ngx_http_upstream_conf_t配置结构体中的temp_file_write_size含义相同,同时它们的值也是相等的,表示一次写入文件时的最大长度/

ssize_t temp_file_write_size;

//读取上游响应的超时时间

ngx_msec_t read_timeout;

//向下游发送响应的超时时间

ngx_msec_t send_timeout;

//向下游发送响应时,TCP连接中设置的send_lowat“水位”

ssize_t send_lowat;

//用于分配内存缓冲区的连接池对象

ngx_pool_t*pool;

//用于记录日志的ngx_log_t对象

ngx_log_t*log;

//表示在接收上游服务器响应头部阶段,已经读取到的响应包体

ngx_chain_t*preread_bufs;

//表示在接收上游服务器响应头部阶段,已经读取到的响应包体长度

size_t preread_size;

//仅用于缓存文件的场景,本章不涉及,故不再详述该缓冲区

ngx_buf_t*buf_to_file;

//存放上游响应的临时文件,最大长度由max_temp_file_size成员限制

ngx_temp_file_t*temp_file;

//已使用的ngx_buf_t缓冲区数目

int num;

};


注意,ngx_event_pipe_t结构体仅用于转发响应。