12.5 接收上游服务器的响应头部

当请求全部发送给上游服务器时,Nginx开始准备接收来自上游服务器的响应。在图12-3的第7步中设置了由ngx_http_upstream_process_header方法处理上游服务器的响应,而图12-4的第8步也是通过调用该方法接收响应的,本节的内容就在于说明可能会被反复多次调用的ngx_http_upstream_process_header方法。

12.5.1 应用层协议的两段划分方式

在12.1.1节我们已经了解到,只要上游服务器提供的应用层协议是基于TCP实现的,那么upstream机制都是适用的。基于TCP的响应其实就是有顺序的数据流,那么,upstream机制只需要按照接收到的顺序调用HTTP模块来解析数据流不就行了吗?多么简单和清晰!然而,实际上,应用层协议要比这复杂得多,这主要表现在协议长度的不可确定和协议内容的解析上。首先,应用层协议的响应包可大可小,如最小的响应可能只有128B,最大的响应可能达到5GB,如果属于HTTP框架的ngx_http_upstream_module模块在内存中接收到全部响应内容后再调用各个HTTP模块处理响应,就很容易引发OutOfMemory错误,即使没有错误也会因为内存消耗过大从而降低了并发处理能力。如果在磁盘文件中接收全部响应,又会带来大量的磁盘I/O操作,最终大幅提高服务器的负载。其次,对响应中的所有内容都进行解析并无必要(解析操作毕竟对CPU是有消耗的)。例如,从Memcached服务器上下载一幅图片,Nginx只需要解析Memcached协议,并不需要解析图片的内容,对于图片内容,Nginx只需要边接收边转发给客户端即可。

为了解决上述问题,应用层协议通常都会将请求和响应分成两部分:包头和包体,其中包头在前而包体在后。包头相当于把不同的协议包之间的共同部分抽象出来,不同的数据包之间包头都具备相同的格式,服务器必须解析包头,而包体则完全不做格式上的要求,服务器是否解析它将视业务上的需要而定。包头的长度要么是固定大小,要么是限制在一个数值以内(例如,类似Apache这样的Web服务器默认情况下仅接收包头小于4KB的HTTP请求),而包体的长度则非常灵活,可以非常大,也可以为0。对于Nginx服务器来说,在process_header处理包头时,需要开辟的内存大小只要能够容纳包头的长度上限即可,而处理包体时需要开辟的内存大小情况较复杂,可参见12.6节~12.8节。

包头和包体存储什么样的信息完全取决于应用层协议,包头中的信息通常必须包含包体的长度,这是应用层协议分为包头、包体两部分的最主要原因。很多包头还会包含协议版本、请求的方法类型、数据包的序列号等信息,这些是upstream机制并不关心的,它已经在ngx_http_upstream_t结构体中抽象出了process_header方法,由具体的HTTP模块实现的process_header来解析包头。实际上,upstream机制并没有对HTTP模块怎样实现process_header方法进行限制,但如果HTTP模块的目的是实现反向代理,不妨将接收到的包头按照上游的应用层协议与HTTP的关系,把解析出的一些头部适配到ngx_http_upstream_t结构体中的headers_in成员中,这样,upstream机制在图12-5的第8步就会自动地调用ngx_http_upstream_process_headers方法将这些头部设置到发送给下游客户端的HTTP响应包头中。

包体的内容往往较为简单,当HTTP模块希望实现反向代理功能时大都不希望解析包体。这样的话,upstream机制基于这种最常见的需求,把包体的常见处理方式抽象出3类加以实现,12.5.2节中将介绍这3种包体的处理方式。