跳到主要内容

9. 其他考虑事项

9.1. 服务实例 (Service Instances)

本文使用 "服务实例 (service instance)" 指运行在某台主机上的软件, 该软件能够在一组 { IP address, port } 元组上接收连接. 之所以称为一个实例, 是因为无论客户端使用这些元组中的哪一个进行连接, 它连接到的都是同一逻辑节点上运行的同一软件, 并会获得相同的应答和相同的密钥材料.

服务实例是从客户端视角识别的. 如果客户端配置的是 { IP address, port } 元组, 它无法判断某个元组上的服务是否就是监听在另一个元组上的同一服务器. 因此在这种情况下, 客户端会把每个不同的元组都视为不同的服务实例.

在某些情况下, 客户端配置的是主机名和端口号. 端口号可以与主机名一起显式给出, 也可以省略并假定为某个默认值. 主机名和端口号也可以从网络获知, 例如通过 DNS SRV 记录. 在这些情况下, { hostname, port } 元组在通常的 DNS 名称大小写不敏感比较规则 [RFC1034] 下唯一标识服务实例.

两个主机名可能指向某些共同的 IP 地址, 这属于配置异常, 客户端没有义务检测. 其影响可能是, 客户端被告知断开连接后, 因为同一服务器表现为另一个服务实例, 又重新连接到了同一台服务器.

实现不应解析主机名后再通过匹配 IP 地址来判断两个实体是否为 "同一服务实例".

9.2. 任播考虑事项 (Anycast Considerations)

当某个任播 (anycast) 服务配置在特定 IP 地址和端口上时, 虽然会有多个物理服务器响应该 IP 地址, 但每个服务器都必须可以被视为等价. 这里的 "等价" 指这些服务器能提供同一服务, 并且在适用时能在建立连接时提供相同的认证信息, 例如 PKI 证书.

如果网络拓扑变化导致某个 TCP 连接中的数据包被发送到不了解该连接的任播服务器实例, 新服务器会因为没有该连接记录而自动用 TCP reset 终止连接, 随后客户端可以按需重新连接或停止使用该连接.

如果连接重新建立后, 客户端关于连接到同一实例的假设在某些方面被破坏, 在本文语境下应视为不正确行为. 不过, 就这类情况给出具体建议超出了本规范范围; 后续描述 DNS Stateful Operations 具体用途的文档可以处理该问题.

9.3. 连接共享 (Connection Sharing)

如 DNS-over-TCP [RFC7766] 先前规定:

To mitigate the risk of unintentional server overload, DNS clients MUST take care to minimize the number of concurrent TCP connections made to any individual server. It is RECOMMENDED that for any given client/server interaction there SHOULD be no more than one connection for regular queries, one for zone transfers, and one for each protocol that is being used on top of TCP (for example, if the resolver was using TLS). However, it is noted that certain primary/secondary configurations with many busy zones might need to use more than one TCP connection for zone transfers for operational reasons (for example, to support concurrent transfers of multiple zones).

单个服务器可以支持多种服务, 包括 DNS Updates [RFC2136], DNS Push Notifications [Push], 以及一个或多个 DNS 区域上的其他服务. 当客户端发现多个不同操作的目标服务器是同一服务实例时 (见第 9.1 节), 客户端应当对这些操作使用同一个共享的 DSO Session.

这一要求有两个好处. 第一, 它减少 DNS 服务器上不必要的连接负载. 第二, 它避免为同一 DNS 服务器建立每个额外新连接所需的启动时间.

不过, 服务器实现者和运营者应意识到, 并非所有情况下都能共享连接. 单个主机设备上可能运行多个互不协调的独立客户端软件实例. 类似地, 同一 NAT 网关之后的多个独立客户端设备通常也会在 DNS 服务器看来表现为同一客户端 IP 地址上的不同源端口. 由于这些限制, DNS 服务器必须准备好接受来自同一客户端 IP 地址上不同源端口的多个连接.

9.4. 中间盒运行考虑事项

当应用层中间盒 (例如 DNS 代理, 转发器或会话复用器) 位于路径中时, 必须小心避免 DSO 流量被错误处理. 避免此类问题的最简单方式是不使用中间盒. 如果无法避免, 应评估中间盒以确认其行为正确.

中间盒的正确行为应满足以下条件之一:

  • 中间盒不转发 DSO 消息, 并用 NOERROR 或 DSOTYPENI 以外的响应码回应 DSO 消息.

  • 中间盒充当 DSO 服务器, 并按本规范建立连接.

  • 入站连接和出站连接之间存在 1:1 对应关系, 因而当连接建立到中间盒时, 可以保证中间盒到某个 DNS 解析器也恰好建立一个对应连接, 并且所有入站消息都会在不修改和不重排序的情况下被转发. NAT 转发器或 TCP 连接优化器就是例子, 例如面向地球同步卫星链路等高延迟连接的优化器.

