{{ v.name }}
{{ v.cls }}类
{{ v.price }} ¥{{ v.price }}
在nginx中,nginx需要频繁进行域名解析的过程做了自己的优化,使用了自己的一套域名解析过程,并做了缓存处理。我们可以设置dns解析服务器的地址,即通过resolver指令来设置dns服务器的地址,由此来启动nginx的域名解析。
本文,我们来看看nginx是如何做的,这里我们只选出重要的代码进行分析,完整代码请参考nginx源代码,本文基于nginx-1.0.6版本进行的分析。
首先,来看看resolver的初始化。在ngx_http_core_loc_conf_s的声明中,可以看到对reolver:
structngx_http_core_loc_conf_s{ngx_resolver_t*resolver;/*resolver*/}
resolver中保存了与域名解析相关的一些数据,它保存了dns的本地缓存,通过红黑树的方式来组织数据,以达到快速查找。
typedefstruct{ngx_event_t*event;//用于连接dns服务器ngx_udp_connection_t*udp_connection;//保存了本地缓存的dns数据ngx_rbtree_tname_rbtree;ngx_rbtree_node_tname_sentinel;}ngx_resolver_t;
在nginx初始化的时候,通过ngx_resolver_create来初始化这一结构体,如果有设置resolver,则在ngx_http_core_resolver中有调用:
staticchar*ngx_http_core_resolver(ngx_conf_t*cf,ngx_command_t*cmd,void*conf){ngx_http_core_loc_conf_t*clcf=conf;//初始化,第二个参数是我们设置的域名解析服务器的ip地址clcf->resolver=ngx_resolver_create(cf,&u.addrs[0]);if(clcf->resolver==null){returnngx_ok;}returnngx_conf_ok;}
来看看ngx_resolver_create做了些什么:
ngx_resolver_t*ngx_resolver_create(ngx_conf_t*cf,ngx_addr_t*addr){ngx_resolver_t*r;ngx_udp_connection_t*uc;r=ngx_calloc(sizeof(ngx_resolver_t),cf->log);if(r==null){returnnull;}//省略了其它数据的初始化过程r->event=ngx_calloc(sizeof(ngx_event_t),cf->log);if(r->event==null){returnnull;}//设置事件的句柄r->event->handler=ngx_resolver_resend_handler;r->event->data=r;//设置dns服务器的地址if(addr){uc=ngx_calloc(sizeof(ngx_udp_connection_t),cf->log);if(uc==null){returnnull;}r->udp_connection=uc;uc->sockaddr=addr->sockaddr;uc->socklen=addr->socklen;uc->server=addr->name;}returnr;}
初始化好了之后,就可以调用了。在nginx中,upstream中使用到了此方法的域名解析。我们结合proxy模块与upstream模块来实例讲解吧,注意在proxy中,只有当proxy_pass中包含有变量时,才会用到nginx自己的dns解析。而且这里有一个需要特别注意的,如果proxy_pass中包含变量,那么nginx中就需要配置resolver来指定dns服务器地址了,否则,将直接返回502错误。从下面的代码中我们可以看到。首先,在ngx_http_proxy_handler函数中,有如下代码:
staticngx_int_tngx_http_proxy_handler(ngx_http_request_t*r){//这里的意思是,如果没有变量,就不进行变量解析if(plcf->proxy_lengths==null){ctx->vars=plcf->vars;u->schema=plcf->vars.schema;}else{//只有当proxy_pass里面包含变量时,才解析变量,在ngx_http_proxy_eval中会添加域名解析的需求,请看ngx_http_proxy_eval的实现if(ngx_http_proxy_eval(r,ctx,plcf)!=ngx_ok){returnngx_http_internal_server_error;}}}
而在proxy模块的ngx_http_proxy_eval函数中,可以看到如下代码:
staticngx_int_tngx_http_proxy_eval(ngx_http_request_t*r,ngx_http_proxy_ctx_t*ctx,ngx_http_proxy_loc_conf_t*plcf){ngx_str_tproxy;ngx_url_turl;//proxy为要转向的urlurl.url.data=proxy.data+add;url.default_port=port;url.uri_part=1;//注意这里设置的为不用解析域名url.no_resolve=1;//由于有设置不用解析域名,所以在ngx_parse_url中就不会对域名进行解析if(ngx_parse_url(r->pool,&url)!=ngx_ok){returnngx_error;}//保存与需要解析域名相关的信息u->resolved=ngx_pcalloc(r->pool,sizeof(ngx_http_upstream_resolved_t));if(u->resolved==null){returnngx_error;}if(url.addrs&&url.addrs[0].sockaddr){//如果域名已经是ip地址的格式,就保存起来,这样在upstream里面就不会再进行解析//在upsteam模块里面会判断u->resolved->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{u->resolved->host=url.host;u->resolved->port=(in_port_t)(url.no_port?port:url.port);u->resolved->no_port=url.no_port;}}
所以,可以看出,只在当proxy_pass到包含变量的url时,才有可能进行域名的解析。因为如果是固定的url,则完全可以在初始化的时候解析域名,而不用在请求的时候进行了。关于这部分代码的实现,可以参考ngx_http_upstream_init_round_robin函数,而且注意,在proxy_pass时,是直接添加upstream来实现的,等有机会介绍upstream代码时再做解释。接下来在upstream中ngx_http_upstream_init_request在初始化请求时,当u->resolved为不空时,需要解析域名。看代码:
staticvoidngx_http_upstream_init_request(ngx_http_request_t*r){ngx_str_t*host;ngx_http_upstream_t*u;u=r->upstream;//如果已经是ip地址格式了,就不需要再进行解析if(u->resolved->sockaddr){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;}//接下来就要开始查找域名host=&u->resolved->host;temp.name=*host;//初始化域名解析器ctx=ngx_resolve_start(clcf->resolver,&temp);if(ctx==null){ngx_http_upstream_finalize_request(r,u,ngx_http_internal_server_error);return;}//返回ngx_no_resolver表示无法进行域名解析if(ctx==ngx_no_resolver){ngx_log_error(ngx_log_err,r->connection->log,0,"noresolverdefinedtoresolve%v",host);ngx_http_upstream_finalize_request(r,u,ngx_http_bad_gateway);return;}//设置需要解析的域名的类型与信息ctx->name=*host;ctx->type=ngx_resolve_a;//解析完成后的回调函数ctx->handler=ngx_http_upstream_resolve_handler;ctx->data=r;u->resolved->ctx=ctx;//开始解析域名if(ngx_resolve_name(ctx)!=ngx_ok){u->resolved->ctx=null;ngx_http_upstream_finalize_request(r,u,ngx_http_internal_server_error);return;}//域名还没有解析完成,则直接返回return;//其它动作}
在上面的代码中,我们可以看到,需要解析域名,我们调用ngx_resolve_start,设置好回调函数等上下文信息后,然后再调用ngx_resolve_name,等域名解析完成后会调用ngx_http_upstream_resolve_handler。那ngx_resolve_start函数的主要工作是初始化当前解析请求的上下文:
ngx_resolver_ctx_t*ngx_resolve_start(ngx_resolver_t*r,ngx_resolver_ctx_t*temp){in_addr_taddr;ngx_resolver_ctx_t*ctx;