一、upstream模块
1、upstream模块接口
从本质上说,upstream也属于handler,只是它不产生自己的内容,而是通过请求后端服务器得到内容。请求并取得响应内容的整个过程已经被封装到Nginx内部,所以upstream模块只需要开发若干回调函数,完成构造请求和解析响应等具体的工作。这些回调函数具体如下:
(1)create_request
生成发送到后端服务器的请求缓冲链,在初始化upstream 时使用。
(2)reinit_request
在某台后端服务器出错的情况,Nginx会尝试另一台后端服务器。 Nginx选定新的服务器以后,会先调用此函数,以重新初始化upstream模块的工作状态,然后再次进行upstream连接。
(3)process_header
处理后端服务器返回的信息头部。所谓头部是与upstream server通信的协议规定的,比如HTTP协议的header部分,或者memcached 协议的响应状态部分。
(4)abort_request
在客户端放弃请求时被调用。不需要在函数中实现关闭后端服务器连接的功能,系统会自动完成关闭连接的步骤,所以一般此函数不会进行任何具体工作。
(5)finalize_request
正常完成与后端服务器的请求后调用该函数,与abort_request相同,一般也不会进行任何具体工作。
(6)input_filter
处理后端服务器返回的响应正文。Nginx默认的input_filter会将收到的内容封装成为缓冲区链ngx_chain。该链由upstream的out_bufs指针域定位,所以开发人员可以在模块以外通过该指针得到后端服务器返回的正文数据。
(7)input_filter_init
初始化input filter的上下文。Nginx默认的input_filter_init直接返回。
大部分回调函数一般不会真正用到,upstream最重要的回调函数是create_request、process_header和input_filter,它们共同实现了与后端服务器的协议解析部分。
2、memcached模块
Nginx提供了很多现成的upstream模块,比如
memcached、
proxy、
fastcgi,这里以memcached模块为例说明upstream模块。
Nginx提供了ngx_http_memcached模块,该模块提供了从memcached读取数据的功能,而不提供向memcached写数据的功能。
upstream模块与handler模块并无实质不同,特别之处仅仅在于模块处理函数的实现。upstream模块的处理函数进行的操作都包含一个固定的流程,比如memcached的模块处理函数ngx_http_memcached_handler,这个固定的操作流程如下:
(1)创建upstream数据结构。
if (ngx_http_upstream_create(r) != NGX_OK) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
(2)设置模块的tag和schema。schema现在只会用于日志,tag会用于buf_chain管理。
u = r->upstream;
ngx_str_set(&u->schema, "memcached://");
u->output.tag = (ngx_buf_tag_t) &ngx_http_memcached_module;
(3)设置upstream的后端服务器列表数据结构。
mlcf = ngx_http_get_module_loc_conf(r, ngx_http_memcached_module);
u->conf = &mlcf->upstream;
(4)设置upstream回调函数。
u->create_request = ngx_http_memcached_create_request;
u->reinit_request = ngx_http_memcached_reinit_request;
u->process_header = ngx_http_memcached_process_header;
u->abort_request = ngx_http_memcached_abort_request;
u->finalize_request = ngx_http_memcached_finalize_request;
u->input_filter_init = ngx_http_memcached_filter_init;
u->input_filter = ngx_http_memcached_filter;
(5)创建并设置upstream环境数据结构。
ctx = ngx_palloc(r->pool, sizeof(ngx_http_memcached_ctx_t));
if (ctx == NULL) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
ctx->rest = NGX_HTTP_MEMCACHED_END;
ctx->request = r;
ngx_http_set_ctx(r, ctx, ngx_http_memcached_module);
u->input_filter_ctx = ctx;
(6)完成upstream初始化并进行收尾工作。
r->main->count++;
ngx_http_upstream_init(r);
return NGX_DONE;
对于不同的upstream模块,上面步骤基本相同,区别比较大的是第3步,不同的模块在获取后端服务器列表时,策略的差异非常大,其次是第4步的回调函数。
需要注意的是,upstream请求和客户端请求之间进行了一对一绑定。
3、回调函数
memcached模块中需要处理的回调函数主要如下:
(1)ngx_http_memcached_create_request
按照设置的内容生成一个key,接着生成一个“get $key”的请求,放在r->upstream->request_bufs里面。
(2)ngx_http_memcached_process_header
这是模块的业务重点函数,其重要职责是将后端服务器返回的状态翻译成返回给客户端的状态。process_header函数完成响应头的正确处理后会返回NGX_OK。如果返回NGX_AGAIN,表示未读取完整数据,需要从后端服务器继续读取数据。返回NGX_DECLINED无意义,其它任何返回值都被认为是出错状态,Nginx将结束upstream请求并返回错误信息。
(3)ngx_http_memcached_filter_init
修正从后端服务器收到的内容长度。因为在处理header时没有加上这部分长度。
(4)ngx_http_memcached_filter
memcached模块是少有的带有处理正文的回调函数的模块。因为memcached模块需要过滤正文末尾CRLF “END” CRLF,所以实现了自己的filter回调函数。处理正文的实际意义是将从后端服务器收到的正文有效内容封装成ngx_chain_t,并加在u->out_bufs末尾。Nginx并不进行数据拷贝,而是建立ngx_buf_t数据结构指向这些数据内存区,然后由ngx_chain_t组织这些buf。这种实现避免了内存大量搬迁,也是Nginx高效的奥秘之一。
二、负载均衡模块
负载均衡模块用于从”upstream”指令定义的后端主机列表中选取一台主机。Nginx先使用负载均衡模块找到一台主机,再使用upstream模块实现与这台主机的交互。Nginx内置的负载均衡策略有很多种,这里以ip hash模块为例进行分析。如果需要使用ip hash的负载均衡算法,则需要作如下配置:
upstream test {
ip_hash;
server 192.168.0.1;
server 192.168.0.2;
}
核心指令”ip_hash”只能在upstream {}中使用,这条指令用于通知Nginx使用ip hash负载均衡算法。如果没加这条指令,Nginx会使用默认的round robin负载均衡模块。
Nginx在读取到upstream模块的配置文件时会调用ip hash模块的钩子代码中设置的回调函数init_upstream:
static char *ngx_http_upstream_ip_hash(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
ngx_http_upstream_srv_conf_t *uscf;
uscf = ngx_http_conf_get_module_srv_conf(cf, ngx_http_upstream_module);
uscf->peer.init_upstream = ngx_http_upstream_init_ip_hash;
uscf->flags = NGX_HTTP_UPSTREAM_CREATE
|NGX_HTTP_UPSTREAM_MAX_FAILS
|NGX_HTTP_UPSTREAM_FAIL_TIMEOUT
|NGX_HTTP_UPSTREAM_DOWN;
return NGX_CONF_OK;
}
init_upstream回调函数执行时需要初始化负载均衡模块的配置,还要设置一个新钩子,这个钩子函数会在Nginx处理每个请求时作为初始化函数调用,ip hash模块初始化配置的代码如下:
ngx_http_upstream_init_round_robin(cf, us);
us->peer.init = ngx_http_upstream_init_ip_hash_peer;
Nginx收到一个请求以后,如果发现需要访问upstream,就会执行对应的peer.init函数。这个函数最重要的作用是构造一张表,当前请求可以使用的upstream服务器被依次添加到这张表中。之所以需要这张表,最重要的原因是如果upstream服务器出现异常,不能提供服务时,可以从这张表中取得其它服务器进行重试操作。
此外,在ip hash模块的实现中,还有两个重要的函数即peer.get和peer.free,其中,peer.get负责从server列表中选择某个server(一般是不重复选择),而peer.free负责server释放前的资源释放工作。当某次peer.get函数得到的连接地址连接不上,或者请求对应的服务器得到异常响应,Nginx会执行ngx_http_upstream_next,然后可能再次调用peer.get函数尝试别的连接。upstream模块和负载均衡模块在请求处理过程中的相互关系如下:
图1 upstream整体处理流程