不满足上述条件之一的中间盒很可能以意外且难以诊断的方式失败. 例如, DNS 负载均衡器可能从入站 TCP 流中拆出 DNS 消息, 并把同一流中的每个消息转发给不同 DNS 服务器. 如果使用这种负载均衡器, 且其指向的 DNS 服务器实现并启用了 DSO, DSO Session 建立可能会成功, 但客户端和服务器之间不会存在一致的会话. 如果该负载均衡器指向未实现 DSO 或配置为不允许 DSO 的 DNS 服务器, 则不会出现这种问题, 但一旦安装实现了 DSO 的新服务器软件, 这种配置就存在意外失败风险.

当然, 可以实现正确支持 DSO 的中间盒. 甚至可以实现支持长期操作的 DSO 中间盒. 这可以通过保持前述入站和出站连接的 1:1 对应关系来完成, 也可以由中间盒终止入站会话, 同时在中间盒中维护被请求的长期操作状态. 本文不详细规定这种做法.

9.5. TCP 延迟确认考虑事项

大多数现代 TCP 实现都包含称为 "延迟确认 (Delayed Acknowledgement)" 的功能 [RFC1122].

没有该功能时, TCP 可能非常浪费网络资源. 以一个缺少延迟确认的简单 TCP 远程登录实现为例: 用户敲击一个按键时发送一个数据包. 数据包到达服务器后, 简单 TCP 实现立即发送确认. 几毫秒后, 服务器进程读取这一个字节的按键数据, 因而 TCP 实现又立即发送窗口更新. 又过几毫秒后, 服务器进程生成字符回显并立即发送该数据包. 在这种情况下, 简单 TCP 实现几乎瞬间发送三个包: ack, 窗口更新和数据.

显然, 如果 TCP 实现能把三个独立包合并成一个会更高效, 这正是延迟确认功能提供的能力.

使用延迟确认时, TCP 实现在收到数据包后通常等待 200 ms, 然后在以下任一条件满足时发送 ack: (a) 又有更多数据包到达, (b) 接收进程生成某些回复数据, 或 (c) 200 ms 已经过完且前两者都未发生.

使用延迟确认后, 远程登录效率更高, 每个字符回显只生成一个包而不是三个包.

延迟确认的逻辑是, 200 ms 延迟不会造成显著危害. 如果另一端正在等待某些内容, 接收进程就应生成另一端等待的回复, TCP 随即会立即发送该回复, 并把 ack 和窗口更新合并进去. 如果接收进程实际上不会为该特定消息生成回复, 那么按定义另一端就不会等待任何内容. 因此, 200 ms 延迟是无害的.

但如果发送方使用 Nagle 算法, 这个假设可能不成立. Nagle 算法也是一种提高效率的功能, 用于保护网络免受写法不佳的客户端软件连续快速执行大量小写入的影响. 它允许把这些小写入合并为更大, 浪费更少的数据包.

遗憾的是, Nagle 算法和延迟确认这两个有价值的效率功能在一起使用时可能产生不良交互 [NagleDA].

DSO 请求消息会引发响应; DSO 单向消息和 DSO 响应消息不会引发响应.

对于会引发响应的 DSO 请求消息, Nagle 算法和延迟确认按预期工作.

对于不会引发响应的 DSO 消息, 延迟确认机制会使 ack 延迟 200 ms. ack 的这 200 ms 延迟又可能导致 Nagle 算法阻止发送方继续发送数据, 直到等待的 ack 到达. 在往返时间低于毫秒级的企业级 Gigabit Ethernet (GigE) 骨干网上, 200 ms 延迟相对而言非常巨大.

提出该问题时, 常见的两个解决方案都并不理想:

  1. 禁用延迟确认. 对不会引发响应的 DSO 消息而言, 去掉延迟确认可避免无谓的 200 ms 延迟, 并立即返回 ack, 告知 Nagle 算法可以让发送方立刻发送下一个包. 但对确实会引发响应的 DSO 消息而言, 去掉延迟确认会失去把 ack 与数据合并的效率收益, 响应方现在会发送两个或三个包而不是一个.

  2. 禁用 Nagle 算法. 当 ack 被延迟确认算法推迟时, 去掉 Nagle 算法可防止发送方被阻塞, 使其能立即发送下一个小包. 但在往返时间较高的网络上, 去掉 Nagle 算法会丧失把多个小包合并为较少大包的效率收益, 而该收益的目标正是限制同时在途的小包数量.

这里的问题是, 对不会引发响应的 DSO 消息而言, TCP 实现会卡在等待状态, 不确定应用是否即将生成响应, 还是 TCP 应继续发送 ack 和窗口更新.

解决方案是提供一种网络 API, 允许接收方告知 TCP 实现: 收到的消息已经读取并处理完毕, 且不会为该消息生成响应. 这样 TCP 就可以停止等待永远不会到来的响应, 并立即发送 ack 和窗口更新.

对于 DSO 实现, 不推荐禁用延迟确认, 因为这可能损害网络.

对于 DSO 实现, 不推荐禁用 Nagle 算法, 因为这可能损害网络.

在本文准备发布时, 已知至少有一种 TCP 实现允许 TCP 消息接收方发出信号, 表明它不会发送响应, 因而延迟确认机制可以停止等待. 在操作系统提供该功能的实现中, 应当使用它.