Nginx Upstream模块源码分析(上)

摘要:
Upstream模块是一个很重要的模块,很多其他模块都会使用它来完成对后端服务器的访问,达到反向代理和负载均衡的效果。下面以一个使用Upstream模块的第三方模块SessionSticky为例,分析一下Upstream模块的执行流程。NGX_HTTP_UPS_CONF:可以出现在http里面的upstream配置指令里。2)初始化函数初始化函数的工作很简单,create函数中为配置分配空间,init函数中会调用peer的init_stream(),这是Upstream与使用它的模块的第一次交互。其他模块就是通过注册各种回调函数,加入到Upstream处理的生命周期的。

Upstream模块是一个很重要的模块,很多其他模块都会使用它来完成对后端服务器的访问,
达到反向代理和负载均衡的效果。例如Fastcgi、Memcached、SessionSticky等。
如果自己实现这部分功能,采用传统的实现方式,很可能会阻塞Nginx降低其性能,因为Nginx是全异步非阻塞的。

所以要想不破坏其优美的架构,就得按照其规范实现很多回调函数,注册这些钩子到Nginx的处理流程中。
下面以一个使用Upstream模块的第三方模块SessionSticky为例,分析一下Upstream模块的执行流程。



一、配置解析

每个模块的入口变量ngx_module_t中,都需要指明:
一个ngx_command_t数组表示模块可以解析的配置;
一个module_ctx上下文,注册初始化和合并配置时的回调函数;
一个解析配置的函数;

Upstream模块的ngx_command_t数组的配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
staticngx_command_tngx_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
};
staticngx_http_module_tngx_http_upstream_module_ctx={
ngx_http_upstream_add_variables,/*preconfiguration*/
NULL,/*postconfiguration*/
ngx_http_upstream_create_main_conf,/*createmainconfiguration*/
ngx_http_upstream_init_main_conf,/*initmainconfiguration*/
NULL,/*createserverconfiguration*/
NULL,/*mergeserverconfiguration*/
NULL,/*createlocationconfiguration*/
NULL/*mergelocationconfiguration*/
};
ngx_module_tngx_http_upstream_module={
NGX_MODULE_V1,
&ngx_http_upstream_module_ctx,/*modulecontext*/
ngx_http_upstream_commands,/*moduledirectives*/
NGX_HTTP_MODULE,/*moduletype*/
NULL,/*initmaster*/
NULL,/*initmodule*/
NULL,/*initprocess*/
NULL,/*initthread*/
NULL,/*exitthread*/
NULL,/*exitprocess*/
NULL,/*exitmaster*/
NGX_MODULE_V1_PADDING
};

1)配置项

从上面的配置可知,Upstream模块可以解析http内的upstream块和块内的server。配置项含义如下,
Nginx就是靠这些选项帮助它找到能解析当前配置的模块:
  • NGX_CONF_TAKE1:配置指令接受1个参数。

  • NGX_CONF_1MORE:配置指令接受至少一个参数。

  • NGX_CONF_BLOCK:配置指令可以接受的值是一个配置信息块。也就是一对大括号括起来的内容。里面可以再包括很多的配置指令。比如常见的server指令就是这个属性的。

  • NGX_HTTP_MAIN_CONF: 可以直接出现在http配置指令里。

  • NGX_HTTP_UPS_CONF: 可以出现在http里面的upstream配置指令里。


2)初始化函数

