• 使用动态 DNS 来完成 HTTP 请求

    使用动态 DNS 来完成 HTTP 请求

    其实针对大多应用场景,DNS 是不会频繁变更的,使用 Nginx 默认的 resolver 配置方式就能解决。

    对于部分应用场景,可能需要支持的系统众多:win、centos、ubuntu 等,不同的操作系统获取 DNS 的方法都不太一样。再加上我们使用 Docker,导致我们在容器内部获取 DNS 变得更加难以准确。

    如何能够让 Nginx 使用随时可以变化的 DNS 源,成为我们急待解决的问题。

    当我们需要在某一个请求内部发起这样一个 http 查询,采用 proxy_pass 是不行的(依赖 resolver 的 DNS,如果 DNS 有变化,必须要重新加载配置),并且由于 proxy_pass 不能直接设置 keepalive,导致每次请求都是短链接,性能损失严重。

    使用 resty.http,目前这个库只支持 ip :port 的方式定义 url,其内部实现并没有支持 domain 解析。resty.http 是支持 set_keepalive 完成长连接,这样我们只需要让他支持 DNS 解析就能有完美解决方案了。

    1. local resolver = require "resty.dns.resolver"
    2. local http = require "resty.http"
    3. function get_domain_ip_by_dns( domain )
    4. -- 这里写死了google的域名服务ip,要根据实际情况做调整(例如放到指定配置或数据库中)
    5. local dns = "8.8.8.8"
    6. local r, err = resolver:new{
    7. nameservers = {dns, {dns, 53} },
    8. retrans = 5, -- 5 retransmissions on receive timeout
    9. timeout = 2000, -- 2 sec
    10. }
    11. if not r then
    12. return nil, "failed to instantiate the resolver: " .. err
    13. end
    14. local answers, err = r:query(domain)
    15. if not answers then
    16. return nil, "failed to query the DNS server: " .. err
    17. end
    18. if answers.errcode then
    19. return nil, "server returned error code: " .. answers.errcode .. ": " .. answers.errstr
    20. end
    21. for i, ans in ipairs(answers) do
    22. if ans.address then
    23. return ans.address
    24. end
    25. end
    26. return nil, "not founded"
    27. end
    28. function http_request_with_dns( url, param )
    29. -- get domain
    30. local domain = ngx.re.match(url, [[//([\S]+?)/]])
    31. domain = (domain and 1 == #domain and domain[1]) or nil
    32. if not domain then
    33. ngx.log(ngx.ERR, "get the domain fail from url:", url)
    34. return {status=ngx.HTTP_BAD_REQUEST}
    35. end
    36. -- add param
    37. if not param.headers then
    38. param.headers = {}
    39. end
    40. param.headers.Host = domain
    41. -- get domain's ip
    42. local domain_ip, err = get_domain_ip_by_dns(domain)
    43. if not domain_ip then
    44. ngx.log(ngx.ERR, "get the domain[", domain ,"] ip by dns failed:", err)
    45. return {status=ngx.HTTP_SERVICE_UNAVAILABLE}
    46. end
    47. -- http request
    48. local httpc = http.new()
    49. local temp_url = ngx.re.gsub(url, "//"..domain.."/", string.format("//%s/", domain_ip))
    50. local res, err = httpc:request_uri(temp_url, param)
    51. if err then
    52. return {status=ngx.HTTP_SERVICE_UNAVAILABLE}
    53. end
    54. -- httpc:request_uri 内部已经调用了keepalive,默认支持长连接
    55. -- httpc:set_keepalive(1000, 100)
    56. return res
    57. end

    动态 DNS,域名访问,长连接,这些都具备了,貌似可以安稳一下。在压力测试中发现这里面有个机制不太好,就是对于指定域名解析,每次都要和 DNS 服务会话询问 IP 地址,实际上这是不需要的。普通的浏览器,都会对 DNS 的结果进行一定的缓存,那么这里也必须要使用了。

    对于缓存实现代码,请参考 ngx_lua 相关章节,肯定会有惊喜等着你挖掘碰撞。