#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_tu->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_lengthsplcf->proxy_values 被赋上了值。即plcf->upstream.upstream中的upstream字段未被赋值。

具体被赋值的时间点,是在业务处理流程函数ngx_http_proxy_handlerngx_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_evalproxy_lengthsproxy_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指令相关推荐

  1. Nginx 源码分析-- 模块module 解析执行 nginx.conf 配置文件流程分析 一

    搭建nginx服务器时,主要的配置文件 nginx.conf 是部署和维护服务器人员经常要使用到的文件, 里面进行了许多服务器参数的设置.那么nginx 以模块 module为骨架的设计下是如何运用模 ...

  2. nginx源码分析(5)——监听socket初始化

    在nginx源码分析(4)中,看到了nginx的事件模型,但其中没有介绍监听socket的初始化.而对于web server来说,需要通过监听socket来监听客户端的连接等.本篇将会具体介绍这方面的 ...

  3. nginx源码分析之网络初始化

    nginx作为一个高性能的HTTP服务器,网络的处理是其核心,了解网络的初始化有助于加深对nginx网络处理的了解,本文主要通过nginx的源代码来分析其网络初始化. 从配置文件中读取初始化信息 与网 ...

  4. Nginx源码分析链接

    nginx-0.8.38源码探秘:http://blog.csdn.net/ccdd14/article/details/5872312 nginx源码分析: http://blog.sina.com ...

  5. Nginx源码分析:epoll事件处理模块概述

    nginx源码分析 nginx-1.11.1 参考书籍<深入理解nginx模块开发与架构解析> 事件处理模块概述 Nginx的高效请求的处理依赖于事件管理机制,本次默认的场景是Linux操 ...

  6. Nginx源码分析:惊群处理与负载均衡

    nginx源码分析 nginx-1.11.1 参考书籍<深入理解nginx模块开发与架构解析> Nginx的惊群处理与负载均衡概述 当Nginx工作在master/worker模式下时,就 ...

  7. Nginx源码分析:核心数据结构ngx_cycle_t与内存池概述

    nginx源码分析 nginx-1.11.1 参考书籍<深入理解nginx模块开发与架构解析> 核心数据结构与内存池概述 在Nginx中的核心数据结构就是ngx_cycle_t结构,在初始 ...

  8. Nginx源码分析:master/worker工作流程概述

    nginx源码分析 nginx-1.11.1 参考书籍<深入理解nginx模块开发与架构解析> Nginx的master与worker工作模式 在生成环境中的Nginx启动模式基本都是以m ...

  9. Nginx源码分析:启动流程

    nginx源码分析 nginx-1.11.1 参考书籍<深入理解nginx模块开发与架构解析> nginx简介 Nginx的作为服务端软件,表现的主要特点是更快.高扩展.高可靠性.低内存消 ...

最新文章

  1. Arachni web扫描工具
  2. BZOJ 1691: [Usaco2007 Dec]挑剔的美食家( 平衡树 )
  3. nginx四层端口转发
  4. idea中war和war exploded的区别及修改jsp必须重新启动tomcat才能生效的问题(转)
  5. 【渝粤题库】陕西师范大学209010 现代教育战略 作业 (专升本)
  6. python白森_氧气恋人小说-江白森修辞在线阅读-829阅读网
  7. advice 和 拦截器_ControllerAdvice拦截器
  8. r语言岭回归参数选择_数据分析中常见的七种回归分析以及R语言实现(三)---岭回归...
  9. 面面俱到,这 23 个公共数据集赶紧Mark起来!
  10. 微软内核工程师:ReactOS 剽窃了 Windows 研究内核代码!
  11. Perl语言程序设计_简介
  12. bat脚本转成exe执行程序
  13. 基于蚁群算法的多配送中心的车辆调度问题的研究(Matlab代码实现)
  14. cad灯具图标_灯具在CAD中怎么表示出来 都代表哪种灯 谢谢
  15. 我了解的软件测试基本概念
  16. Unity双击桌面快捷方式打不开怎么办?
  17. Hibernate占位符?和:及JPA占位符
  18. 计算机考研360能去哪里,计算机专业考研,有什么好的211院校推荐?
  19. ITRON入门学习之实时操作系统的意义与价值
  20. 如何自学Python爬虫,python爬虫快速入门教程

热门文章

  1. ubuntumysql卸载
  2. 5G科普——什么是NFV
  3. tcpdump 抓包实时打印_tcpdump抓包规则常用命令
  4. python安装模块3种方法详解
  5. 网游服务器中的GUID(唯一标识码)实现-基于snowflake算法
  6. 实现数据权限控制的一种方法
  7. 使用MATLAB绘制二元函数图像
  8. 代码重构与体系结构重构
  9. Android布局中gravity、layout_gravity与layout_centerHorizontal属性区别介绍
  10. 【python批量处理pdf】pdf合并 + pdf转word文件