我眼中的 Nginx(六):深入 Nginx/Openresty 服務裡的 DNS 解析

又拍雲發表於2019-04-29

張超:又拍雲系統開發高階工程師,負責又拍雲 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 地址等資訊。

下面這個簡單的反向代理配置,就會在進行代理前解析 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 解析方式都有它適用的場景,也有它自己的不足,沒有最好的,只有最合適的。在程式設計的時候,需要找到最合適自己業務場景的方式,才能最大程度地保障服務的可用性和可靠性,避免帶來一些災難。

推薦閱讀:

我眼中的 Nginx(五):Nginx - 子請求設計之道​ 我眼中的 Nginx(四):是什麼讓你的 Nginx 服務退出這麼慢?​ 我眼中的 Nginx(三):Nginx 變數和變數插值 我眼中的 Nginx(二):HTTP/2 dynamic table size update​ 我眼中的 Nginx(一):Nginx 和位運算​

相關文章