配置解析阶段:
Syntax: resolver address ... [valid=time];
ngx_http_core_resolver()
clcf->resolver = ngx_resolver_create()
• 设置cleanup的handler (ngx_resolver_cleanup)
• 初始化保存域名节点信息的红黑树 (r->name_rbtree)
• 初始化重传和过期队列 (r->name_resend_queue r->name_expire_queue)
• 设置超时事件的handler (ngx_resolver_resend_handler)
• 解析dns server的ip并设置到地址数组 (r->udp_connections)
• 解析参数 (valid, ipv6 等)
请求构造阶段:
proxy_pass http://$host;
ngx_resolver_ctx_t ctx 每次域名解析都会生成这个结构体, 直接malloc,未使用r->pool.
ctx = ngx_resolve_start()
• 如果$host是ip地址, 直接设置ctx->quick = 1, 表示后续逻辑不需要走dns解析逻辑.
• 如果r->udp_connections 不存在, 返回NGX_NO_RESOLVER, 最终请求返回502.
初始化ctx参数
• ctx->type = NGX_RESOLVE_A;
• ctx->handler = ngx_http_upstream_resolve_handler;
• ctx->timeout = clcf->resolver_timeout;
• ngx_resolve_name(ctx)
• 如果 ctx->quick == 1, 直接调用 ctx->handler, 跳过dns解析.
• 否者调用 ngx_resolve_name_locked, 执行dns解析.
• ngx_resolve_name_locked(r, ctx)
1 调用ngx_resolver_lookup_name查找域名节点rn是否在r->name_rbtree缓存节点中, 存在进入(2), 否者进入 (5)
2 判断rn->valid是否过期,没有过期进入(3), 否者进入(4).
3 如果存在 rn->naddrs, 是A记录节点, 循环调用rn->waiting链表上的 ctx->handler, 然后函数返回OK; 如果不存在 rn->naddrs, 表示是CNAME记录节点, 那么递归调用ngx_resolve_name_locked,进入步骤 (1).
4 rn->valid已经过期, 如果存在rn->waiting, 表示已经触发了新的dns请求, 只需要把ctx挂在到链表上, 函数返回NGX_AGAIN. 如果不存在rn->waiting,表示这是域名失效之后的第一个请求, 需要清空上一次dns请求申请的内
存, 进入 (6)
5 不存在rn, 表示第一次域名请求, 初始化rn节点, 并加入 r->name_rbtree红黑树.
6 创建域名查询请求 ngx_resolver_create_name_query
7 发送域名查询请求 ngx_resolver_send_query, 并设置dns查询的读事件 uc->connection->read->handler = ngx_resolver_read_response
8 挂载超时事件 ngx_add_timer(ctx->event, ctx->timeout) ctx->event->handler->ngx_resolver_timeout_handler
9 函数结束, 返回NGX_AGAIN.
dns域名查询的发送请求阶段完成, 接下来请求将等待事件触发.
事件触发阶段:
DNS查询可读事件 ( ngx_resolver_read_response )
1 读事件触发, 调用ngx_resolver_read_response
2 最终调用到 ngx_resolver_process_a.
3 如果是A记录, 设置rn的ip地址, 并循环调用 ctx->waiting的handler, 把堆积的请求都处理完, 函数返回.
4 如果是CNAME记录, 将rn->waiting链表中的ctx循环调用ngx_resolve_name_locked, 继续进行dns解析流程.
超时事件 ( ngx_resolver_timeout_handler )
1 如果在ctx->timeout (resolver_timeout) 时间内没有可读事件, 将触发 ngx_resolver_timeout_handler.
2 该函数将 ctx->state = NGX_RESOLVE_TIMEDOUT, 并调用 ctx->handler,handler会判断ctx->state,返回502.
请求结束阶段:
ngx_resolve_name_done(ctx) 每个请求在结束的时候会调用
• 如果关闭了请求的时候还没有域名解析超时, 删除定时器. ngx_del_timer(ctx->event)
• 如果ctx存在于rn->waiting链表中, 从链表中删除ctx.
• 释放ctx分配的内存.
• 调用ngx_resolver_expire, 遍历r->name_expire_queue队列删除r->name_rbtree中的过期rn.
DNS异步重传阶段: (跟请求无关)
重传函数 ngx_resolver_resend_handler
• 遍历r->name_resend_queue链表, 如果存在rn->waiting, 表示还有请求在等待域名解析结果, 需要重新发送域名请求(ngx_resolver_send_query); 否则就从r->name_rbtree中删除rn节点.
• 重新挂载超时定时器, 等待下次重传事件.
重传队列和过期队列:
• r->name_expire_queue 过期队列 每次域名查询之后都会更新时间 rn->expire = ngx_time() + r->expire (30s)
• r->name_resend_queue 重传队列 每次域名重传之后都会更新时间 rn->expire = ngx_time() + r->resend_timeout (5s)
rn必在其中一个队列: 初始化时构造DNS请求, 在resend队列, 在DNS解析成功之后, 放入expire队列. 如果ttl过期了, 将从expire队列删除, 放入resend队列, 构造新的DNS请求....
rn->waiting 在nginx启动或者域名过期, 等待dns查询结果返回的过程中, 请求这个域名的请求(ctx)都将挂载在这个队列上.等查询结果返回之后, 循环调用链表中每个请求的handler.如果在查询结果返回之前请求就因为其他原因终止了, 请求会通过调用ngx_resolve_name_done把自己从链表中删除.