首页 > Nginx > Nginx upstream原理分析【0】指令解析

Nginx upstream原理分析【0】指令解析

2013年5月23日 发表评论 阅读评论 10555次阅读    

熟悉nginx的同学都知道upstream的重要作用,当nginx接收到一个连接后,读取完客户端发送出来的Header,然后就会进行各个处理过程的调用。

之后就是upstream发挥作用的时候了,upstream在客户端跟后端比如FCGI/PHP之间,接收客户端的HTTP body,发送给FCGI,然后接收FCGI的结果,发送给客户端。作为一个桥梁的作用。同时,upstream为了充分显示其灵活性,至于后端具体是什么协议,什么系统他都不care,我只实现主体的框架,具体到FCGI协议的发送,接收,解析,这些都交给后面的插件来处理,比如有fastcgi,memcached,proxy等插件,我之前也做过一个简单的跟公司内部系统的module,其实他们都是一种回调形式的模块,跟upstream模块配合从而完成整个请求。如无特殊说明,后面都已FCGI作为后端模块为例说明。

0、跟FCGI协议处理模块的关系:

upstream有个重要的结构体ngx_http_upstream_s,每个请求一个独立的结构体,其存储了upstream的绝大部分数据,比如各个读写回调,接收到的数据缓冲链表,要发送给客户端的缓冲链表。

其中一套很重要的回调函数是跟FCGI模块的交互接口,比如create_request,process_header 是其中最重要的2个函数,他们的作用分别为:

create_request用来创建要发送给后面FCGI的FCGI格式的数据,存放在u->request_bufs链接表里面。用来待会进行发送。具体看这个ngx_http_upstream_s结构体的内容,字段含义注释在后面了。

struct ngx_http_upstream_s {//本结构体用来保存一个连接的upstream信息,包括各种需要upstream回调的函数等。
    ngx_http_upstream_handler_pt     read_event_handler; //ngx_http_upstream_process_header
    ngx_http_upstream_handler_pt     write_event_handler;// ngx_http_upstream_send_request_handler

    ngx_peer_connection_t            peer;

    ngx_event_pipe_t                *pipe;

    ngx_chain_t                     *request_bufs;//客户端发送过来的数据body部分,在ngx_http_upstream_init_request设置为客户端发送的HTTP BODY
    //也可能是代表要发送给后端的数据链表结构,比如ngx_http_proxy_create_request会这么放的。比如是FCGI结构数据,或者Proxy结构等。

    ngx_output_chain_ctx_t           output;//输出数据的结构,里面存有要发送的数据,以及发送的output_filter指针
    ngx_chain_writer_ctx_t           writer;//参考ngx_chain_writer,里面会将输出buf一个个连接到这里。

    ngx_http_upstream_conf_t        *conf;//为u->conf = &flcf->upstream;

    ngx_http_upstream_headers_in_t   headers_in;//存放从上游返回的头部信息,

    ngx_http_upstream_resolved_t    *resolved;//解析出来的fastcgi_pass   127.0.0.1:9000;后面的字符串内容,可能有变量嘛。

    ngx_buf_t                        buffer;///读取上游返回的数据的缓冲区,也就是proxy,FCGI返回的数据。这里面有http头部,也可能有body部分。
    									//其body部分会跟event_pipe_t的preread_bufs结构对应起来。就是预读的buf,其实是i不小心读到的。
    size_t                           length;//要发送给客户端的数据大小,还需要读取这么多进来。

    ngx_chain_t                     *out_bufs;//这个是要发送给客户端的数据链接表?
    ngx_chain_t                     *busy_bufs;//调用了ngx_http_output_filter,并将out_bufs的链表数据移动到这里,待发送完毕后,会移动到free_bufs
    ngx_chain_t                     *free_bufs;//空闲的缓冲区。可以分配

    ngx_int_t                      (*input_filter_init)(void *data);//进行初始化,没什么用,memcache设置为ngx_http_memcached_filter_init
    ngx_int_t                      (*input_filter)(void *data, ssize_t bytes);//用来读取后端的数据,非buffering模式。
    										//ngx_http_upstream_non_buffered_filter,ngx_http_memcached_filter等。
    										//这个函数的调用时机: ngx_http_upstream_process_non_buffered_upstream等调用ngx_unix_recv接收到upstream返回的数据后
    										//就调用这里进行协议转换,不过目前转换不多。