初始化函数的工作很简单,create函数中为配置分配空间,init函数中会调用peer的init_stream(),
这是Upstream与使用它的模块的第一次交互。其他模块就是通过注册各种回调函数,加入到Upstream处理的生命周期的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
staticvoid*
ngx_http_upstream_create_main_conf(ngx_conf_t*cf)
{
ngx_http_upstream_main_conf_t*umcf;
umcf=ngx_pcalloc(cf->pool,sizeof(ngx_http_upstream_main_conf_t));
if(umcf==NULL){
returnNULL;
}
if(ngx_array_init(&umcf->upstreams,cf->pool,4,
sizeof(ngx_http_upstream_srv_conf_t*))
!=NGX_OK)
{
returnNULL;
}
returnumcf;
}
staticchar*
ngx_http_upstream_init_main_conf(ngx_conf_t*cf,void*conf)
{
ngx_http_upstream_main_conf_t*umcf=conf;
ngx_uint_ti;
ngx_array_theaders_in;
ngx_hash_key_t*hk;
ngx_hash_init_thash;
ngx_http_upstream_init_ptinit;
ngx_http_upstream_header_t*header;
ngx_http_upstream_srv_conf_t**uscfp;
uscfp=umcf->upstreams.elts;
for(i=0;i<umcf->upstreams.nelts;i++){
//就是在这里回调peer的init_stream()函数
init=uscfp[i]->peer.init_upstream?uscfp[i]->peer.init_upstream:
ngx_http_upstream_init_round_robin;
if(init(cf,uscfp[i])!=NGX_OK){
returnNGX_CONF_ERROR;
}
}
/*upstream_headers_in_hash*/
if(ngx_array_init(&headers_in,cf->temp_pool,32,sizeof(ngx_hash_key_t))
!=NGX_OK)
{
returnNGX_CONF_ERROR;
}
for(header=ngx_http_upstream_headers_in;header->name.len;header++){
hk=ngx_array_push(&headers_in);
if(hk==NULL){
returnNGX_CONF_ERROR;
}
hk->key=header->name;
hk->key_hash=ngx_hash_key_lc(header->name.data,header->name.len);
hk->value=header;
}
hash.hash=&umcf->headers_in_hash;
hash.key=ngx_hash_key_lc;
hash.max_size=512;
hash.bucket_size=ngx_align(64,ngx_cacheline_size);
hash.name="upstream_headers_in_hash";
hash.pool=cf->pool;
hash.temp_pool=NULL;
if(ngx_hash_init(&hash,headers_in.elts,headers_in.nelts)!=NGX_OK){
returnNGX_CONF_ERROR;
}
returnNGX_CONF_OK;
}


3)解析函数

解析函数解析出ngx_http_upstream_srv_conf_t并保存到全局配置数组中。其他使用Upstream的模块,
会大量使用读取配置,完成自己的功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
staticchar*
ngx_http_upstream(ngx_conf_t*cf,ngx_command_t*cmd,void*dummy)
{
char*rv;
void*mconf;
ngx_str_t*value;
ngx_url_tu;
ngx_uint_tm;
ngx_conf_tpcf;
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_FAILS
|NGX_HTTP_UPSTREAM_FAIL_TIMEOUT
|NGX_HTTP_UPSTREAM_DOWN
|NGX_HTTP_UPSTREAM_BACKUP);
if(uscf==NULL){
returnNGX_CONF_ERROR;
}
ctx=ngx_pcalloc(cf->pool,sizeof(ngx_http_conf_ctx_t));
if(ctx==NULL){
returnNGX_CONF_ERROR;
}
http_ctx=cf->ctx;
ctx->main_conf=http_ctx->main_conf;
/*theupstream{}'ssrv_conf*/
ctx->srv_conf=ngx_pcalloc(cf->pool,sizeof(void*)*ngx_http_max_module);
if(ctx->srv_conf==NULL){
returnNGX_CONF_ERROR;
}
//将ngx_http_upstream_srv_conf_t保存到配置数组中,其他模块会读取配置
ctx->srv_conf[ngx_http_upstream_module.ctx_index]=uscf;
uscf->srv_conf=ctx->srv_conf;
/*theupstream{}'sloc_conf*/
ctx->loc_conf=ngx_pcalloc(cf->pool,sizeof(void*)*ngx_http_max_module);
if(ctx->loc_conf==NULL){
returnNGX_CONF_ERROR;
}
for(m=0;ngx_modules[m];m++){
if(ngx_modules[m]->type!=NGX_HTTP_MODULE){
continue;
}
module=ngx_modules[m]->ctx;
if(module->create_srv_conf){
mconf=module->create_srv_conf(cf);
if(mconf==NULL){
returnNGX_CONF_ERROR;
}
ctx->srv_conf[ngx_modules[m]->ctx_index]=mconf;
}
if(module->create_loc_conf){
mconf=module->create_loc_conf(cf);
if(mconf==NULL){
returnNGX_CONF_ERROR;
}
ctx->loc_conf[ngx_modules[m]->ctx_index]=mconf;
}
}
/*parseinsideupstream{}*/
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){
returnrv;
}
if(uscf->servers==NULL){
ngx_conf_log_error(NGX_LOG_EMERG,cf,0,
"noserversareinsideupstream");
returnNGX_CONF_ERROR;
}
returnrv;
}



