商务服务
NGINX动态DNS解析原理及源码分析
2024-11-19 03:08

NGINX在配置上游的服务器时,支持域名配置。根据不同的配置,NGINX提供了静态和动态解析两种方式。本文试图从代码层面分析动态dns解析是如何实现的。

NGINX动态DNS解析原理及源码分析

a. 静态解析

 

如上的配置,在NGINX启动运行时,会使用本机在/etc/hosts和/etc/resolve.conf中配置的主机和dns服务器对域名http://private.server1.com.cn和http://private.server2.com.cn进行解析。这个解析过程是通过lib C的函数getaddrinfo进行的同步操作。

如果解析失败,NGINX就不能成功启动。解析得到的ip地址会一直伴随着NGINX运行的整个生命周期。如果在运行期间对应域名的ip地址发生变化,服务就会中断。唯一的解决办法就是重新启动NGINX。

b. 动态解析

开源版的NGINX提供了resolver这种动态的dns解决方案。核心思想是NGINX自身充当dns的客户端进行动态dns解析。

 

如上配置,当访问服务器的根目录时,会把请求转移到test变量定义的服务器中。而且,这个test变量定义的服务器http://private.server1.com.cn会通过resolver 定义的dns 服务器进行动态解析。

在此配置中,通过resolver得到的解析结果有效期是10秒。有效期过后,再次访问根目录时就会对域名进行重新解析。

需要注意的是,如果proxy_pass后面是一个域名而不是一个变量,那么对域名的解析也是发生在启动解析期间,无法完成动态域名解析的功能。  

动态域名解析是通过resolver指令和变量来实现的。指令resolve可以在http范围内全局设定,也可以在某一个server甚至某一个location里面单独设定。

 

在如上配置中,如果访问服务的根目录和/duplicate/目录,需要反向代理的服务器同为 private.server1.com.cn。但是当访问这两个不同的目录时,使用的dns服务器分别是8.8.8.8和114.114.114.114。而且,通过这两个dns服务器解析的结果不能被针对根目录和/duplicate/目录的访问共享。

指令resolver的配置语法是: resolver 114.114.114.114 8.8.8.8 valid=10s ipv6=off;

这个配置中指定了两个dns 服务器114.114.114.114和8.8.8.8,这两个dns服务器会被依次轮流用而不是按照主从的角色去使用。

如果某一个dns服务器不可达,会尝试另外的dns服务,直到有dns服务器能返回解析结果。无论返回的结果是成功还是失败,它都会被采用。

即使是失败也不会再去尝试另外的dns服务器。 另外,如果因为网络原因导致dns服务器暂时不可达,原来的dns过期缓存也没有办法得到重复使用。

参数valid指定了解析结果的有效期。

参数ipv6用来指明是否接收解析结果中的ipv6地址。对于IPv6的配置,默认是开启的,也就是当域名解析到既有

ipv4又有ipv6时,都会解析到。可以通过ipv6=on|off,来控制ipv6解析

与resolver相关的数据结构如下图所示。主要相关的数据结构有

ngx_http_request_t , ngx_http_upstream_t ,ngx_http_upstream_resolved_t, ngx_resolver_ctx_t, ngx_resolver_t, ngx_resolver_connection_t, ngx_connection_t, ngx_http_core_loc_conf_t, ngx_resolver_node_t.

 

从这个数据结构关系图中,我们可以看到一个http请求需要进行动态的dns解析时,主要的数据结构是如何连接起来的。

有了数据结构的大体概念以后,我们下面试着从代码层面分析整个resolver的工作流程和工作原理。

  1. 配置层面

与动态dns解析功能相关的指令有proxy_pass和resolver两个指令。

a. 在配置阶段,与resolver指令对应的解析函数是ngx_http_core_resolver。函数会生成一个ngx_resolver_t结构并且和location对应的ngx_http_core_loc_conf_t结构连接起来。如上图中的A点所示。

