Nginx的upstream模块

1.相关配置

upstream模块的典型应用是反向代理,这里就以ngx_http_proxy_module模块为例。假定我们有如下这样的实例环境,客户端对服务器80端口的请求都被Nginx Proxy Server转发到另外两个真实的Nginx Web Server实例上进行处理(下图是实验环境,Web Server和Proxy Server都只是Nginx进程,并且运行在同一台服务器):
Nginx的upstream模块
那么,Nginx Proxy Server的核心配置多半是这样:

123456789101112131415161718
Filename : nginx.conf	…http {    …		upstream load_balance {			server localhost:8001;			server localhost:8002;		}				server {			listen 80;			location / {				proxy_buffering off;				proxy_pass http://load_balance;			}		}	}

上面的proxy_buffering off;配置是为了禁用nginx反向代理的缓存功能,保证客户端的每次请求都被转发到后端真实服务器,以便我们每次跟踪分析的nginx执行流程更加简单且完整。而另外两个配置命令upstream和proxy_pass在此处显得更为重要,其中upstream配置命令的回调处理函数为ngx_http_upstream(),该函数除了申请内存、设置初始值等之外,最主要的动作就是切换配置上下文并调用ngx_conf_parse()函数继续进行配置解析:

2.源码解析

12345678
Filename : ngx_http_upstream.c	    pcf = *cf;	    cf->ctx = ctx;	    cf->cmd_type = NGX_HTTP_UPS_CONF;		    rv = ngx_conf_parse(cf, NULL);	…	    if (uscf->servers == NULL) {

进入到upstream配置块内,最主要的配置命令也就是server,其对应的处理函数为ngx_http_upstream_server(),对于每一个后端真实服务器,除了其uri地址外,还有诸如down、weight、max_fails、fail_timeout、backup这样的可选参数,所有这些都需要ngx_http_upstream_server()函数来处理。
在ngx_http_upstream.c的第4173行下个断点,我们可以看到这里给出示例的解析结果:
Nginx的upstream模块

另外一个重要配置命令proxy_pass主要出现在location配置上下文中,而其对应的处理函数为ngx_http_proxy_pass(),抹去该函数内的众多细节,我们重点关注两个赋值语句:

12345678
Filename : ngx_http_proxy_module.c	static char *	ngx_http_proxy_pass(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)	{	…	    clcf->handler = ngx_http_proxy_handler;	…	    plcf->upstream.upstream = ngx_http_upstream_add(cf, &u, 0);

上面片段代码里的第一个赋值语句给当前location的http处理设置回调函数,而第二个赋值语句则是查找(没有找到则会创建,比如如果配置文档中upstream命令出现在proxy_pass命令的后面)其对应的upstream配置,我们这里就一个名为load_balance的upstream,所以找到的配置就是它了:
Nginx的upstream模块

前面曾提到,Nginx将对客户端的http请求处理分为多个阶段,而其中有个NGX_HTTP_FIND_CONFIG_PHASE阶段主要就是做配置查找处理,如果当前请求location设置了upstream,即回调函数指针clcf->handler不为空,则表示对该location的请求需要后端真实服务器来处理:

12345678910111213141516
Filename : ngx_http_core_module.c	ngx_int_t	ngx_http_core_find_config_phase(ngx_http_request_t *r,	    ngx_http_phase_handler_t *ph)	{	…	    ngx_http_update_location_config(r);	…	void	ngx_http_ 大专栏  Nginx的upstream模块update_location_config(ngx_http_request_t *r)	{	…	    if (clcf->handler) {	        r->content_handler = clcf->handler;	    }	}

在其它有location更新的情况下,比如redirect重定向location或named命名location或if条件location等,此时也会调用ngx_http_update_location_config()函数进行location配置更新。我们知道upstream模块的主要功能是产生响应数据,虽然这些响应数据来自后端真实服务器,所以在NGX_HTTP_CONTENT_PHASE 阶段的checker函数ngx_http_core_content_phase()内,我们可以看到在r->content_handler不为空的情况下会优先对r->content_handler函数指针进行回调:

123456789101112
Filename : ngx_http_core_module.c	ngx_int_t	ngx_http_core_content_phase(ngx_http_request_t *r,	    ngx_http_phase_handler_t *ph)	{	…	    if (r->content_handler) {	        r->write_event_handler = ngx_http_request_empty_handler;	        ngx_http_finalize_request(r, r->content_handler(r));	        return NGX_OK;	    }	…

如果r->content_handler不为空,即存在upstream,那么进入处理,注意第1397行直接返回NGX_OK,也即不再调用挂在该阶段的其它模块回调函数,所以说upstream模块的优先级是最高的。根据前面的回调赋值,调用r->content_handler()指针函数,实质上就是执行函数ngx_http_proxy_handler(),直到这里,我们才真正走进upstream代理模块的处理逻辑里。

3.回调函数

对于任何一个Upstream模块而言,最核心的实现主要是7个回调函数,upstream代理模块自然也不例外,它实现并注册了这7个回调函数:

回调指针函数功能upstream代理模块
create_request根据nginx与后端服务器通信协议(比如HTTP、Memcache),将客户端的HTTP请求信息转换为对应的发送到后端服务器的真实请求ngx_http_proxy_create_request 由于nginx与后端服务器通信协议也为HTTP,所以直接拷贝客户端的请求头、请求体(如果有)到变量r->upstream->request_bufs内。
process_header根据nginx与后端服务器通信协议,将后端服务器返回的头部信息转换为对客户端响应的HTTP响应头。ngx_http_proxy_process_status_line 此时后端服务器返回的头部信息已经保存在变量r->upstream->buffer内,将这串字符串解析为HTTP响应头存储到变量r->upstream->headers_in内。
input_filter_init根据前面获得的后端服务器返回的头部信息,为进一步处理后端服务器将返回的响应体做初始准备工作。ngx_http_proxy_input_filter_init 根据已解析的后端服务器返回的头部信息,设置需进一步处理的后端服务器将返回的响应体的长度,该值保存在变量r->upstream->length内。
input_filter正式处理后端服务器返回的响应体ngx_http_proxy_non_buffered_copy_filter 本次收到的响应体数据长度为bytes,数据长度存储在r->upstream->buffer内,把它加入到r->upstream->out_bufs响应数据链等待发送给客户端。
finalize_request正常结束与后端服务器的交互,比如剩余待取数据长度为0或读到EOF等,之后就会调用该函数。由于nginx会自动完成与后端服务器交互的清理工作,所以该函数一般仅做下日志,标识响应正常结束。ngx_http_proxy_finalize_request 记录一条日志,标识正常结束与后端服务器的交互,然后函数返回。
reinit_request对交互重新初始化,比如当nginx发现一台后端服务器出错无法正常完成处理,需要尝试请求另一台后端服务器时就会调用该函数。ngx_http_proxy_reinit_request设置初始值,设置回调指针,处理比较简单。
abort_request异常结束与后端服务器的交互后就会调用该函数。大部分情况下,该函数仅做下日志,标识响应异常结束。ngx_http_proxy_abort_request记录一条日志,标识异常结束与后端服务器的交互,然后函数返回。

上表格中前面5个函数执行的先后次序如下图所示,由于在Client/Proxy/Server之间,一次请求/响应数据可以发送多次(下图中只画出一次就发送完毕的情况),所以下图中对应的函数也可能被执行多次,不过一般情况下,这5个函数执行的先后次序就是这样了。
Nginx的upstream模块

4.总结

这些回调函数如何夹杂到nginx中被调用并不需要完全搞清楚,要写一个Upstream模块,我们只要实现上面提到的这7个函数即可,当然,可以看到最主要的也就是create_request、process_header和input_filter这三个回调,它们实现从HTTP协议到Nginx与后端服务器之间交互协议的来回转换,使得在用户看来,他访问的就是一台功能完整的Web服务器,而也许事实上,显示在他面前的数据来自Memcache或别的什么服务器。

参考文献:

nginx核心讲解

相关推荐