Nginx源码分析之 upstream指令
#Nginx 源码分析 upstream指令
想要的解决问题:
1:upstream存储结构
2:动态 upstream 流程(proxy_pass跟随变量或者域名)
最简单的配置文件
http {upstrem abc {server 1.1.1.1;server 2.2.2.2;}upstrem efg {server 3.3.3.3;server 4.4.4.4;}server {listen 80;location / {proxy_pass http://abc;}}
}
存储结构
首先,upstream是复杂指令,里面还需要解析 server 指令;所以其存储形式如下:
每个upstream指令用ngx_http_upstream_srv_conf_t
表示,每个server指令,用ngx_http_upstream_server_t
描述。
ngx_http_upstream_server_t
----ngx_http_upstream_srv_conf_t
--------ngx_http_upstream_server_t
------------1.1.1.1
------------2.2.2.2ngx_http_upstream_server_t
----ngx_http_upstream_srv_conf_t
--------ngx_http_upstream_server_t
------------3.3.3.3
------------4.4.4.4
我们来看一下Nginx如何将upstream组织成如上存储结构的:
首先,每解析一个upstream块时,就往 ngx_http_upstream_main_conf_t
中插入upstreams:每个upstream指令都会往 ngx_http_upstream_main_conf_t->upstreams
数组中插入一个对象。每个对象是 ngx_http_upstream_srv_conf_t
类型的数据结构。ngx_http_upstream_srv_conf_t
对象的名字就是 upstream
指令后面跟的名字abc或者efg,方便proxy_pass
指令根据名字查找到;
static char *
ngx_http_upstream(ngx_conf_t *cf, ngx_command_t *cmd, void *dummy)
{........ngx_http_upstream_srv_conf_t *uscf;/*每解析一个upstream,就生成一个ngx_http_upstream_srv_conf_t对象,当然也会查重*/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);}
换句话说,每个 ngx_http_upstream_srv_conf_t
挂在 ngx_http_upstream_main_conf_t
中的upstreams
成员下面。ngx_http_upstream_srv_conf_t
用来描述一个upstream块,ngx_http_upstream_main_conf_t->upstreams
用来管理所有 upstream块。
每个 ngx_http_upstream_srv_conf_t
中,有个 servers
成员,其类型是ngx_http_upstream_server_t
,用来描述一个 server
,换句话说,每解析到一个server指令,则会新建一个ngx_http_upstream_server_t
对象挂在其所属的upstream的servers
字段中。
至此,解析完upstream以及其server指令后,其存储在内存的结构描述完成;http_proxy 模块是如何找到对应的upsream呢?
proxy_pass 引用 upstream
proxy_pass 为非变量
location / {proxy_pass http://abc;}
在解析proxy_pass
指令时通过ngx_http_upstream_add
函数,找到对应的upstream(由ngx_http_upstream_srv_conf_t
描述)
plcf->upstream.upstream = ngx_http_upstream_add(cf, &u, 0);if (plcf->upstream.upstream == NULL) {return NGX_CONF_ERROR;}
由于Nginx可能会先解析到proxy_pass
指令,也有可能先解析到upstream
指令,所以统一调用ngx_http_upstream_add
来创建/查找 upstream,通过flag来控制流程,核心就是谁先解析,就由谁来创建。
由此可见,Nginx在配置解析阶段,就就确定好了upstream,在滴啊用ngx_http_upstream_init_request
时,通过uscf = u->conf->upstream;
直接获得这个ngx_http_upstream_srv_conf_t
(u->conf
是在函数ngx_http_proxy_handler
时,指向了配置结构的内存)。
proxy_pass 为 变量
通常,我们需要根据不同条件来选择不同的upstream,使用变量就方便的多。
set $ups abc;if ( $host = "www.efg.com" ) {set $ups efg;}location / {proxy_pass http://$ups;}
在解析proxy_pass
指令时,发现后面跟随了变量
url = &value[1];n = ngx_http_script_variables_count(url);if (n) {ngx_memzero(&sc, sizeof(ngx_http_script_compile_t));sc.cf = cf;sc.source = url;sc.lengths = &plcf->proxy_lengths;sc.values = &plcf->proxy_values;sc.variables = n;sc.complete_lengths = 1;sc.complete_values = 1;if (ngx_http_script_compile(&sc) != NGX_OK) {return NGX_CONF_ERROR;}#if (NGX_HTTP_SSL)plcf->ssl = 1;
#endifreturn NGX_CONF_OK;}
plcf->proxy_lengths
和 plcf->proxy_values
被赋上了值。即plcf->upstream.upstream
中的upstream字段未被赋值。
具体被赋值的时间点,是在业务处理流程函数ngx_http_proxy_handler
和ngx_http_upstream_init_request
被处理的。
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) {return NGX_HTTP_INTERNAL_SERVER_ERROR;}}
所以核心函数是ngx_http_proxy_eval
,proxy_lengths
和proxy_values
保存着待处理的变量,该函数就是将其变量变成str。
static ngx_int_t
ngx_http_proxy_eval(ngx_http_request_t *r, ngx_http_proxy_ctx_t *ctx,ngx_http_proxy_loc_conf_t *plcf)
{ngx_str_t proxy;if (ngx_http_script_run(r, &proxy, plcf->proxy_lengths->elts, 0,plcf->proxy_values->elts)== NULL){return NGX_ERROR;}//proxy.data 就是变量被替换后的字符串,例如此时proxy.data就是字符串"http://efg"......u->resolved = ngx_pcalloc(r->pool, sizeof(ngx_http_upstream_resolved_t));if (u->resolved == NULL) {return NGX_ERROR;}if (url.addrs && url.addrs[0].sockaddr) {u->resolved->sockaddr = url.addrs[0].sockaddr;u->resolved->socklen = url.addrs[0].socklen;u->resolved->naddrs = 1;u->resolved->host = url.addrs[0].name;} else {//将"efg"赋值给 u->resolved->hostu->resolved->host = url.host;}}
此时,还没找真正的upstream,只是将变量翻译成了对应的值。
static void
ngx_http_upstream_init_request(ngx_http_request_t *r)
{......if (u->resolved == NULL) {uscf = u->conf->upstream;} else {#if (NGX_HTTP_SSL)u->ssl_name = u->resolved->host;
#endifhost = &u->resolved->host;//如果proxy_pass 配置为变量,且变量翻译后为一个ip时,走这里if (u->resolved->sockaddr) {if (u->resolved->port == 0&& u->resolved->sockaddr->sa_family != AF_UNIX){ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,"no port in upstream \"%V\"", host);ngx_http_upstream_finalize_request(r, u,NGX_HTTP_INTERNAL_SERVER_ERROR);return;}if (ngx_http_upstream_create_round_robin_peer(r, u->resolved)!= NGX_OK){ngx_http_upstream_finalize_request(r, u,NGX_HTTP_INTERNAL_SERVER_ERROR);return;}ngx_http_upstream_connect(r, u);return;}umcf = ngx_http_get_module_main_conf(r, ngx_http_upstream_module);uscfp = umcf->upstreams.elts;//如果proxy_pass 配置为变量,且变量翻译后为非IP时走这里//根据 u->resolved->host 从 存储结构中找到对应的upstream,找到后,后续的流程就和未配置变量一样。for (i = 0; i < umcf->upstreams.nelts; i++) {uscf = uscfp[i];if (uscf->host.len == host->len&& ((uscf->port == 0 && u->resolved->no_port)|| uscf->port == u->resolved->port)&& ngx_strncasecmp(uscf->host.data, host->data, host->len) == 0){goto found;}}}
proxy_pass 为 域名
例如
location / {proxy_pass http://www.example.com;}
此时,在 proxy_pass
时创建了 upstream对象 ngx_http_upstream_srv_conf_t
,但是里面的 servers 字段为空,则会在ngx_http_upstream_init_main_conf
时,查询域名对应的dns。这也就是为什么如果proxy_pass 写死域名时,不会动态更新DNS的原因,比较获取DNS的流程是在进程启动阶段,后续就不更新了。
调用栈如下:
ngx_inet_resolve_host
ngx_http_upstream_init_round_robin
ngx_http_upstream_init_main_conf
proxy_pass 为 ip
例如
location / {proxy_pass http://127.0.0.1;}
在解析 proxy_pass
命令时,调用 ngx_http_upstream_add
创建了 ngx_http_upstream_srv_conf_t
对象,然后在执行ngx_parse_url
函数时,解析到了发现参数是ip地址,此时他会创建一个 ngx_http_upstream_server_t
对象,而不是等待 server 指令时创建 ngx_http_upstream_server_t
对象。
server指令参数为域名
upstrem abc {server www.example.com;}server {listen 80;location / {proxy_pass http://abc;}}
在处理 upsream 的 server 指令时,会调用 ngx_parse_url
处理 server参数,其中会获取域名对应的ip,解析到ip后,其余的处理流程,就和server指令参数是ip的一样了。
proxy_pass 如何区分参数 是 域名 还是 upstream块的名字?
例如
upstrem www.exapmle.com {server 127.0.0.1;}server {listen 80;location / {proxy_pass http://www.exapmle.com;}}
Nginx是去 解析www.example.com
的ip然后去访问,还是去127.0.0.1
去访问?
Nginx 逻辑是这样的:
1:proxy_pass 参数 除了是ip地址以为,其余的全部默认为 upstream 块名字。
2:proxy_pass 参数 如果是变量,变量解析完成之后,按照1处理。
3:当 配置解析完成之后,发现 upstream 里面的 server 不存在时,则在ngx_http_upstream_init_round_robin
中,进行解析。
由此可见,域名解析都在配置阶段进行了处理,若是想要通过变量形式动态获取到某个域名,默认情况下,解析域名是行不通的。因为变量处理时在请求处理阶段早就过了配置解析阶段。
例如下面这个配置,想要访问 "www.efg.com"是不成功的,因为Nginx会去找名为"www.efg.com"的upstream块,但是未找到。
server {listen 80;set $ups abc;if ( $host = "www.efg.com" ) {set $ups "www.efg.com";}location / {proxy_pass http://$ups;}}
想要动态解析域名的方式是 配置 resolver
指令。
resolver 114.114.114.114;server {listen 80;set $ups abc;if ( $host = "www.efg.com" ) {set $ups "www.efg.com";}location / {proxy_pass http://$ups;}}
由此可见,Nginx代码真烂
Nginx源码分析之 upstream指令相关推荐
- Nginx 源码分析-- 模块module 解析执行 nginx.conf 配置文件流程分析 一
搭建nginx服务器时,主要的配置文件 nginx.conf 是部署和维护服务器人员经常要使用到的文件, 里面进行了许多服务器参数的设置.那么nginx 以模块 module为骨架的设计下是如何运用模 ...
- nginx源码分析(5)——监听socket初始化
在nginx源码分析(4)中,看到了nginx的事件模型,但其中没有介绍监听socket的初始化.而对于web server来说,需要通过监听socket来监听客户端的连接等.本篇将会具体介绍这方面的 ...
- nginx源码分析之网络初始化
nginx作为一个高性能的HTTP服务器,网络的处理是其核心,了解网络的初始化有助于加深对nginx网络处理的了解,本文主要通过nginx的源代码来分析其网络初始化. 从配置文件中读取初始化信息 与网 ...
- Nginx源码分析链接
nginx-0.8.38源码探秘:http://blog.csdn.net/ccdd14/article/details/5872312 nginx源码分析: http://blog.sina.com ...
- Nginx源码分析:epoll事件处理模块概述
nginx源码分析 nginx-1.11.1 参考书籍<深入理解nginx模块开发与架构解析> 事件处理模块概述 Nginx的高效请求的处理依赖于事件管理机制,本次默认的场景是Linux操 ...
- Nginx源码分析:惊群处理与负载均衡
nginx源码分析 nginx-1.11.1 参考书籍<深入理解nginx模块开发与架构解析> Nginx的惊群处理与负载均衡概述 当Nginx工作在master/worker模式下时,就 ...
- Nginx源码分析:核心数据结构ngx_cycle_t与内存池概述
nginx源码分析 nginx-1.11.1 参考书籍<深入理解nginx模块开发与架构解析> 核心数据结构与内存池概述 在Nginx中的核心数据结构就是ngx_cycle_t结构,在初始 ...
- Nginx源码分析:master/worker工作流程概述
nginx源码分析 nginx-1.11.1 参考书籍<深入理解nginx模块开发与架构解析> Nginx的master与worker工作模式 在生成环境中的Nginx启动模式基本都是以m ...
- Nginx源码分析:启动流程
nginx源码分析 nginx-1.11.1 参考书籍<深入理解nginx模块开发与架构解析> nginx简介 Nginx的作为服务端软件,表现的主要特点是更快.高扩展.高可靠性.低内存消 ...
最新文章
- Arachni web扫描工具
- BZOJ 1691: [Usaco2007 Dec]挑剔的美食家( 平衡树 )
- nginx四层端口转发
- idea中war和war exploded的区别及修改jsp必须重新启动tomcat才能生效的问题(转)
- 【渝粤题库】陕西师范大学209010 现代教育战略 作业 (专升本)
- python白森_氧气恋人小说-江白森修辞在线阅读-829阅读网
- advice 和 拦截器_ControllerAdvice拦截器
- r语言岭回归参数选择_数据分析中常见的七种回归分析以及R语言实现(三)---岭回归...
- 面面俱到,这 23 个公共数据集赶紧Mark起来!
- 微软内核工程师:ReactOS 剽窃了 Windows 研究内核代码!
- Perl语言程序设计_简介
- bat脚本转成exe执行程序
- 基于蚁群算法的多配送中心的车辆调度问题的研究(Matlab代码实现)
- cad灯具图标_灯具在CAD中怎么表示出来 都代表哪种灯 谢谢
- 我了解的软件测试基本概念
- Unity双击桌面快捷方式打不开怎么办?
- Hibernate占位符?和:及JPA占位符
- 计算机考研360能去哪里,计算机专业考研,有什么好的211院校推荐?
- ITRON入门学习之实时操作系统的意义与价值
- 如何自学Python爬虫,python爬虫快速入门教程