b. 指令proxy_pass对应的解析函数是 ngx_http_proxy_pass 。如果proxy_pass后面的参数是变量,解析函数会把变量存放到ngx_http_proxy_loc_conf_t结构中的proxy_values数组中。在此阶段不会试图对变量进行解析。

如果proxy-pass后面的参数不是变量,则会在配置解析阶段解析后面upstream主机的ip地址并且生成upstream结构并且和ngx_http_proxy_loc_conf_t中的upstream结构连接起来。

与此同时,设置http_proxy模块的处理函数为ngx_http_proxy_handler 。如上图中的B点所示。此函数会在http处理各个模块的回调函数时被调用。

2. 数据层面

动态dns的解析发生在NGINX接收完客户端的请求,然后和上游的upstream服务器进行连接时。

下面我们分析从NGINX打开服务端口接收客户请求到dns域名得到解析并且完成连接这一完整过程。

2.1.

a. 当有客户端发送tcp连接请求时,ngx_epoll_process_events返回listenfd可读事件,调用ngx_event_accept函数接收客户端请求。再调用对应的listening socket的handler函数ngx_http_init_connection函数进入http处理。函数ngx_http_init_connection是在ngx_http_optimize_servers函数中和listenging socket进行连接的。

b. 在函数ngx_http_init_connection中,生成ngx_http_connection_t 结构hc。然后查找对应的服务器地址并且赋值到hc的addr_conf属性中。最后把connection对应的读写的回调函数分别设置为ngx_http_wait_request_handler 和ngx_http_empty_handler 。这样再有数据读入事件发生时,函数ngx_http_wait_request_handler就会得到调用。

c. 函数ngx_http_wait_request_handler会通过ngx_http_create_request创建http request(r)。同时设置读事件回调函数为ngx_http_process_request_line 。当再有数据读入事件发生时,函数ngx_http_process_request_line就会得到调用。与此同时,还会同时调用函数ngx_http_process_request_line来处理已经接受到的请求。

d. 函数ngx_http_process_request_line先是调用ngx_http_read_request_header将请求行读取到缓存中,然后调用ngx_http_parse_request_line解析出请求行信息,最后把读事件的回调函数设置为ngx_http_process_request_headers并且调用ngx_http_process_request_headers处理请求头。

e. 在函数ngx_http_process_request_headers 内部先是调用函数ngx_http_read_request_header 读取请求头,然后调用ngx_http_parse_header_line 函数解析出请求头,接着调用ngx_http_process_request_header 函数对请求头进行必要的验证,最后调用ngx_http_process_request 函数处理请求。

f. 函数ngx_http_process_request中,把event的读写回调函数全部设置为ngx_http_request_handler,把http request(r)的read_event_handler设置为ngx_http_block_reading。同时调用ngx_http_handler函数。而在ngx_http_handler(ngx_http_request_t r) 函数内部调用ngx_http_core_run_phases进行HTTP多阶段处理。函数ngx_http_handler同时会把http request(r)的write_event_handler设置为ngx_http_core_run_phases。

g. 在ngx_http_core_run_phases循环中,迭代所有http模块handler,然后在handler函数中根据请求结构体ngx_http_request_t做出相应的处理。与动态dns解析相关的http proxy模块的回调函数ngx_http_proxy_handler也会在此期间得到调用。

2.2.

如上所述就是一个普通的NGINX http请求的处理流程。到现在为止http的处理逻辑已经到达了各个模块自身的处理中。对应动态的dns对应的处理函数是ngx_http_proxy_handler。下面我们就从这个处理函数开始,分析动态dns解析是如何实现的。

a. 在ngx_http_proxy_handler函数中,首先通过ngx_http_upstream_create为http request(r)生成一个ngx_http_upstream_t结构用来存放所有的upstream服务器信息。然后得到请求对应的ngx_http_proxy_loc_conf_t数据。在这个结构中存放着我们配置的proxy_pass后面变量信息。再通过ngx_http_proxy_eval和ngx_http_script_run等函数获取对应变量的具体数值。再把这些信息存放到http request(r)的upstream中的resolved的host变量中。

