关于DNS

DNS(域名服务协议)是一种分布式网络目录服务,主要用于域名与IP地址的相互转换。
在进行通信访问的时候,需要对非IP的域名进行DNS解析,这个过程就是域名到IP地址的转换过程。
DNS解析是一个比较大的消耗,不管是服务器还是本地操作系统,通常都会有自己的DNS Cache负责减少不必要的请求。
有一些程序也会在自己的进程内设计自己的DNS Cache,包括常见流行的浏览器、通信框架等。 workflow也设计了自己的DNS Cache,为了方便用户使用,DNS这部分功能被框架完全接管“隐藏”了起来。

TTL

全称是“生存时间(Time To Live)”,简单的说TTL表示DNS记录在DNS Cache上缓存的时间。

框架的DNS方法

目前直接调用系统函数getaddrinfo获取结果,一些细节:

  1. 当命中框架自己的DNS Cache且TTL有效时,DNS解析不会发生。
  2. 当域名是ipv4、ipv6、unix-domain-socket,DNS解析不会发生。
  3. DNS解析是一个特殊的计算任务,被封装成了一个WFThreadTask。
  4. DNS解析使用的是一个完全独立隔离的线程池,即不占用计算线程池、也不占用通信线程池。

正在考虑在不久的将来增加UDP请求DNS Server的方式获取结果。

全局DNS配置

WFGlobal.h文件里,可以看到我们一个全局配置信息:

  1. struct WFGlobalSettings
  2. {
  3. EndpointParams endpoint_params;
  4. unsigned int dns_ttl_default;
  5. unsigned int dns_ttl_min;
  6. int dns_threads;
  7. int poller_threads;
  8. int handler_threads;
  9. int compute_threads;
  10. };
  11. static constexpr struct WFGlobalSettings GLOBAL_SETTING_DEFAULT =
  12. {
  13. .endpoint_params = ENDPOINT_PARAMS_DEFAULT,
  14. .dns_ttl_default = 12 * 3600, /* in seconds */
  15. .dns_ttl_min = 180, /* reacquire when communication error */
  16. .dns_threads = 4,
  17. .poller_threads = 4,
  18. .handler_threads = 20,
  19. .compute_threads = -1
  20. };
  21. //compute_threads<=0 means auto-set by system cpu number

其中,与DNS相关的配置包括:

  • dns_threads: DNS线程池线程数,默认4。
  • dns_ttl_default: DNS Cache中默认的TTL,单位秒,默认12小时,dns cache是当前进程的,即进程退出就会消失,配置也仅对当前进程有效。
  • dns_ttl_min: dns最短生效时间,单位秒,默认3分钟,用于通信失败重试是否尝试重新dns的决策。

简单来讲,每次通信都会检查TTL来决定要不要重新进行DNS解析。
默认检查dns_ttl_default,通信失败重试时才会去检查dns_ttl_min。

全局的DNS配置,可以通过upstream功能,被单独的地址配置覆盖。
Upstream每一个AddressParams也有dns_ttl_default和dns_ttl_min配置项,使用方式与Global相仿。
具体结构详见upstream文档

高并发场景下TTL过期瞬间的处理

如果TTL过期瞬间,对这个域名正在发生大量的并发请求,可能会面临同一时刻对同一个域名进行大量DNS解析。
框架通过自洽逻辑,合理规避/降低这种可能的发生:

  • 从DNS Cache中get结果时,如果发生TTL过期,会直接将TTL过期时间增加10秒,然后把过期这个结果返回出去,这一系列在一个互斥锁的保护下进行。
  • 如果TTL过期瞬间大量请求涌入,在这段互斥逻辑的保护下,【第一个】发现过期会得到过期的结果并发起DNS解析、而其他请求10秒内都会继续使用旧的结果。
  • 只要10秒内【第一个】成功完成新的DNS解析,就可以更新DNS Cache、保证逻辑的正确性;下一个10秒,会再进行有且只有1次的DNS解析。
  • 每一个10秒,会对“刚刚”过期域名进行有且只有1次的DNS解析。
  • 为了不让这段互斥逻辑影响性能,框架使用了双检锁的模式进行处理加速、有效规避互斥锁的竞争。
  • 再次强调,仅对“刚刚”过期的有效、对于过期时间太久了并没有作用。
  • 如果想更进一步的理解这部分逻辑,请查阅DNSCache的源码。

框架目前仍然有两种场景会面临同一时刻对同一个域名进行大量DNS解析:

  1. 程序刚刚启动,瞬间对同一个域名进行大量请求。
  2. 对一个域名相当长一段时间没有访问(远大于TTL),突然一瞬间对这个域名进行大量请求。

框架认为这两种场景是可以接受的,更确切的说这种场景下的大量DNS请求是完全合理且逻辑严密的。