    void                            *input_filter_ctx;//指向所属的请求等上下文

#if (NGX_HTTP_CACHE)
    ngx_int_t                      (*create_key)(ngx_http_request_t *r);
#endif
	//下面的upstream回调指针是各个模块设置的,比如ngx_http_fastcgi_handler里面设置了fcgi的相关回调函数。
    ngx_int_t                      (*create_request)(ngx_http_request_t *r);//生成发送到上游服务器的请求缓冲(或者一条缓冲链)
    ngx_int_t                      (*reinit_request)(ngx_http_request_t *r);//在后端服务器被重置的情况下(在create_request被第二次调用之前)被调用
    ngx_int_t                      (*process_header)(ngx_http_request_t *r);//处理上游服务器回复的第一个bit,时常是保存一个指向上游回复负载的指针
    void                           (*abort_request)(ngx_http_request_t *r);//在客户端放弃请求的时候被调用
    void                           (*finalize_request)(ngx_http_request_t *r,//在Nginx完成从上游服务器读入回复以后被调用
                                         ngx_int_t rc);
    ngx_int_t                      (*rewrite_redirect)(ngx_http_request_t *r,
                                         ngx_table_elt_t *h, size_t prefix);

    ngx_msec_t                       timeout;

    ngx_http_upstream_state_t       *state;//当前的状态

    ngx_str_t                        method;//GET,HEAD,POST
    ngx_str_t                        schema;//就是前面的http,https,mecached://等。
    ngx_str_t                        uri;

    ngx_http_cleanup_pt             *cleanup;//ngx_http_upstream_cleanup

    unsigned                         store:1;
    unsigned                         cacheable:1;
    unsigned                         accel:1;
    unsigned                         ssl:1;
#if (NGX_HTTP_CACHE)
    unsigned                         cache_status:3;
#endif
    unsigned                         buffering:1;//是否要buffer 后端的数据,如果要用event_pipe的方式发送数据

    unsigned                         request_sent:1;//是否已经将request_bufs的数据放入输出链表里面
    unsigned                         header_sent:1;//标记已经发送了头部字段。
};

上面的会回调函数我们会在后面不断引用介绍的。这里先带过知道其存在即可。

1、upstream模块配置解析:

这个模块的配置指令很少,只有upstream,server2个指令。官网列了个格式案例:

upstream backend {
server backend1.example.com weight=5;
server backend2.example.com:8080;
server unix:/tmp/backend3;
}

              server {
location / {
proxy_pass http://backend;
}
}

下面分2块介绍这2条指令。

1.1、upstream指令解析:

这个指令其实是个指令块,upstream{},不涉及到具体参数等解析。当nginx解析到这个块block的时候,会调用ngx_http_upstream函数进行处理。函数比较简单,首先将u代表名字比如上面的“backend”的数据设置到umcf->upstreams里面去。然后返回对应的upstream{}结构数据指针。也就是增加一个命名的upstream,放到umcf->upstreams数组里面去。这个数组是每http{}块都有一份的。或者里面有的话也可以。Context: http,不能存在server里面的。这个数组,代表有多少个upstream{}块。存放server xx.xx.xx.xx:xx weight=2 max_fails=3;  信息的数组。当然从之前的文章中可以看到,这样的语句也能产生一个新的upstream结构:fastcgi_pass 127.0.o.1:8888;

typedef struct {//这个数组是每http{}块都有一份的。或者里面有的话也可以。Context: 	http,不能存在server里面的
    ngx_hash_t                       headers_in_hash;//ngx_http_upstream_headers_in里面的数据.
    ngx_array_t                      upstreams;//数组,代表有多少个upstream{}块。server xx.xx.xx.xx:xx weight=2 max_fails=3;  信息的数组。
    //由ngx_http_upstream_create_main_conf函数返回。存放在上层的ctx中。
                                             /* ngx_http_upstream_srv_conf_t */
} ngx_http_upstream_main_conf_t;

继续上面的分析,接着nginx会创建一个ngx_http_conf_ctx_t结构体,这个不陌生了,使用来存储该指令上下文的,过几天写个文章介绍一下nginx的配置解析,这个很有意思。nginx的配置文件比apache什么的复杂多了。
我们可以从ngx_http_upstream()看到,upstream指令块的上下文ctx结构中,main_conf是跟上次http{}块共用的,这样在upstream里面修改的main_conf等都是跟http一个。然后就是创建了srv_conf,loc_conf,然后循环调用各个模块的create_srv_conf,create_loc_conf,注意没有main_conf,公用的。
之后就是用新分配的ctx替换原来的ctx:cf->ctx = ctx;临时切换ctx,进入upstream{}块中进行解析。切换后,调用ngx_conf_parse(cf, NULL);进行块内的解析,这样在块内获取到的配置数据都是刚刚申请的这个ctx,而不是http{}层的ctx了。简单看一下下面函数:

