使用动态 DNS 来完成 HTTP 请求

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

对于部分应用场景,可能需要支持的系统众多:Windows、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 相关章节,肯定会有惊喜等着你挖掘碰撞。