docs for dns (#1459)

This commit is contained in:
kedixa
2023-12-26 19:27:12 +08:00
committed by GitHub
parent ba98ed0945
commit abb3df6acd

View File

@@ -1,100 +1,70 @@
# 关于DNS
当使用域名请求网络时首先需要通过域名解析获取服务器地址再使用网络地址进行后续的请求。Workflow已经实现了完备的域名解析和缓存系统通常来说用户无需知晓内部机制即可流畅地发起网络任务。
DNS(域名服务协议)是一种分布式网络目录服务主要用于域名与IP地址的相互转换。
在进行通信访问的时候需要对非IP的域名进行DNS解析这个过程就是域名到IP地址的转换过程。
DNS解析是一个比较大的消耗不管是服务器还是本地操作系统通常都会有自己的DNS Cache负责减少不必要的请求。
有一些程序也会在自己的进程内设计自己的DNS Cache包括常见流行的浏览器、通信框架等。
workflow也设计了自己的DNS Cache为了方便用户使用DNS这部分功能被框架完全接管“隐藏”了起来。
## DNS相关配置
Workflow中的全局配置包括
### TTL
全称是“生存时间Time To Live)”简单的说TTL表示DNS记录在DNS Cache上缓存的时间。
### 框架的DNS方法
#### 同步请求
通过调用系统函数getaddrinfo获取结果一些细节
1. 当命中框架自己的DNS Cache且TTL有效时DNS解析不会发生。
2. 当域名是ipv4、ipv6、unix-domain-socketDNS解析不会发生。
3. DNS解析是一个特殊的计算任务,被封装成了一个WFThreadTask。
4. DNS解析使用的是一个完全独立隔离的线程池即不占用计算线程池、也不占用通信线程池。
#### 异步请求
框架实现了完备的DNS协议解析在Unix系统下默认将默认使用框架内置的DNS解析器不会创建DNS线程池。
如需想恢复为多线程dns解析需要在全局配置里将resolv_conf_path参数设置为NULL。
### 全局DNS配置
在[WFGlobal.h](../src/manager/WFGlobal.h)文件里,可以看到我们一个全局配置信息:
~~~cpp
struct WFGlobalSettings
{
EndpointParams endpoint_params;
struct EndpointParams endpoint_params;
struct EndpointParams dns_server_params;
unsigned int dns_ttl_default;
unsigned int dns_ttl_min;
int dns_threads;
int poller_threads;
int handler_threads;
int compute_threads;
int fio_max_events;
const char *resolv_conf_path;
const char *hosts_path;
};
static constexpr struct WFGlobalSettings GLOBAL_SETTING_DEFAULT =
{
.endpoint_params = ENDPOINT_PARAMS_DEFAULT,
.dns_server_params = ENDPOINT_PARAMS_DEFAULT,
.dns_ttl_default = 12 * 3600, /* in seconds */
.dns_ttl_min = 180, /* reacquire when communication error */
.dns_threads = 4,
.poller_threads = 4,
.handler_threads = 20,
.compute_threads = -1,
.resolv_conf_path = "/etc/resolv.conf",
.hosts_path = "/etc/hosts",
};
~~~
其中与DNS相关的配置包括
* dns_server_params对dns服务器的最大并发数超时等配置。
* dns_threads: DNS线程池线程数默认4。只有当resolv_conf_path配置为空时这个参数才会起作用。否则我们并不会创建dns线程。
* dns_ttl_default: DNS Cache中默认的TTL单位秒默认12小时dns cache是当前进程的即进程退出就会消失配置也仅对当前进程有效。
* dns_ttl_min: dns最短生效时间单位秒默认3分钟用于通信失败重试是否尝试重新dns的决策。
* resolv_conf_path: resolv.conf配置文件路径为NULL表示使用多线程DNS解析。
* hosts_path: hosts配置文件路径。可以为NULL。
简单来讲每次通信都会检查TTL来决定要不要重新进行DNS解析。
默认检查dns_ttl_default通信失败重试时才会去检查dns_ttl_min。
其中与域名解析相关的配置项有
全局的DNS配置可以通过upstream功能被单独的地址配置覆盖。
Upstream每一个AddressParams也有dns_ttl_default和dns_ttl_min配置项使用方式与Global相仿。
具体结构详见[upstream文档](./about-upstream.md#Address属性)。
* dns_server_params
* address_family: 该项会在后续展开说明
* max_connections: 向DNS服务器发送请求的最大并发数默认为200
* connect_timeout/response_timeout/ssl_connect_timeout: 参考[超时](about-timeout.md)相关说明
* dns_threads: 当使用同步方式实现域名解析时解析操作会在独立的线程池中执行该项指定线程池的线程数默认为4
* dns_ttl_default: 域名解析成功的结果会被放到域名缓存中该项指定其存活时间单位为秒默认值12小时当解析结果过期后会重新解析以获取最新内容
* dns_ttl_min: 当通信失败时有可能出现缓存的结果已经失效的情况该项指定一个较短的存活时间当通信失败时以更频繁的速率更新缓存单位为秒默认值3分钟
* resolv_conf_path: 该文件保存了访问DNS相关的配置在常见的Linux发行版上通常位于`/etc/resolv.conf`,若该项配置为`NULL`则表示使用多线程同步解析的模式
* hosts_path: 该文件是一个本地的域名查找表若被解析的域名命中该表则不会向DNS发起请求在常见的Linux发行版上通常位于`/etc/hosts`,若该项配置为`NULL`则表示不使用查找表
### resolv.conf扩展功能
Workflow对`resolv.conf`配置文件进行了扩展,用户可以通过修改配置以支持`DNS over TLS(DoT)`功能,**注意**直接修改`/etc/resolv.conf`会影响其他进程可以将该文件复制一份用于修改并将Workflow的`resolv_conf_path`配置修改为新文件的路径。例如使用`dnss`协议的`nameserver`会通过SSL进行连接
## SSL DNS (DoT)的支持
我们的主分支代码不包括windows分支支持通过SSL连接访问DNS服务器即DoT。
我们简单的扩展了**resolv.conf**的格式你可以通过以下方式加入一个SSL dns server:
~~~bash
nameserver dnss://8.8.8.8/
~~~
我们用**dnss://** 来表示一个SSL dns server地址。通过以上的配置我们所有的DNS请求都将通过SSL连接与全球DNS server 8.8.8.8通信。
全球DNS server完美支持SSL连接大家可以立刻试用一下这个功能。
为了不用修改系统的**resolv.conf**文件,你可能需要创建一个私有的**resolv.conf**并修改workflow配置
~~~cpp
#include <workflow/WFGlobal.h>
#include <workflow/WFTaskFactory.h>
int main()
{
struct WFGlobalSettings settings = GLOBAL_SETTINGS_DEFAULT;
settings.resolv_conf_path = "./myresolv.conf";
WORKFLOW_library_init(&settings);
WFHttpTask *task = WFTaskFactory::create_http_task("https://www.example.com", ...);
...
}
nameserver dnss://[2001:4860:4860::8888]/
~~~
### DNS解析策略与DNS cache过期策略
* 同一个域名同时只会发送一个DNS请求这是通过一种异步锁机制实现的
* 只有一种例外两个不同的upstream指向同一个域名但分别要求只用IPv4和只用IPv6可能会同时发起两个请求。
* 对DNS server的最大并发受dns_server_params.max_connections控制默认为200。
* 某个域名DNS cache过期一瞬间如果多个请求同时需要请求该域名会有一个任务重新发起DNS请求并把原cache有效期临时延长5秒尽量减少DNS cache过期引起的访问中断。
### Address Family
在某些网络环境下虽然本机支持IPv6但因未被分配公网IPv6地址而无法与外部通信例如本地IPv6地址以`fe80`开始)。此时可以将`endpoint_params.address_family`设置为`AF_INET`来强制域名解析时仅解析IPv4地址。同样的`resolv.conf`文件中可能同时指定了`nameserver`的IPv4地址和IPv6地址此时可以将`dns_server_params.address_family`设置为`AF_INET``AF_INET6`来强制仅使用IPv4或IPv6地址来访问DNS
### 使用Upstream配置
全局配置默认对每个域名生效,若需要对某些域名单独指定不同的配置,则可使用[Upstream](./about-upstream.md#Address属性)功能。使用Upstream可以单独指定`dns_ttl_default``dns_ttl_min`配置项,以及通过`endpoint_params.address_family`单独指定该域名使用的IP地址类别。
## 域名解析与缓存策略
网络任务通常需要通过域名解析获取到需要访问的IP地址Workflow中域名解析相关策略如下
1. 检查域名缓存是否有该域名对应的IP地址若有缓存且未过期则使用该组IP地址
2. 检查域名是否为IPv4、IPv6地址或`Unix Domain Socket`,若是则直接使用该地址,无需发起域名解析
3. 检查`hosts_path`文件中是否包含该域名对应的IP地址若有则直接使用该地址
4. 获取异步锁保证同一域名的解析请求在同一时刻仅发起一次并向DNS发起解析请求
5. 解析成功后会将解析结果保存到当前进程的域名缓存中,以供下次使用,并释放异步锁
6. 解析失败后会释放异步锁且将失败原因通知给等在同一个异步锁上的所有任务通知结束后再发起的新的任务则会再次请求DNS
许多需要大量发起网络请求的场景都会配备域名缓存组件如果每次发起网络任务时都向DNS发起解析请求则DNS必然会不堪重负。Workflow设置了缓存存活时长dns_ttl_default和dns_ttl_min来保证缓存会在合理的时间后过期以及时更新域名的解析结果。当某个域名的缓存项过期后首先发现过期的任务会将其存活时间延长5秒并向DNS发起解析请求5秒内同一域名上的请求会直接使用缓存的DNS解析结果而无需等待本次解析结束。
异步锁机制可以保证**同一域名**的解析请求在同一时刻仅发起一次在没有锁保护的情况下若短时间内对同一域名发起大量网络任务每个任务都会因无法从缓存中获取结果而向DNS发起解析请求这会对DNS带来很大且不必要的负担。这里的同一域名表示的是`(host, port, family)`三元组若通过Upstream的方式对某域名分别要求只使用IPv4和IPv6则他们会被不同的异步锁保护也就有可能同时发起DNS请求。
### 异步域名解析
Workflow实现了完备的DNS任务参考[dns_cli](./tutorial-17-dns_cli.md)),若指定了`resolv_conf_path`配置项则向DNS发起域名解析时会使用异步请求的方式进行在类Unix系统下Workflow默认使用`/etc/resolv.conf`作为该配置的值。异步域名解析不会阻塞任何线程,也不会独占线程池,可以更高效地完成域名解析的任务。
### 同步域名解析
若指定`resolv_conf_path``NULL`,则会通过调用`getaddrinfo`函数来实现同步域名解析,该方式会使用独立的线程池,其线程数通过`dns_threads`参数配置。若短时间内需要发起较多的域名解析请求,则同步的方式会带来较大的延迟。