static char *
ngx_http_upstream(ngx_conf_t *cf, ngx_command_t *cmd, void *dummy)
{//当碰到upstream{}指令的时候调用这里。
    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代表的数据设置到umcf->upstreams里面去。然后返回对应的upstream{}结构数据指针。
    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) {
        return NGX_CONF_ERROR;
    }
	//申请ngx_http_conf_ctx_t结构,经典的main/srv/local_conf指针结构
    ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_conf_ctx_t));
    if (ctx == NULL) {
        return NGX_CONF_ERROR;
    }
    http_ctx = cf->ctx;//跟上层的HTTP公用main_conf,这里跟server{}指令一样的,共享main_conf
    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;
    }
	//在ngx_http_upstream_module模块里面记录我的srv_conf。里面记录了我里面有哪几个server指令
    ctx->srv_conf[ngx_http_upstream_module.ctx_index] = uscf;//uscf里面记录了server列表信息。
    uscf->srv_conf = ctx->srv_conf;//这一条,记住我这个upstream所属的srv_conf数组。也就是所属的http{}块里面的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; ngx_modules[m]; m++) {//老规矩,初始化所有HTTP模块的srv,loc配置。调用每个模块的create回调
        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) {
                return NGX_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) {
                return NGX_CONF_ERROR;
            }
            ctx->loc_conf[ngx_modules[m]->ctx_index] = mconf;
        }
    }
    /* parse inside upstream{} */
    pcf = *cf;
    cf->ctx = ctx;//临时切换ctx,进入upstream{}块中进行解析。
    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 == NULL) { "no servers are inside upstream");
        return NGX_CONF_ERROR;
    }
    return rv;
}

上面代码中,ngx_http_upstream_add函数比较重要,它用来增加一个upstream的.如果u代表的server已经存在,则返回句柄,否则在umcf->upstreams里面新加一个,设置初始化。ngx_http_fastcgi_pass等函数也会调用这个函数用来增加一个upstream的server.
函数通过根据名字等查找umcf->upstreams.elts数组,看看是否存在一个名字相同的upstream,如果有就返回其ngx_http_upstream_srv_conf_t的地址。否则新建一个ngx_http_upstream_srv_conf_t结构,存放相关的配置,然后分配uscf->servers数组,这个数组就是用来记录server xxxx;指令的数组。如下图所示:
upstream_srv_conf_t
下面看一下ngx_http_upstream_add的代码和注释:

ngx_http_upstream_srv_conf_t *
ngx_http_upstream_add(ngx_conf_t *cf, ngx_url_t *u, ngx_uint_t flags)
{//如果u代表的server已经存在,则返回句柄,否则在umcf->upstreams里面新加一个,设置初始化。
//在ngx_http_fastcgi_pass等碰到后端地址的地方,会调用这个函数,增加一个upstream的server.
//这里的单位是upstream,不是server行,调用的上层一般只有一个地址,于是就只有一个server.当做单个upstream处理
    ngx_uint_t                      i;
    ngx_http_upstream_server_t     *us;
    ngx_http_upstream_srv_conf_t   *uscf, **uscfp;
    ngx_http_upstream_main_conf_t  *umcf;

    if (!(flags & NGX_HTTP_UPSTREAM_CREATE)) {//如果没有设置CREATE标志,表示不需要创建。
        if (ngx_parse_url(cf->pool, u) != NGX_OK) {//简析一下地址格式,名称,unix:域,inet6,4地址,http://host:port/等
            if (u->err) {
                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,  "%s in upstream \"%V\"", u->err, &u->url);
            }
            return NULL;
        }
    }

    umcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_upstream_module);
    uscfp = umcf->upstreams.elts;
    for (i = 0; i < umcf->upstreams.nelts; i++) {
		//遍历当前的upstream,如果有重复的,则比较其相关的字段,并打印日志。如果找到相同的,则返回对应指针。
        if (uscfp[i]->host.len != u->host.len || ngx_strncasecmp(uscfp[i]->host.data, u->host.data, u->host.len)  != 0) {
            continue;//不相同的不管。
        }

        if ((flags & NGX_HTTP_UPSTREAM_CREATE) && (uscfp[i]->flags & NGX_HTTP_UPSTREAM_CREATE)) {
            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,  "duplicate upstream \"%V\"", &u->host);
            return NULL;
        }
        if ((uscfp[i]->flags & NGX_HTTP_UPSTREAM_CREATE) && u->port) {
            ngx_conf_log_error(NGX_LOG_WARN, cf, 0,  "upstream \"%V\" may not have port %d", &u->host, u->port);
            return NULL;
        }
        if ((flags & NGX_HTTP_UPSTREAM_CREATE) && uscfp[i]->port) {
            ngx_log_error(NGX_LOG_WARN, cf->log, 0, "upstream \"%V\" may not have port %d in %s:%ui",
                          &u->host, uscfp[i]->port, uscfp[i]->file_name, uscfp[i]->line);
            return NULL;
        }
        if (uscfp[i]->port != u->port) {
            continue;
        }
        if (uscfp[i]->default_port && u->default_port  && uscfp[i]->default_port != u->default_port)  {
            continue;
        }
        return uscfp[i];//找到相同的配置数据了,直接返回它的指针。
    }
	//没有找到相同的配置upstream,下面创建一个。这里的srv_conf跟server{}不是一回事,是指upstream{}里面的server xxxx:xxx;行
    uscf = ngx_pcalloc(cf->pool, sizeof(ngx_http_upstream_srv_conf_t));
    if (uscf == NULL) {
        return NULL;
    }
    uscf->flags = flags;
    uscf->host = u->host;//这里的host其实可以为任何字符串都行的。
    uscf->file_name = cf->conf_file->file.name.data;
    uscf->line = cf->conf_file->line;
    uscf->port = u->port;//没有就默认80
    uscf->default_port = u->default_port;

    if (u->naddrs == 1) {//比如: server xx.xx.xx.xx:xx weight=2 max_fails=3;  刚开始,ngx_http_upstream会调用本函数。但是其naddres=0.
        uscf->servers = ngx_array_create(cf->pool, 1,  sizeof(ngx_http_upstream_server_t));
        if (uscf->servers == NULL) {
            return NGX_CONF_ERROR;
        }
        us = ngx_array_push(uscf->servers);//记录本upstream{}块的所有server指令。
        if (us == NULL) {
            return NGX_CONF_ERROR;
        }
        ngx_memzero(us, sizeof(ngx_http_upstream_server_t));
        us->addrs = u->addrs;//拷贝地址 信息。
        us->naddrs = u->naddrs;
    }
	//
    uscfp = ngx_array_push(&umcf->upstreams);//放到upstream的main_conf里面去。
    if (uscfp == NULL) {
        return NULL;
    }
    *uscfp = uscf;//在当前的umcf->upstreams updstream配置里面增加一项。
    return uscf;
}

1.2、server指令解析:

server指令解析函数是ngx_http_upstream_server,用来在所在upstream中增加一个server配置。

当解析到"server"的时候调用ngx_http_upstream_server这里.里面只是在uscf->servers里面增加了一个server,也就是面途中的server,最后下面的一个组成部分并设置好ngx_http_upstream_server_t结构的数据。其他没干。下面看一下代码和注释:

