IPsec 之上数据库查询卡住:UDP 乱序还是 TCP?
握手干净却卡住的查询,问题指向隧道路径内部。修路径还是退回 443 上的 TCP,由三个条件决定。
TCP 握手完成后查询卡住,并不是路由问题。路由故障会让握手失败,所以已经建立、随后却沉默的会话,指向路径内部:检测、MTU,或者隧道 UDP 包的到达方式。摆在面前的决策是修这条路径,还是把隧道切到 TCP。三个条件就能定下来,而没有一条与数据库有关。
先给卡顿分类,再怪罪隧道
四种结果在用户看来一样,对工程师却意味着完全不同的事情。
| What the client sees | What it rules out |
|---|---|
| Connection refused | Nothing is listening on the address and port the client reached |
| Timeout before the handshake | A filter, a missing route, or the wrong address |
| Handshake completes, query hangs | The session exists, so look at inspection, MTU and packet order |
| Stalls that recover on retry | Often reordering, or a state table entry expiring |
只有最后两行值得在隧道上花时间。跨 VPN 的连接被拒绝,通常是数据库只监听 loopback,这是监听端的问题;握手前的超时则是过滤或路由。两者都不会因为更换封装而改善。
先把分类写进工单。这是最便宜的一步,能砍掉大部分排查空间。
决定 UDP 还是 TCP 的三个条件
封装后的 IPsec 流量跑在 UDP 上,而 UDP 让路径可以随意乱序和丢包,且不通知任何人。当乱序把包推出隧道的防重放窗口之外,它们会被静默丢弃,内层 TCP 会话只能干等。握手成功、查询却永不返回,背后的机制就是这个。
修路径还是退回 TCP,取决于三件事。第一,路径上是真的有丢包,还是只有乱序?第二,能否改变检测发生的位置,或者把这条隧道排除在造成乱序的组件之外?第三,工作负载是否对卡顿模式敏感,例如许多复用会话共用一条链路?
如果第一个答案是丢包、第二个是否,那么 443 上的 TCP 就是务实的退路。如果第二个答案是是,就修路径,继续用 UDP。
读防火墙的记录,而不是应用的报错
应用只会报超时,这对定位原因毫无用处。丢包或拦截出现在防火墙自己的日志里,所以按数据库和应用地址过滤,同时找丢弃和放行。威胁防护模块可以丢弃数据包,却不产生应用层错误,而这正是查询卡住的样子。日志里的沉默也是数据:它指向不写日志的环节,比如乱序。
也要检查集群。配置错误的集群可能把会话留在某个成员的状态表里,而数据包走了另一个成员的路径,于是回包到达没有状态的地方。这类卡顿看起来随机,且与负载无关。
动手之前先比较两端,而且比较到达顺序,而不是包数:
tcpdump -ni any udp port 4500 -w /tmp/encap.pcap
tcpdump -ni any tcp port 5432 -w /tmp/inner.pcap443 上的 TCP 究竟让你付出什么
把隧道切到 TCP 会消除乱序的症状,因为中间设备看到的是有序字节流。但它也制造了 TCP 套 TCP。外层连接会重传内层已经重传过的数据,一次丢包就可能同时拖住所有复用会话。在干净的路径上看不出问题;在有丢包的路径上,这种疗法比症状更糟。
当替代方案是完全不通时,这笔交换可以接受,但应当连同责任人记为一项例外,而不是当成常态吸收掉。
记录传输方式、端口、一条样例查询和实测结果,以及日期。没有这份记录,下一位工程师会把实验再做一遍,例外就这样悄悄变成架构。
让这个决定可以复检
路径诊断很快就会过时。中间设备被替换,模块配置变了,链路升级,而 443 上 TCP 的理由已经消失,配置却留了下来。给例外加一个复检日期,并写明什么条件允许你回到 UDP。
用一条小查询和一个大结果集分别测试两种传输。乱序会表现为与大小相关的卡顿,而监听端故障对两种大小一视同仁。