注:当前分析基于 Nginx之搭建反向代理实现tomcat分布式集群 的配置。
1. 用到的指令
下面介绍在上面的配置中用到的指令。
upstream 指令
语法:upstream name { ... }
默认值:none
使用环境:http
该指令用于设置一组可以在 proxy_pass 和 fastcgi_pass 指令中使用的代理服务器,默认的负载均衡方式为轮询。示例如下:
upstream backend {
server backend1.example.com weight = 5;
server 127.0.0.1:8080 max_fails=3 fail_timeout=30s;
server unix:/tmp/backend3;
}
server 指令
语法:server name [parameters]
默认值:none
使用环境:upstream
该指令用于指定后端服务器的名称和参数。服务器的名称可以是一个域名、一个 IP 地址、端口号或 UNIX Socket。
在后端服务器名称之后,可以跟以下参数:
- weight=NUMBER:设置服务器的权重,权重数值越高,被分配到的客户端请求数越多。如果没有设置权重,则为默认权重 1.
- max_fails=NUMBER:在参数 fail_timeout 指定的时间内对后端服务器请求失败的次数,如果检测到后端服务器无法连接及发生服务器错误(404错误除外),则标记为失败。如果没有设置,则为默认值 1。设为数组 0 将关闭这项检查。
- fail_timeout=TIME:在经历参数 max_fails 设置的失败次数后,暂停的时间。
- down:标记服务器为永久离线状态,用于 ip_hash 指令。
- backup:仅仅在非 backup 服务器全部宕机或繁忙的时候才启用。
proxy_pass 指令
将指定请求传递到上游服务器,格式为 URL。
2. 数据结构
2.1 ngx_http_upstream_t
typedef struct ngx_http_upstream_s ngx_http_upstream_t;
struct ngx_http_upstream_s {
/*
* 处理读事件的回调方法,每一个阶段都有不同的 read_event_handler
*/
ngx_http_upstream_handler_pt read_event_handler;
/*
* 处理写事件的回调方法,每一个阶段都有不同的 write_event_handler
*/
ngx_http_upstream_handler_pt write_event_handler;
/*
* 表示主动向上游服务器发起的连接
*/
ngx_peer_connection_t peer;
/*
* 当向下游客户端转发响应时(ngx_http_request_t 结构体中的 subrequest_in_memory
* 标志位为 0),如果打开了缓存且认为上游网速更快(conf 配置中的 buffering 标志
* 位为 1),这时会使用 pipe 成员来转发响应。在使用这种方式转发响应时,必须由
* HTTP 模块在使用 upstream 机制前构造 pipe 结构体,否则会出现严重的 coredump
* 错误.
*/
ngx_event_pipe_t *pipe;
/*
* request_bufs 以链表的方式把 ngx_buf_t 缓存区链接起来,它表示所有需要发送到
* 上游服务器的请求内容。所以,HTTP 模块实现的 create_request 回调方法就在于
* 构造 request_bufs 链表
*/
ngx_chain_t *request_bufs;
/*
* 定义了向下游发送响应的方式
*/
ngx_output_chain_ctx_t output;
ngx_chain_writer_ctx_t writer;
/*
* 使用 upstream 机制时的各种配置
*/
ngx_http_upstream_conf_t *conf;
ngx_http_upstream_srv_conf_t *upstream;
#if (NGX_HTTP_CACHE)
ngx_array_t *caches;
#endif
/*
* HTTP 模块在实现 process_header 方法时,如果希望 upstream 直接转发响应,
* 就需要把解析出的响应头部适配为 HTTP 的响应头部,同时需要把包头中的信息
* 设置到 headers_in 结构体,这样,会把 headers_in 中设置的头部添加到要发
* 送到下游客户端的响应头部 headers_out 中
*/
ngx_http_upstream_headers_in_t headers_in;
/*
* 用于解析主机域名
*/
ngx_http_upstream_resolved_t *resolved;
ngx_buf_t from_client;
/*
* 接收上游服务器响应包头的缓冲区,在不需要把响应直接转发给客户端,
* 或者 buffering 标志位为 0 的情况下转发包体时,接收包体的缓冲区
* 仍然使用 buffer。注意,如果没有自定义 input_filter 方法处理包体,
* 将会使用 buffer 存储全部的包体,这时 buffer 必须足够大,它的大小
* 由 ngx_http_upstream_conf_t 配置结构体中的 buffer_size 成员决定
*/
ngx_buf_t buffer;
/*
* 表示来自上游服务器的响应包体的长度
*/
off_t length;
/*
* out_bufs 在两种场景下有不同的意义:1. 当不需要转发包体,且使用默认
* 的 input_filter 方法(也就是 ngx_http_upstream_non_buffered_filter
* 方法)处理包体时,out_bufs 将会指向响应包体,事实上,out_bufs 链表
* 中会产生多个 ngx_buf_t 缓冲区,每个缓冲区都指向 buffer 缓存中的一部
* 分,而这里的一部分就是每次调用 recv 方法接收到的一段 TCP 流。2. 当
* 需要转发响应包体到下游时(buffering 标志位为 0,即以下游网速优先),
* 这个链表指向上一次向下游转发响应到现在这段时间内接收自上游的缓存响应
*/
ngx_chain_t *out_bufs;
/*
* 当需要转发响应包体到下游时(buffering 标志位为 0,即以下游网速优先),
* 它表示上一次向下游转发响应时没有发送完的内容
*/
ngx_chain_t *busy_bufs;
/*
* 这个链表将用于回收 out_bufs 中已经发送给下游的 ngx_buf_t 结构体,这
* 同样应用在 buffering 标志位为 0 即以下游网速优先的场景
*/
ngx_chain_t *free_bufs;
/*
* 处理包体前的初始化方法,其中 data 参数用于传递用户数据结构,它实际上
* 就是下面的 input_filter_ctx 指针
*/
ngx_int_t (*input_filter_init)(void *data);
/*
* 处理包体的方法,其中 data 参数用于传递用户数据结构,它实际上就是下面的
* input_filter_ctx 指针,而 bytes 表示本次接收到的包体长度。返回 NGX_ERROR
* 时表示处理包体错误,请求需要结束,否则都将继续 upstream 流程
*/
ngx_int_t (*input_filter)(void *data, ssize_t bytes);
/*
* 用于传递 HTTP 模块自定义的数据结构,在 input_filter_init 和 input_filter
* 方法被回调时会作为参数传递过去
*/
void *input_filter_ctx;
#if (NGX_HTTP_CACHE)
ngx_int_t (*create_key)(ngx_http_request_t *r);
#endif
/*
* HTTP 模块实现的 create_request 方法用于构造发往上游服务器的请求
*/
ngx_int_t (*create_request)(ngx_http_request_t *r);
/*
* 与上游服务器的通信失败后,如果按照重试规则还需要再次向上游服务器发起
* 连接,则会调用 reinit_request 方法
*/
ngx_int_t (*reinit_request)(ngx_http_request_t *r);
/*
* 解析上游服务器返回响应的包头,返回 NGX_AGAIN 表示包头还没有接收完整,
* 返回 NGX_HTTP_UPSTREAM_INVALID_HEADER 表示包头不合法,返回 NGX_ERROR
* 表示出现错误,返回 NGX_OK 表示解析到完整的包头.
*/
ngx_int_t (*process_header)(ngx_http_request_t *r);
void (*abort_request)(ngx_http_request_t *r);
/*
* 请求结束时会调用
*/
void (*finalize_request)(ngx_http_request_t *r,
ngx_int_t rc);
/*
* 在上游返回的响应出现 Location 或者 Refresh 头部时表示重定向时,会通过
* ngx_http_upstream_process_headers 方法调用到可由 HTTP 模块实现的
* rewrite_redirect 方法
*/
ngx_int_t (*rewrite_redirect)(ngx_http_request_t *r,
ngx_table_elt_t *h, size_t prefix);
ngx_int_t (*rewrite_cookie)(ngx_http_request_t *r,
ngx_table_elt_t *h);
ngx_msec_t timeout;
/*
* 用于表示上游响应的错误码、包体长度等信息
*/
ngx_http_upstream_state_t *state;
ngx_str_t method;
/*
* schema 和 uri 成员仅在记录日志时会用到,除此之外没有意义
*/
ngx_str_t schema;
ngx_str_t uri;
#if (NGX_HTTP_SSL || NGX_COMPAT)
ngx_str_t ssl_name;
#endif
ngx_http_cleanup_pt *cleanup;
/*
* 是否指定文件缓存路径的标志位
*/
unsigned store:1;
/*
* 是否启用文件缓存
*/
unsigned cacheable:1;
unsigned accel:1;
/*
* 是否基于 SSL 协议访问上游服务器
*/
unsigned ssl:1;
#if (NGX_HTTP_CACHE)
unsigned cache_status:3;
#endif
/*
* 向下游转发上游的响应包体时,是否开启更大的内存及临时磁盘文件用于
* 缓存来不及发送到下游的响应包体.
*/
unsigned buffering:1;
unsigned keepalive:1;
unsigned upgrade:1;
/*
* request_sent 表示是否已经向上游服务器发送了请求,当 request_sent 为
* 1 时,表示 upstream 机制已经向上游服务器发送了全部或者部分的请求。
* 事实上,这个标志位更多的是为了使用 ngx_output_chain 方法发送请求,
* 因为该方法发送请求时会自动把未发送完的 request_bufs 链表记录下来,
* 为了防止反复发送重复请求,必须有 request_sent 标志位记录是否调用过
* ngx_output_chain 方法
*/
unsigned request_sent:1;
unsigned request_body_sent:1;
/*
* 将上游服务器的响应划分为包头和包尾,如果把响应直接转发给客户端,
* header_sent 标志位表示包头是否发送,header_sent 为 1 时表示已经
* 把包头转发给客户端了。如果不转发响应到客户端,则 header_sent
* 没有意义.
*/
unsigned header_sent:1;
};
2.2 ngx_http_upstream_conf_t
typedef struct {
/*
* 当在 ngx_http_upstream_t 结构体中没有实现 resolved 成员时,upstream 这个
* 结构体才会生效,它会定义上游服务器的配置
*/
ngx_http_upstream_srv_conf_t *upstream;
/*
* 建立 TCP 连接的超时时间,实际上就是写事件添加到定时器中设置的超时时间
*/
ngx_msec_t connect_timeout;
/*
* 发送请求的超时时间。通常就是写事件添加到定时器中设置的超时时间
*/
ngx_msec_t send_timeout;
/*
* 接收响应的超时时间。通常就是读事件添加到定时器中设置的超时时间
*/
ngx_msec_t read_timeout;
ngx_msec_t next_upstream_timeout;
/*
* TCP 的 SO_SNOLOWAT 选项,表示发送缓冲区的下限
*/
size_t send_lowat;
/*
* 定义了接收头部的缓冲区分配的内存大小(ngx_http_upstream_t 中的 buffer
* 缓冲区),当不转发响应给下游或者在 buffering 标志位为 0 的情况下转发
* 响应时,它同样表示接收包体的缓冲区大小
*/
size_t buffer_size;
size_t limit_rate;
/*
* 仅当 buffering 标志位为 1,并且向下游转发响应时生效。它会设置到
* ngx_event_pipe_t 结构体的 busy_size 成员中
*/
size_t busy_buffers_size;
/*
* 在 buffering 标志位为 1 时,如果上游速度快于下游速度,将有可能把来自上游的
* 响应存储到临时文件中,而 max_temp_file_size 指定了临时文件的最大长度。实际
* 上,它将限制 ngx_event_pipe_t 结构体中的 temp_file
*/
size_t max_temp_file_size;
/*
* 表示将缓冲区中的响应写入临时文件时一次写入字符流的最大长度
*/
size_t temp_file_write_size;
size_t busy_buffers_size_conf;
size_t max_temp_file_size_conf;
size_t temp_file_write_size_conf;
/*
* 以缓存响应的方式转发上游服务器的包体时所使用的内存大小
*/
ngx_bufs_t bufs;
/*
* 针对 ngx_http_upstream_t 结构体中保存解析完的包头的 headers_in 成员,
* ignore_headers 可以按照二进制位使得 upstream 在转发包头时跳过对某些
* 头部的处理。作为 32 位整型,理论上 ignore_headers 最多可以表示 32 个
* 需要跳过不予处理的头部
*/
ngx_uint_t ignore_headers;
/*
* 以二进制位来表示一些错误码,如果处理上游响应时发现这些错误码,那么在
* 没有将响应转发给下游客户端时,将会选择下一个上游服务器来重发请求
*/
ngx_uint_t next_upstream;
/*
* 在 buffering 标志位为 1 的情况下转发响应时,将有可能把响应存放到临时文件
* 中。在 ngx_http_upstream_t 中的 store 标志位为 1 时,store_access 表示
* 所创建的目录、文件的权限.
*/
ngx_uint_t store_access;
ngx_uint_t next_upstream_tries;
/*
* 决定转发响应方式的标志位,buffering 为 1 时表示打开缓存,这时认为上游
* 的网速快于下游的网速,会尽量地在内存或者磁盘中缓存来自上游的响应;如果
* buffering 为 0,仅会开辟一块固定大小的内存块作为缓存来转发响应
*/
ngx_flag_t buffering;
ngx_flag_t request_buffering;
ngx_flag_t pass_request_headers;
ngx_flag_t pass_request_body;
/*
* 标志位,为 1 时表示与上游服务器交互时将不检查 Nginx 与下游客户端间的连接
* 是否断开。也就是说,即使下游客户端主动关闭了连接,也不会中断与上游服务器
* 间的交互.
*/
ngx_flag_t ignore_client_abort;
/*
* 当解析上游响应的包头时,如果解析后设置到 headers_in 结构体中的 status_n
* 错误码大于 400,则会试图把它与 error_page 中指定的错误码相匹配,如果匹配
* 上,则发送 error_page 中指定的响应,否则继续返回上游服务器的错误码.
*/
ngx_flag_t intercept_errors;
/*
* buffering 标志位为 1 的情况下转发响应时才有意义。这时,如果 cyclic_temp_file
* 为 1,则会试图复用临时文件中已经使用过的空间。不建议将 cyclic_temp_file
* 设为 1.
*/
ngx_flag_t cyclic_temp_file;
ngx_flag_t force_ranges;
/*
* 在 buffering 标志位为 1 的情况下转发响应时,存放临时文件的路径
*/
ngx_path_t *temp_path;
/*
* 不转发的头部。实际上是通过 ngx_http_upstream_hide_hash 方法,
* 根据 hide_headers 和 pass_headers 动态数组构造出的需要隐藏的
* HTTP 头部散列表
*/
ngx_hash_t hide_headers_hash;
/*
* 当转发上游响应头部(ngx_http_upstream_t 中 headers_in 结构体中的头部)
* 给下游客户端时,如果不希望某些头部转发给下游,就设置到 hide_headers
* 动态数组中
*/
ngx_array_t *hide_headers;
/*
* 当转发上游响应头部(ngx_http_upstream_t 中的 headers_in 结构体中的头部)
* 给下游客户端时,upstream 机制默认不会转发如 "Data"、"Server" 之类的头部,
* 如果确实希望直接转发它们到下游,就设置到 pass_headers 动态数组中
*/
ngx_array_t *pass_headers;
/*
* 连接上游服务器时使用的本机地址
*/
ngx_http_upstream_local_t *local;
#if (NGX_HTTP_CACHE)
ngx_shm_zone_t *cache_zone;
ngx_http_complex_value_t *cache_value;
ngx_uint_t cache_min_uses;
ngx_uint_t cache_use_stale;
ngx_uint_t cache_methods;
off_t cache_max_range_offset;
ngx_flag_t cache_lock;
ngx_msec_t cache_lock_timeout;
ngx_msec_t cache_lock_age;
ngx_flag_t cache_revalidate;
ngx_flag_t cache_convert_head;
ngx_flag_t cache_background_update;
ngx_array_t *cache_valid;
ngx_array_t *cache_bypass;
ngx_array_t *cache_purge;
ngx_array_t *no_cache;
#endif
/*
* 当 ngx_http_upstream_t 中的 store 标志位为 1 时,如果需要将上游的响应
* 存放到文件中,store_lengths 将表示存放路径的长度,而 store_values
* 表示存放路径
*/
ngx_array_t *store_lengths;
ngx_array_t *store_values;
#if (NGX_HTTP_CACHE)
signed cache:2;
#endif
signed store:2;
/*
* 上面的 intercept_errors 标志位定义了 400 以上的错误码将会与 error_page
* 比较后再行处理,实际上这个规则是可以有一个例外情况,如果将 intercept_404
* 标志位设为 1,当上游返回 404 时会直接转发这个错误码给下游,而不会去与
* errpr_page 进行比较.
*/
unsigned intercept_404:1;
/*
* 为 1 时,将会根据 ngx_http_upstream_t 中 headers_in 结构体里的 X-Accel-Buffering
* 头部(它的值会是 yes 和 no)来改变 buffering 标志位,当其值为 yes 时,buffering
* 标志位为 1。因此,change_buffering 为 1 时将有可能根据上游服务器返回的响应头部,
* 动态地决定是以上游网速优先还是以下游网速优先
*/
unsigned change_buffering:1;
#if (NGX_HTTP_SSL || NGX_COMPAT)
ngx_ssl_t *ssl;
ngx_flag_t ssl_session_reuse;
ngx_http_complex_value_t *ssl_name;
ngx_flag_t ssl_server_name;
ngx_flag_t ssl_verify;
#endif
ngx_str_t module;
NGX_COMPAT_BEGIN(2)
NGX_COMPAT_END
} ngx_http_upstream_conf_t;
3. upstream 的解析
static ngx_command_t ngx_http_upstream_commands[] = {
{ ngx_string("upstream"),
NGX_HTTP_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_TAKE1,
ngx_http_upstream,
0,
0,
NULL },
{ ngx_string("server"),
NGX_HTTP_UPS_CONF|NGX_CONF_1MORE,
ngx_http_upstream_server,
NGX_HTTP_SRV_CONF_OFFSET,
0,
NULL },
ngx_null_command
};
3.1 ngx_http_upstream
static char *
ngx_http_upstream(ngx_conf_t *cf, ngx_command_t *cmd, void *dummy)
{
char *rv;
void *mconf;
ngx_str_t *value;
ngx_url_t u;
ngx_uint_t m;
ngx_conf_t pcf;
ngx_http_module_t *module;
ngx_http_conf_ctx_t *ctx, *http_ctx;
ngx_http_upstream_srv_conf_t *uscf;
ngx_memzero(&u, sizeof(ngx_url_t));
value = cf->args->elts;
u.host = value[1];
u.no_resolve = 1;
u.no_port = 1;
uscf = ngx_http_upstream_add(cf, &u, NGX_HTTP_UPSTREAM_CREATE
|NGX_HTTP_UPSTREAM_WEIGHT
|NGX_HTTP_UPSTREAM_MAX_CONNS
|NGX_HTTP_UPSTREAM_MAX_FAILS
|NGX_HTTP_UPSTREAM_FAIL_TIMEOUT
|NGX_HTTP_UPSTREAM_DOWN
|NGX_HTTP_UPSTREAM_BACKUP);
if (uscf == NULL) {
return NGX_CONF_ERROR;
}
ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_conf_ctx_t));
if (ctx == NULL) {
return NGX_CONF_ERROR;
}
http_ctx = cf->ctx;
ctx->main_conf = http_ctx->main_conf;
/* the upstream{}'s srv_conf */
ctx->srv_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module);
if (ctx->srv_conf == NULL) {
return NGX_CONF_ERROR;
}
ctx->srv_conf[ngx_http_upstream_module.ctx_index] = uscf;
uscf->srv_conf = ctx->srv_conf;
/* the upstream{}'s loc_conf */
ctx->loc_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module);
if (ctx->loc_conf == NULL) {
return NGX_CONF_ERROR;
}
for (m = 0; cf->cycle->modules[m]; m++) {
if (cf->cycle->modules[m]->type != NGX_HTTP_MODULE) {
continue;
}
module = cf->cycle->modules[m]->ctx;
if (module->create_srv_conf) {
mconf = module->create_srv_conf(cf);
if (mconf == NULL) {
return NGX_CONF_ERROR;
}
ctx->srv_conf[cf->cycle->modules[m]->ctx_index] = mconf;
}
if (module->create_loc_conf) {
mconf = module->create_loc_conf(cf);
if (mconf == NULL) {
return NGX_CONF_ERROR;
}
ctx->loc_conf[cf->cycle->modules[m]->ctx_index] = mconf;
}
}
uscf->servers = ngx_array_create(cf->pool, 4,
sizeof(ngx_http_upstream_server_t));
if (uscf->servers == NULL) {
return NGX_CONF_ERROR;
}
/* parse inside upstream{} */
pcf = *cf;
cf->ctx = ctx;
cf->cmd_type = NGX_HTTP_UPS_CONF;
rv = ngx_conf_parse(cf, NULL);
*cf = pcf;
if (rv != NGX_CONF_OK) {
return rv;
}
if (uscf->servers->nelts == 0) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"no servers are inside upstream");
return NGX_CONF_ERROR;
}
return rv;
}