static char *
ngx_http_upstream_server(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{//解析到"server"的时候调用这里.里面只是在uscf->servers里面增加了一个server,并设置好ngx_http_upstream_server_t结构的数据。其他没干。
    ngx_http_upstream_srv_conf_t  *uscf = conf;//从ngx_conf_handler里面可以看出,这个conf就是upstream的ctx->srv_conf[upstream模块.index]的值
	//upstream模块的conf为NGX_HTTP_SRV_CONF_OFFSET,所以决定了ngx_conf_handler里面的conf参数
    time_t                       fail_timeout;
    ngx_str_t                   *value, s;
    ngx_url_t                    u;
    ngx_int_t                    weight, max_fails;
    ngx_uint_t                   i;
    ngx_http_upstream_server_t  *us;

    if (uscf->servers == NULL) {//如果本upstream的servers数组为空,初始化之
        uscf->servers = ngx_array_create(cf->pool, 4, sizeof(ngx_http_upstream_server_t));
        if (uscf->servers == NULL) {
            return NGX_CONF_ERROR;
        }
    }
    us = ngx_array_push(uscf->servers);
//增加一个server.下面如果配置失败,会直接返回失败的,也就不需要把这项删了。
    if (us == NULL) {
        return NGX_CONF_ERROR;
    }
    ngx_memzero(us, sizeof(ngx_http_upstream_server_t));
    value = cf->args->elts;
    ngx_memzero(&u, sizeof(ngx_url_t));
    u.url = value[1];
    u.default_port = 80;

    if (ngx_parse_url(cf->pool, &u) != NGX_OK) {//解析一下URL的数据结构。
        if (u.err) {
            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "%s in upstream \"%V\"", u.err, &u.url);
        }
        return NGX_CONF_ERROR;
    }
    weight = 1;
    max_fails = 1;
    fail_timeout = 10;
    for (i = 2; i < cf->args->nelts; i++) {
//遍历后面的每一个参数,比如: server 127.0.0.1:8080 max_fails=3 fail_timeout=30s;
        if (ngx_strncmp(value[i].data, "weight=", 7) == 0) {//得到整数类型的权重。
            if (!(uscf->flags & NGX_HTTP_UPSTREAM_WEIGHT)) {
                goto invalid;
            }//weight = NUMBER - set weight of the server, if not set weight is equal to one.
            weight = ngx_atoi(&value[i].data[7], value[i].len - 7);
            if (weight == NGX_ERROR || weight == 0) {
                goto invalid;
            }
            continue;
        }
        if (ngx_strncmp(value[i].data, "max_fails=", 10) == 0) {
	//解析max_fails参数,表示
            if (!(uscf->flags & NGX_HTTP_UPSTREAM_MAX_FAILS)) {
                goto invalid;
            }//NUMBER - number of unsuccessful attempts at communicating with the server within the time period
            max_fails = ngx_atoi(&value[i].data[10], value[i].len - 10);
            if (max_fails == NGX_ERROR) {
                goto invalid;
            }
            continue;
        }
        if (ngx_strncmp(value[i].data, "fail_timeout=", 13) == 0) {
	//这么fail_timeout多的时间内,出现max_fails的失败的服务器将被标记为出问题的。
            if (!(uscf->flags & NGX_HTTP_UPSTREAM_FAIL_TIMEOUT)) {
                goto invalid;
            }//fail_timeout = TIME - the time during which must occur *max_fails* number of unsuccessful attempts at communication with the server that would cause the server to be considered inoperative
            s.len = value[i].len - 13;
            s.data = &value[i].data[13];
            fail_timeout = ngx_parse_time(&s, 1);
            if (fail_timeout == NGX_ERROR) {
                goto invalid;
            }
            continue;
        }
        if (ngx_strncmp(value[i].data, "backup", 6) == 0) {
            if (!(uscf->flags & NGX_HTTP_UPSTREAM_BACKUP)) {
                goto invalid;
            }//backup - (0.6.7 or later) only uses this server if the non-backup servers are all down or busy
            us->backup = 1;
            continue;
        }
        if (ngx_strncmp(value[i].data, "down", 4) == 0) {
            if (!(uscf->flags & NGX_HTTP_UPSTREAM_DOWN)) {
                goto invalid;
            }
            us->down = 1;
            continue;
        }

        goto invalid;
    }
	//下面拷贝设置一下这个server的相关信息。
    us->addrs = u.addrs;
    us->naddrs = u.naddrs;
    us->weight = weight;
    us->max_fails = max_fails;
    us->fail_timeout = fail_timeout;
    return NGX_CONF_OK;

invalid:
    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid parameter \"%V\"", &value[i]);
    return NGX_CONF_ERROR;
}

upstream模块就包含这2个指令,到这里介绍完了,基本就死http{}块的main_conf里面包含一个upstreams数组,其里面记录了所有upstream{}块的配置。每个upstreams数组元素代表一个upstream{}块,其里面又有servers数组,代表每条server指令。
由于upstream模块比较复杂,有点长,因此后面分篇文章一一介绍吧。

Share
分类: Nginx 标签: , , ,
  1. 本文目前尚无任何评论.
  1. 本文目前尚无任何 trackbacks 和 pingbacks.

注意: 评论者允许使用'@user空格'的方式将自己的评论通知另外评论者。例如, ABC是本文的评论者之一,则使用'@ABC '(不包括单引号)将会自动将您的评论发送给ABC。使用'@all ',将会将评论发送给之前所有其它评论者。请务必注意user必须和评论者名相匹配(大小写一致)。