Nginx怎么做域名解析?怎么在你自己开发的模块里面使用Nginx提供的方法解析域名?它内部实现是什么样的?

本文以Nginx 1.5.1为例,从nginx_mail_smtp模块如何进行域名解析出发,分析Nginx进行域名解析的过程。为了简化流程,突出重点,在示例代码中省掉了一些异常部分的处理,比如内存分配失败等。




nginx dns 默认刷新 nginx如何配置dns解析_Nginx


DNS查询分为两种:根据域名查询地址和根据地址查询域名,在代码结构上这两种方式非常相似,这里只介绍根据域名查询地址这一种方式。本文将从以下几个方面进行介绍:

  1. 域名查询的函数接口介绍
  2. 域名解析流程分析
  3. 查询场景分析及实现介绍

一、域名查询的函数接口介绍

在使用同步IO的情况下,调用gethostbyname()或者gethostbyname_r()就可以根据域名查询到对应的IP地址, 但因为可能会通过网络进行远程查询,所以需要的时间比较长。


nginx dns 默认刷新 nginx如何配置dns解析_dns解析过程_02


为了不阻塞当前线程,Nginx采用了异步的方式进行域名查询。整个查询过程主要分为三个步骤,这点在各种异步处理时都是一样的:

  1. 准备函数调用需要的信息,并设置回调方法
  2. 调用函数
  3. 处理结束后回调方法被调用

另外,为了尽量减少查询花费的时间,Nginx还对查询结果做了本地缓存。为了初始化DNS Server地址和本地缓存等信息,需要在真正查询前需要先进行一些全局的初始化操作。

下面先从调用者的角度对每个步骤做详细的分析:

  1. 初始化域名查询所需要的的全局信息
  2. 需要初始化的全局信息包括:
  3. 因为resolver是全局的,与任何一个connection都无关,所有需要放在一个随时都可以取到的地方,如 ngx_mail_core_srv_conf_t结构体上,在使用时从当前session找到ngx_mail_core_srv_conf_t,然后找到resolver。
  4. DNS 服务器的信息需要在配置文件中明确指出,比如
#nginx.conf resolver 8.8.8.8#nginx 默认会根据DNS请求结果里的TTL值来进行缓存,#当然也可以通过一个可选的参数valid来设置过期时间,如:#resolver 127.0.0.1 [::1]:5353 valid=30s;
  1. 下面根据配置中的resolver参数,初始化全局的ngx_resolver_t,其中保存了前面提及的DNS服务器地址和查询结果等信息:
static char *ngx_mail_core_resolver(ngx_conf_t *cf, ngx_command_t *cmd, void *conf){ ngx_mail_core_srv_conf_t *cscf = conf; ngx_str_t *value; value = cf->args->elts;  cscf->resolver = ngx_resolver_create(cf, &value[1], cf->args->nelts - 1); return NGX_CONF_OK;}
  • DNS 服务器的地址,如果指定了多个服务器,nginx会采用Round Robin的方式轮流查询每个服务器
  • 对查询结果的缓存,采用Red Black Tree的数据结构,以要查询名字的Hash作为Key, 节点信息存放在 struct ngx_resolver_node_t中。
  1. 准备本次查询的信息
  2. 和本次查询相关的信息放在ngx_resolver_ctx_t结构体中,包括要查询的名称,查询完的回调方法,以及超时时间等。如果本次要查询的地址已经是IPv4用点分隔的地址了,比如74.125.128.100, nginx会在ngx_resolve_start中进行判断,并设置好标志位,在调用ngx_resolve_name时不会发送真正的DNS查询请求。
static voidngx_mail_smtp_resolve_name(ngx_event_t *rev){ ngx_connection_t *c; ngx_mail_session_t *s; ngx_resolver_ctx_t *ctx; ngx_mail_core_srv_conf_t *cscf;  c = rev->data; s = c->data;  cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);  ctx = ngx_resolve_start(cscf->resolver, NULL); if (ctx == NULL) { ngx_mail_close_connection(c); return; }  ctx->name = s->host; ctx->type = NGX_RESOLVE_A; ctx->handler = ngx_mail_smtp_resolve_name_handler; ctx->data = s; ctx->timeout = cscf->resolver_timeout;  //根据名字进行IP地址查询 if (ngx_resolve_name(ctx) != NGX_OK) { ngx_mail_close_connection(c); }}
  1. 根据名字进行IP地址查询
  2. 前面方法的最后通过ngx_resolve_name方法进行IP地址查询。查询时,Nginx会先检查本地缓存,如果在缓存中,就更新缓存过期时间,并回调设置的handler, 如前面设置的:ngx_mail_smtp_resolve_name_handler,然后整个查询过程结束。如果没有在缓存中就发送查询请求给dns server,同时方法返回。
  3. 查询完成后回调在ngx_resolver_ctx_t中指定的方法
  4. 真正的DNS查询完成后,不管成功,失败或是超时,nginx会回调相应查询的handler, 如前面设置的:ngx_mail_smtp_resolve_name_handler。在handler中都需要调用ngx_resolve_addr_done来标识查询结束。
static voidngx_mail_smtp_resolve_name_handler(ngx_resolver_ctx_t *ctx){ in_addr_t addr; ngx_uint_t i; ngx_connection_t *c; struct sockaddr_in *sin; ngx_mail_session_t *s;  s = ctx->data; c = s->connection;  if (ctx->state) { ngx_log_error(NGX_LOG_ERR, c->log, 0, ""%V" could not be resolved (%i: %s)