b. 函数ngx_http_proxy_handler会继续初始化http request(r)的众多回调函数比如create_request reinit_request process_headerabort_request finalize_request。 同时调用ngx_http_read_client_request_body处理请求的数据体。在调用ngx_http_read_client_request_body时会把函数ngx_http_upstream_init当做参数传入。

c. 在函数ngx_http_read_client_request_body中,处理完请求body以后会调用ngx_http_upstream_init函数对upstream服务器进行初始化。同时把http requst(r)read_event_handler 和write_event_handler进行重新赋值。

d. 函数ngx_http_upstream_init会调用ngx_http_upstream_init_request。

函数ngx_http_upstream_init_request首先检查http request(r)的upstream中的resolved的host是否已经被解析成ip地址。对应我们分析的这种情况,现在的host还是一个域名而不是ip地址。这时,函数ngx_http_upstream_init_request就会调用ngx_resolve_start开始对host进行域名解析。至此真正的动态dns解析逻辑正式被触发。

e. 函数ngx_resolve_start会生成一个ngx_resolver_ctx_t的数据结构ctx 。同时把ctx的resolver设置为对应location结构ngx_http_core_loc_conf_t中的的resolver成员,此成员是在配置解析时生成的。这个ctx的handler会同时被设置成ngx_http_upstream_resolve_handler。最后,这个ctx结构会被关联到http request(r)的upstream中的resolved的ctx变量中。

f. 函数ngx_http_upstream_init_request通过ngx_resolve_start创建完ngx_resolver_ctx_t结构后,会通过ngx_resolve_name调用ngx_resolve_name_locked进行实质的域名解析。

g. 函数ngx_resolve_name_locked逻辑流程如下

如果该域名在resolver中已存在节点

i. 如果该节点仍有效,则更新node超时时间,将resolver中的DNS解析结果赋值给ctx,调用ctx的回调ngx_http_upstream_resolve_handler。函数ngx_http_upstream_resolve_handler的逻辑我们下面在解释。

ii. 如果该节点已失效。若因DNS响应还未返回(rn->waiting,则将该cxt挂至rn->waiting;若因响应后失效,则重新发起DNS请求。

如果该域名在resolver中不存在节点

i. 分配并初始化rn节点,加入resolver红黑树。

ii. 建立DNS请求字符串(rn->query).

iii. 发送DNS请求(ngx_resolver_send_query/ngx_resolver_send_tcp_query/ngx_resolver_send_udp_query)。

iv. 使能ctx->event超时定时器,用于ctx超时。

v. 将rn加入resolver的resend_queue队列,用于DNS的超时重传。如果这是resend_queue中的首个元素,则需要使能r->event重传定时器。该定时器超时时,会遍历resolver的resend_queue,对所有需要重传的node进行判断。

h. 函数ngx_resolver_send_query根据协议配置选用ngx_resolver_send_tcp_query或者ngx_resolver_send_udp_query发送dns请求。我们以ngx_resolver_send_udp_query为例。函数ngx_resolver_send_udp_query会通过ngx_udp_connect创建一个socket并且连接到dns server的服务端口。同时把对应的socket的读事件的回调函数设置为ngx_resolver_udp_read。

i. 当dns响应包到达时,函数ngx_resolver_udp_read通过ngx_resolver_process_response来处理响应数据包。函数ngx_resolver_process_response调用ngx_resolver_process_a来处理域名对应的v4和v6地址。

j. 函数ngx_resolver_process_a会首先根据域名查找rn节点,然后把解析响应的结果存放到rn中。同时copy一份结果赋值给ngx_resolver_ctx_t ctx。此时dns解析成功然后遍历rn->waiting并且调用ctx->handler也就是ngx_http_upstream_resolve_handler函数。同时把rn从resend_queue队列中删除加入name_expire_queue节点超时队列。

k. 函数ngx_http_upstream_resolve_handler首先调用ngx_http_upstream_create_round_robin_peer对http request(r)的upstream服务器进行初始化。然后调用ngx_resolve_name_done执行一些清理工作。最后调用ngx_http_upstream_connect用来和上游服务器进行连接。

至此,整个dns解析过程完成而且解析结果也被成功用来进行上游服务器的连接。


开源版本的NGINX对动态dns解析提供了一定的支持。通过进行源码分析我们会发现这一机制还是有一些局限性。比如,只能通过proxy_pass加变量的方式实现。

很多upstream模块的负载均衡等属性都没法被使用。而且,各个worker process需要独立进行dns解析,而且结果不能共享。

相比于开源版本的原生态NGINX,我们可以采用NGINX Plus, 或者很多第三方模块实现更多更实用的动态dns解析功能。


想要更及时全面地获取NGINX相关的技术干货、互动问答、系列课程、活动资源

请前往NGINX开源社区

  • 官网:nginx.org.cn
  • 微信公众号:https://mp.weixin.qq.com/s/XVE5yvDbmJtpV2alsIFwJg
  • 微信群:https://www.nginx.org.cn/static/pc/images/homePage/QR-code.png?v=1621313354
  • B站:https://space.bilibili.com/6283

    以上就是本篇文章【NGINX动态DNS解析原理及源码分析】的全部内容了,欢迎阅览 ! 文章地址:http://keair.bhha.com.cn/news/1103.html 
     文章      相关文章      动态      同类文章      热门文章      栏目首页      网站地图      返回首页 康宝晨移动站 http://keair.bhha.com.cn/mobile/ , 查看更多   
最新文章
5款文案自动生成器,高质量创意文案一键为你生成
  在当今竞争激烈的内容创作领域,每一个字、每一句话都承载着巨大的价值。对于创作者而言,文案自动生成器的出现
SEO搜索引擎优化怎么做?全面指南让你快速上手!
在当今的数字化时代,SEO(搜索引擎优化)已经成为企业和个人提升在线存在感和吸引目标用户的关键手段。无论你是初学者还是有经
Apple 苹果 iPhone 13系列 A2634 5G手机 256GB 绿色
iPhone 13 边缘采用平坦设计、配备独家超瓷晶面板,支持 IP68 抗水性能,拥有绿色、粉色、蓝色、午夜色、星光色和红色可选择。屏
【A6搜狗手机输入法下载】海尔A6搜狗手机输入法12.1.1免费下载
搜狗输入法,拥有超大中文词库,输入更加精准,智能。搜狗智能旺仔带你用表达,斗图,妙语,输入更加有趣。******特色功能******
制氧机十大品牌排行榜:吸氧机技术排名TOP 3
制氧机是一种重要的医疗设备,用于治疗呼吸系统疾病。随着科技的不断发展,市场上出现了众多品牌的制氧机,伴随着人们对健康的重
【瑕疵检测】基于matlab GUI OTSU织物疵点检测【含Matlab源码 860期】
⛳️座右铭:行百里者,半于九十。 更多Matlab图像处理仿真内容点击👇 ①Matlab图像处理(进阶版ÿ
微信公众号文章信息(阅读量、在看、点赞数)获取
  实现这一个功能主要用到了selenium、mitmproxy和wechatarticles,利用selenium可以实现脚本模拟浏览器访问,mit
上海九院口腔科怎么样?医生排名名单、技术特长介绍、真人种牙术后评价~
此价格信息为当地市场参考价,通过用户反馈大数据整理仅供参考!机构实际价格以到院面诊为准!
'智能AI文章生成器:海外脚本与一键创作工具'
在数字化时代的浪潮中人工智能技术正以前所未有的速度改变着咱们的工作和生活途径。智能文章生成器的出现无疑为内容创作者们打开
微信推文重复率
论文免费查重庞大的数据库支持,论文免费查重拥有全球最大的比对库资源。其比对库拥有超过1亿篇的学术期刊和学位论文、1000多家的
相关文章