大文件下载与 VPN:在请求处选择路径
路由器无法在响应到达之前得知其大小。按任务分流需要应用层的决策点,或者一种保持现有路径的排队策略。
“把大下载绕开 VPN,把小的响应留在 VPN 上”听起来像一条路由策略,其实并不是。路由器只能凭当下已有的信息做决定:地址、端口、协议、防火墙标记、一条已识别的流。响应的最终大小不在其中,因为它还没到;等到路由器能测量字节时,它已经在转发这些字节了。如果你要按任务分流,决定必须移到知道任务的地方,下面的清单就是用来判断你的设计里有没有这样一个点。
路由器为什么量不出响应的大小
在一个数据包需要路由的那一刻,路由器最多只见过一条流的前几个包。它能说出谁和谁在通信、用什么协议、带什么标记、到目前为止过了多少字节。已经过去的字节描述的是历史。响应的大小属于一个尚未生成的响应,而对于分块或流式响应,它也许永远无法预先得知。
就连协议自身的元数据也比看上去更弱。HTTP 允许在正文之前不公布长度的响应,而一次用户操作可能产生许多请求,它们的总成本与任何一个头部数值都不相似。把头部当作任务的大小,先是一个建模错误,然后才是路由错误。
检查 1:是否存在能选择路径的决策点?
指出将要做出选择的那一组件,然后说明它在做选择的那一刻知道什么。从一组有文档记录的 host 拉取更新的更新程序,在传输开始之前就知道自己的目的。一个先查元数据、再打开请求的下载服务,在连接存在之前就知道任务。一个配合的 proxy 可以应用请求级策略或缓存响应,而不必假装自己是路由器。
如果答案是“网关”,这份清单已经失败了。网关可以按目的地、标记或客户端网段对已知流分类,并从第一个包起把它们送到既定路径;这对一个获批的更新程序往往够用,而对未知的响应大小永远不够。
检查 2:元数据究竟告诉了你什么?
应用感知的分流取决于它读取的字段。HTTP 的语义中关于响应长度的规则允许开头拿不到长度的情形,而内容编码本来就会让传输的字节数与表示的大小不同。因此,一个在头部跨过阈值时切换路径的策略会表现得前后不一,而且偏偏最容易出现在它本想抓住的大传输上。
应用元数据能够支撑的,是关于某个任务的决定:这个工作流是批量同步,这个是交互式的,那个是来自已知来源的软件更新。那是用意图语言写成的策略,稳定到足以测试。“这个响应会大于四十兆字节”不是那种策略。
检查 3:这条流是否已经存在?
一条流的路由可以改变,而连接的身份不变,这一区别值得分清楚。TCP 连接由其两端的地址和端口标识,如果它下面的路由表发生变化,数据包可以走另一条路。无法悄然改变的是网络地址转换的状态:把流量移到另一个出口通常会改变公网源元组,远端会看到一个没有对应状态的另一个对端。
conntrack -L -p tcp --dport 443 | head -5
ip route get 203.0.113.10
ss -tn state established dst 203.0.113.10
所以实用的规则是:在连接中途从一个出口移到另一个出口,是一段新关系,而不是一次迁移。如果设计要求同一条连接继续下去,决定就必须在第一个 SYN 之前做出,事后追加的任何包规则都无法干净地重建它。
检查 4:真正的需求是延迟,而不是出口?
很多“把大流量送到别处”的请求,其实是在抱怨一次大传输让交互式工作显得很慢。那是调度问题,而它有一个调度上的答案:在现有路径上使用公平排队或受支持的带宽策略,就能在不移动任何一方的情况下,避免大批量流把一条聊天消息饿死。
tc qdisc replace dev eth0 root cake bandwidth 100mbit
这样处理症状,可以让出口决定留在安全策略放置它的地方,而且一条命令就能回退。为整整一类流量更改出口,是一次带有认证、日志和访问规则后果的策略变更,所以它不该是延迟投诉时最先拿起的工具。
如果需求确实是另一个互联网出口,就用这些词说出来,并在受支持的边界上实现:从已获批的工作流新建连接,或者由一个应用通过选定出口打开它自己的请求。然后记录哪些任务走哪个出口,免得下一个人还要从路由表里推断。
把清单留在变更记录里:决策点、驱动它的字段或任务、流是否已经存在、测到的症状是延迟还是出口。四个问题,而能回答全部四个的策略,是你可以运营而不必辩护的策略。