建议收藏 Nginx的DNS解析详细过程分析

Nginx怎么做域名解析?怎么在你自己开发的模块里面使用Nginx提供的方法解析域名?它内部实现是什么样的?
本文以Nginx 1.5.1为例 , 从nginx_mail_smtp模块如何进行域名解析出发 , 分析Nginx进行域名解析的过程 。为了简化流程 , 突出重点 , 在示例代码中省掉了一些异常部分的处理 , 比如内存分配失败等 。

建议收藏 Nginx的DNS解析详细过程分析

文章插图
 
DNS查询分为两种:根据域名查询地址和根据地址查询域名 , 在代码结构上这两种方式非常相似 , 这里只介绍根据域名查询地址这一种方式 。本文将从以下几个方面进行介绍:
  1. 域名查询的函数接口介绍
  2. 域名解析流程分析
  3. 查询场景分析及实现介绍
一、域名查询的函数接口介绍
在使用同步IO的情况下 , 调用gethostbyname()或者gethostbyname_r()就可以根据域名查询到对应的IP地址, 但因为可能会通过网络进行远程查询 , 所以需要的时间比较长 。
建议收藏 Nginx的DNS解析详细过程分析

文章插图
 
为了不阻塞当前线程 , 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 = https://www.isolves.com/it/wlyx/fwq/2019-08-26/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 = https://www.isolves.com/it/wlyx/fwq/2019-08-26/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 , 同时方法返回 。


    推荐阅读