其實(shí)針對(duì)大多應(yīng)用場(chǎng)景,DNS 是不會(huì)頻繁變更的,使用 Nginx 默認(rèn)的 resolver 配置方式就能解決。
對(duì)于部分應(yīng)用場(chǎng)景,可能需要支持的系統(tǒng)眾多:win、centos、ubuntu 等,不同的操作系統(tǒng)獲取 DNS 的方法都不太一樣。再加上我們使用 Docker,導(dǎo)致我們?cè)谌萜鲀?nèi)部獲取 DNS 變得更加難以準(zhǔn)確。
如何能夠讓 Nginx 使用隨時(shí)可以變化的 DNS 源,成為我們急待解決的問(wèn)題。
當(dāng)我們需要在某一個(gè)請(qǐng)求內(nèi)部發(fā)起這樣一個(gè) http 查詢,采用 proxy_pass 是不行的(依賴 resolver 的 DNS,如果 DNS 有變化,必須要重新加載配置),并且由于 proxy_pass 不能直接設(shè)置 keepalive,導(dǎo)致每次請(qǐng)求都是短鏈接,性能損失嚴(yán)重。
使用 resty.http,目前這個(gè)庫(kù)只支持 ip :port 的方式定義 url,其內(nèi)部實(shí)現(xiàn)并沒(méi)有支持 domain 解析。resty.http 是支持 set_keepalive 完成長(zhǎng)連接,這樣我們只需要讓他支持 DNS 解析就能有完美解決方案了。
local resolver = require "resty.dns.resolver"
local http = require "resty.http"
function get_domain_ip_by_dns( domain )
-- 這里寫死了google的域名服務(wù)ip,要根據(jù)實(shí)際情況做調(diào)整(例如放到指定配置或數(shù)據(jù)庫(kù)中)
local dns = "8.8.8.8"
local r, err = resolver:new{
nameservers = {dns, {dns, 53} },
retrans = 5, -- 5 retransmissions on receive timeout
timeout = 2000, -- 2 sec
}
if not r then
return nil, "failed to instantiate the resolver: " .. err
end
local answers, err = r:query(domain)
if not answers then
return nil, "failed to query the DNS server: " .. err
end
if answers.errcode then
return nil, "server returned error code: " .. answers.errcode .. ": " .. answers.errstr
end
for i, ans in ipairs(answers) do
if ans.address then
return ans.address
end
end
return nil, "not founded"
end
function http_request_with_dns( url, param )
-- get domain
local domain = ngx.re.match(url, [[//([\S]+?)/]])
domain = (domain and 1 == #domain and domain[1]) or nil
if not domain then
ngx.log(ngx.ERR, "get the domain fail from url:", url)
return {status=ngx.HTTP_BAD_REQUEST}
end
-- add param
if not param.headers then
param.headers = {}
end
param.headers.Host = domain
-- get domain's ip
local domain_ip, err = get_domain_ip_by_dns(domain)
if not domain_ip then
ngx.log(ngx.ERR, "get the domain[", domain ,"] ip by dns failed:", err)
return {status=ngx.HTTP_SERVICE_UNAVAILABLE}
end
-- http request
local httpc = http.new()
local temp_url = ngx.re.gsub(url, "http://"..domain.."/", string.format("http://%s/", domain_ip))
local res, err = httpc:request_uri(temp_url, param)
if err then
return {status=ngx.HTTP_SERVICE_UNAVAILABLE}
end
-- httpc:request_uri 內(nèi)部已經(jīng)調(diào)用了keepalive,默認(rèn)支持長(zhǎng)連接
-- httpc:set_keepalive(1000, 100)
return res
end
動(dòng)態(tài) DNS,域名訪問(wèn),長(zhǎng)連接,這些都具備了,貌似可以安穩(wěn)一下。在壓力測(cè)試中發(fā)現(xiàn)這里面有個(gè)機(jī)制不太好,就是對(duì)于指定域名解析,每次都要和 DNS 服務(wù)會(huì)話詢問(wèn) IP 地址,實(shí)際上這是不需要的。普通的瀏覽器,都會(huì)對(duì) DNS 的結(jié)果進(jìn)行一定的緩存,那么這里也必須要使用了。
對(duì)于緩存實(shí)現(xiàn)代碼,請(qǐng)參考 ngx_lua 相關(guān)章節(jié),肯定會(huì)有驚喜等著你挖掘碰撞。