张超: 又拍云系统开发高级工程师, 负责又拍云 CDN 平台相关组件的更新及维护. GitHub ID: tokers, 活跃于 OpenResty 社区和 Nginx 邮件列表等开源社区, 专注于服务端技术的研究; 曾为 ngx_lua 贡献源码, 在 Nginx,ngx_lua,CDN 性能优化, 日志优化方面有较为深入的研究.
DNS 解析在 Nginx/OpenResty 的服务里是不可分割的一个功能, 本文主要来介绍下 Nginx 和 OpenResty 服务里的一些不同的 DNS 解析方式以及它们之间的优缺点.
配置解析阶段
很多时候我们会在 Nginx 配置文件里配置上一些域名, 比如配置我们的上游服务器.
- upstream example.com {
- server foo.example.com;
- }
对于这类域名, Nginx 会在配置解析阶段就将其解析出来, 接下来 (请求处理过程) 使用的都是当时解析得到的 IP.Nginx 核心有一个函数 ngx_parse_url, 负责对 url 格式进行分析, 包括解析出主机名, 端口号以及 URL path 等. 针对 IPv4 的情况, 它会调用 ngx_parse_inet_url 进行具体的解析任务, 如果必要, 最终它会调用到 ngx_inet_resolve_host 进行域名解析, ngx_inet_resolve_host 大多情况下会使用 getaddrinfo 进行解析, 最终向 /etc/resolv.conf 下所配置的 DNS server 发起解析请求.
归纳来说这个解析过程有两个特点, 一是使用了系统配置的 DNS server; 二是解析过程是同步且阻塞的, 因此这种解析方式仅在 Nginx 配置解析阶段会被使用. 另外这种解析方式的缺点就是只解析一次, 所以如果在 Nginx 运行过程中域名解析发生了改变也是无法感知到的, 除非手动重启 Nginx 服务.
运行时 DNS resolver
Nginx 核心提供了一套供运行时使用的 DNS 解析机制, 它充分契合 Nginx 的事件模型, 同样是异步非阻塞的, 并且提供了缓存机制. http,stream 和 mail 模块分别提供了配置指令(比如 http 模块提供的 resolver), 供我们配置相关 DNS server 地址等信息.
下面这个简单的反向代理配置, 就会在进行代理前解析 http://www.upyun.com 这个域名.
- location / {
- set $myupstream www.upyun.com;
- proxy_http_version 1.1;
- proxy_set_header Connection "";
- proxy_pass http://${
- myupstream
- }/index.html;
- }
注意如果直接在 proxy_pass 指令里写明需要代理的域名(即不使用变量的方式), 那么域名解析就会发生在配置解析阶段了, 即上面所讲的过程. 这其实也是一种实现动态 upstream 的方式.
这套运行时 DNS resolver 其实是一个 DNS client 的角色, 由它自己组织查询报文并发送给目标 DNS 服务器, 同时支持解析 IPv6 地址(从 1.5.8 开始), 支持反向地址解析和 SRV 解析. 它把对每个域名的解析抽象为一棵红黑树的节点, 包括任何必要的信息. 同时这棵红黑树也充当着缓存, 查询时会以域名作为 key, 如果对应缓存是新鲜的, 即会复用缓存, 并且会对解析得到的地址顺序进行一定的回转后再提供给上层使用. 如果没有缓存或者缓存过期, 新的 DNS 请求会被构建并且发送.
当然, 很多时候这套运行时的 DNS resolver 也不能完全满足需求:
无法配置主备 DNS 服务器地址, 我们在 resolver 指令里配置的地址都会按顺序被轮询到.
无法在 DNS 服务器故障或者网络质量不佳的情况下复用陈旧的缓存, 这可能导致上层服务不可用.
每个 Nginx worker 进程独享解析缓存.
Cosocket
Cosocket 是 lua-nginx-module 提供的最强有力的接口(个人来看没有之一). 它的 connect 方法同样支持传入域名, 之后会调用上面介绍的 Nginx 运行时 resolver 来解析对应域名, 然后随机挑选一个 IP 作为本次连接的目标 IP 地址. 由于是使用的 Nginx 运行时 resolver , 如果 DNS resolver 无法正常进行解析, 则利用 Cosocket 构建的服务也都会受到影响.
lua-resty-dns
OpenResty 官方开源的 lua-resty-dns 是利用 Cosocket 实现的一套百分百非阻塞的 DNS resolver, 它仅仅充当了一个 DNS 解析器, 没有任何其他的附加功能, 包括缓存.
前面介绍过 Nginx 运行时 DNS resolver 在很多时候是有诸多的不便的, 而 lua-resty-dns 则给我们留了很多的扩展空间:
提供主备 DNS 服务器地址 -- 结合像 lua-resty-checkups 这样的工具可以很容易的维护主备 DNS 服务器地址, 包括进行心跳检测.
进程间共享缓存 -- 结合使用 lua-nginx-module 提供的共享内存字典, 可以非常容易地管理缓存.
故障时使用陈旧缓存 -- 在 lua-resty-dns 报告异常时再次使用缓存即可(只要缓存拷贝没有被强行淘汰).
目前 OpenResty 生态圈已经有一些基于 lua-resty-dns 实现的加强版实现, 比如 Kong 的 lua-resty-dns-client.
总的来说, 每一种 DNS 解析方式都有它适用的场景, 也有它自己的不足, 没有最好的, 只有最合适的. 在程序设计的时候, 需要找到最合适自己业务场景的方式, 才能最大程度地保障服务的可用性和可靠性, 避免带来一些灾难.
来源: https://www.cnblogs.com/upyun/p/10789495.html