openresty域名動態解析


 工作中使用openresty,使用第三方服務API通過域名訪問。但是,域名通過DNS解析出來之后,在openresty是有

配置解析階段

很多時候我們會在 Nginx 配置文件里配置上一些域名,比如配置我們的上游服務器。

upstream example.com { server test.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.baidu.com 這個域名。

location / { set $myupstream www.baidu.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 進程獨享解析緩存.

運行時 balancer_by_lua_file 

  使用 OpenResty 做反向代理的傳統模式是在配置文件的 upstream{ } 塊里書寫多個服務器定義集群。這種方式不夠靈活,增加服務器必須手動修改配置后重啟 OpenResty,會影響正常服務。

  OpenResty 的 “balancer_by_lua” 指令讓動態負載均衡稱為可能,它替代了原生的 hash/ip_hash/least_conn 等算法,不僅可以讓自由定制負載均衡策略,還可以隨意調整后端服務器的數量,完全超越了 upstream 系列指令,實現了接近商業版 Nginx Plus 的功能。

   使用方式   

upstream dyn_backend {                          # 動態上游集群
    server 0.0.0.0;                         # 占位用,無實際意義
    balancer_by_lua_file service/proxy/balancer.lua;         # 執行負載均衡的 Lua 代碼
    keepalive 10;                          # 需在 balancer 指令之后
}

   “balancer_by_lua” 也是一個比較特殊的執行階段,在這里不能使用 ngx.sleep、ngx.req.* 或 coocket,同時應當盡量避免大計算量操作或磁盤讀寫,否則會導致阻塞。

    動態負載均衡使用的服務器列表通常存儲在外部的 Redis 或 MySQL 里,由於不能直接使用 cosocker,所以在 “balancer_by_lua” 里也就不能操作這些服務器。但這並不是什么大問題,完全可以在其他的階段(例如 access_by_lua、ngx.timer)里訪問服務器獲取列表、解析域名,然后放在 ngx.ctx 或全局模塊里傳遞過來。

  支持版本

    This directive was first introduced in the v0.10.0 release.

 功能接口

 在 “balancer_by_lua” 里除了基本的 ngx.* 功能接口外,主要使用的是庫 ngx.balancer,它必須顯式加載后才能使用,即:

local balancer - require "ngx.balancer"                             -- 顯式加載 ngx.balancer 庫

ngx.balancer 提供四個函數:

  • set_current_peer:設置使用的后端服務器,必須是 IP 地址,不能是域名;
  • set_timeouts:設置后端的連接和讀寫超時時間,單位是秒;
  • set_more_tries:設置連接失敗后的重試次數;
  • get_last_failure:獲取上一次連接失敗的具體原因。

 這幾個函數的的用法都很簡單,動態負載均衡的重點其實是服務器列表的維護和選擇算法,這些工作通常應該在 “balancer_by_lua” 之外完成,ngx.balancer 只是最后的執行者。

 下面的代碼是 ngx.balancer 的典型用法,使用了固定的服務器列表和隨機數來選擇后端,實際應用應該替換為動態更新的數據和更有意義的算法:

local servers = {                                                   -- 簡單的服務器列表,IP 地址
    {"127.0.0.1", 80},                                              -- 實際上應該從 Redis
    {"127.0.0.1", 81},                                              -- 等服務器里動態加載
}

balancer.set_timeouts(1, 0.5, 0.5)                                  -- 后端的連接和讀寫超時時間
balancer.set_more_tries(2)                                          -- 連接失敗后最多在重試 2 次
 
local n = math.random(#servers)                                     -- 這里使用隨機算法作為示例
 
local ok, err = balancer.set_current_peer(                          -- 設置使用的后端服務器
                servers[n][1], servers[n][2])                       -- 使用 IP 地址和端口號
 
if not ok then                                                      -- 檢查是否設置成功
    ngx.log(ngx.ERR, "failed to set peer: ", err)
    return ngx.exit(500)
end

 

 另一種實現方式是把負載均衡算法的主要計算工作放在 “access_by_lua” 等階段里完成,這樣更加靈活,計算出的后端服務器地址等數據放在 ngx.var 或 ngx.ctx 里傳遞,“balancer_by_lua” 階段只需要少量的代碼:

local server = ngx.ctx.server                                       -- 之前計算得到的后端服務器
if balancer.get_last_failure() then                                 -- 后端出錯,需要重新選擇
    server = ...                                                    -- 重新計算后端服務器
end
 
 
local ok, err = balancer.set_current_peer(server[1], server[2])     -- 通常無需在計算,直接設置,IP 地址和端口號 

 

 動態域名使用

upstream dynamicBackend {
    server 0.0.0.1; # just an invalid address as a place holder
    balancer_by_lua_file 'conf/dynamic_domain/balancer_handle.lua';
    keepalive 100; # connection pool
}

location / {
    proxy_connect_timeout 5s;
    proxy_send_timeout 10s;
    proxy_read_timeout 30s;

    #默認值為0:重試次數不受限制
    proxy_next_upstream_tries 2;

    #默認情況下只有GET請求會重試(基於這樣的考慮:只有GET請求才是冪等)
    proxy_next_upstream error timeout non_idempotent;

    access_by_lua_file 'conf/access_handle.lua';

    log_by_lua_file 'conf/log_handle.lua';

    proxy_pass $proxy_scheme://dynamicBackend;

}

 

  

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM