深入 Nginx/Openresty 服務里的 DNS 解析

深入 Nginx/Openresty 服務里的 DNS 解析

來自專欄我眼裡的 nginx5 人贊了文章

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 地址等信息。

下面這個簡單的反向代理配置,就會在進行代理前解析 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 也不能完全滿足需求:

  1. 無法配置主備 DNS 伺服器地址,我們在 resolver 指令里配置的地址都會按順序被輪詢到。
  2. 無法在 DNS 伺服器故障或者網路質量不佳的情況下復用陳舊的緩存,這可能導致上層服務不可用。
  3. 每個 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 則給我們留了很多的擴展空間:

  1. 提供主備 DNS 伺服器地址 —— 結合像 lua-resty-checkups 這樣的工具可以很容易的維護主備 DNS 伺服器地址,包括進行心跳檢測。
  2. 進程間共享緩存 —— 結合使用 lua-nginx-module 提供的共享內存字典,可以非常容易地管理緩存。
  3. 故障時使用陳舊緩存 —— 在 lua-resty-dns 報告異常時再次使用緩存即可(只要緩存拷貝沒有被強行淘汰)。

目前 OpenResty 生態圈已經有一些基於lua-resty-dns 實現的加強版實現,比如 Kong 的 lua-resty-dns-client。

總的來說,每一種 DNS 解析方式都有它適用的場景,也有它自己的不足,沒有最好的,只有最合適的。在程序設計的時候,需要找到最合適自己業務場景的方式,才能最大程度地保障服務的可用性和可靠性,避免帶來一些災難。

推薦閱讀:

TAG:Nginx | OpenResty | DNS |