二、模块启动

Upstream模块都初始化好了之后,是如何被启动的呢?一般我们是这样使用Upstream模块的:

1
2
3
4
5
6
7
8
9
10
11
12
13
upstreambackend{
session_sticky;
serverwww.baidu.comweight=10;
serverwww.google.comweight=10;
}
server{
listen80;
server_namesessionsticky.com;
location/{
proxy_passhttp://backend;
}
}

奥秘模块HttpProxyModule中,此Handler模块解析proxy_pass,配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
staticngx_command_tngx_http_proxy_commands[]={
{ngx_string("proxy_pass"),
NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF|NGX_HTTP_LMT_CONF|NGX_CONF_TAKE1,
ngx_http_proxy_pass,
NGX_HTTP_LOC_CONF_OFFSET,
0,
NULL},
......
}
staticchar*
ngx_http_proxy_pass(ngx_conf_t*cf,ngx_command_t*cmd,void*conf)
{
ngx_http_proxy_loc_conf_t*plcf=conf;
size_tadd;
u_shortport;
ngx_str_t*value,*url;
ngx_url_tu;
ngx_uint_tn;
ngx_http_core_loc_conf_t*clcf;
ngx_http_script_compile_tsc;
if(plcf->upstream.upstream||plcf->proxy_lengths){
return"isduplicate";
}
clcf=ngx_http_conf_get_module_loc_conf(cf,ngx_http_core_module);
//注册Handler的处理函数
clcf->handler=ngx_http_proxy_handler;
.......
returnNGX_CONF_OK;
}

解析函数中注册的Handler处理函数ngx_http_proxy_handler(),在调用ngx_http_read_client_request_body()时
将ngx_http_upstream_init传入,作为接收客户端请求体的后处理函数。这样每次从客户端读取完请求Body后,
都会回调Upstream的init函数。

注意Nginx与Squid的区别,Nginx会将请求体全部读取完后再进行后续处理。而Squid会边读边转发。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
staticngx_int_t
ngx_http_proxy_handler(ngx_http_request_t*r)
{
ngx_int_trc;
ngx_http_upstream_t*u;
ngx_http_proxy_ctx_t*ctx;
ngx_http_proxy_loc_conf_t*plcf;
if(ngx_http_upstream_create(r)!=NGX_OK){
returnNGX_HTTP_INTERNAL_SERVER_ERROR;
}
ctx=ngx_pcalloc(r->pool,sizeof(ngx_http_proxy_ctx_t));
if(ctx==NULL){
returnNGX_ERROR;
}
ngx_http_set_ctx(r,ctx,ngx_http_proxy_module);
plcf=ngx_http_get_module_loc_conf(r,ngx_http_proxy_module);
u=r->upstream;
if(plcf->proxy_lengths==NULL){
ctx->vars=plcf->vars;
u->schema=plcf->vars.schema;
#if(NGX_HTTP_SSL)
u->ssl=(plcf->upstream.ssl!=NULL);
#endif
}else{
if(ngx_http_proxy_eval(r,ctx,plcf)!=NGX_OK){
returnNGX_HTTP_INTERNAL_SERVER_ERROR;
}
}
u->output.tag=(ngx_buf_tag_t)&ngx_http_proxy_module;
u->conf=&plcf->upstream;
//以下就是HttpProxyModule注册的默认的回调函数
#if(NGX_HTTP_CACHE)
u->create_key=ngx_http_proxy_create_key;
#endif
u->create_request=ngx_http_proxy_create_request;
u->reinit_request=ngx_http_proxy_reinit_request;
u->process_header=ngx_http_proxy_process_status_line;
u->abort_request=ngx_http_proxy_abort_request;
u->finalize_request=ngx_http_proxy_finalize_request;
r->state=0;
if(plcf->redirects){
u->rewrite_redirect=ngx_http_proxy_rewrite_redirect;
}
if(plcf->cookie_domains||plcf->cookie_paths){
u->rewrite_cookie=ngx_http_proxy_rewrite_cookie;
}
u->buffering=plcf->upstream.buffering;
u->pipe=ngx_pcalloc(r->pool,sizeof(ngx_event_pipe_t));
if(u->pipe==NULL){
returnNGX_HTTP_INTERNAL_SERVER_ERROR;
}
u->pipe->input_filter=ngx_http_proxy_copy_filter;
u->pipe->input_ctx=r;
u->input_filter_init=ngx_http_proxy_input_filter_init;
u->input_filter=ngx_http_proxy_non_buffered_copy_filter;
u->input_filter_ctx=r;
u->accel=1;
//在这里注册ngx_http_upstream_init回调函数,读取完请求体后就会触发
rc=ngx_http_read_client_request_body(r,ngx_http_upstream_init);
if(rc>=NGX_HTTP_SPECIAL_RESPONSE){
returnrc;
}
returnNGX_DONE;
}



免责声明:文章转载自《Nginx Upstream模块源码分析(上)》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇【CSS3】border属性在Android Studio 0.5.2中使用ArcGIS Android SDK下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

Nginx配置文件中文注释详解

Nginx ("engine x") 是一个高性能的 HTTP 和 反向代理 服务器,也是一个 IMAP/POP3/SMTP 代理服务器。 Nginx 是由 Igor Sysoev 为俄罗斯访问量第二的 Rambler.ru 站点开发的,它已经在该站点运行超过两年半了。Igor 将源代码以类BSD许可证的形式发布。 Nginx配置文件详解 #运行用户 us...

【nginx】大文件下载

nginx自带文件读取功能,而且实现地很好。 比如直接读取txt文件,png图片等,用chrome可以直接获取到内容。 但是对于很大的文件,比如有2个G的视频,nginx如何吐出2G的内容呢? 实验: 准备很大的MP4文件(比如2G),nginx搭建好webserver,nginx开启access_log选项(log中要包含下载文件大小,http code...

Nginx配置https兼容http

现象 如果一个https站点里面有引用一些http的静态资源,图片可以正常加载,但是js文件、css文件就会加载失败,如下图: 原因 为了解释这个问题,首先要理解一下Mixed Content的概念:HTTPS 网页中加载的 HTTP 资源被称之为 Mixed Content(混合内容),不同浏览器对 Mixed Content 有不一样的处理规则。 忽...

nginx 拦截 swagger 登录

随着微服务的也来越多,每个服务都有单独的文档,那么问题来了,怎么把所有文档整合在一起呢 本方法采用服务器拦截的方式进行处理 首先需要在opt 的主目录中 /opt/ 创建一个新文件 htpasswd此文件的书写格式是用户名:密码每行一个账户并且 密码必须使用函数 crypt(3) 加密官方档说 可以用 Apache 的 htpasswd 工具来创建密码文件...

nginx创建本地服务器和配置代理(解决跨域)

1,下载nginx 2,解压后打开conf/nginx.conf 修改配置 server { listen 8841;#监听端口 server_name localhost;#代理服务地址 location /{ #默认访问 root htm...

apache转发规则 + nginx location 正则匹配经典案例

0.apache的proxypass浏览器url不会变 我们想要的效果是请求http://qwyimg.haha.com.cn/......直接返回结果,而不是请求http://qwyimg.haha.com.cn/......返回一个地址,在通过这个返回的地址在去请求拿到结果 ProxyPass "/qiqiao/storage/oss/files/"...