奇怪的 40ms 延迟
date
Oct 22, 2021
updateDate
slug
strange-40ms-delay
status
Published
tags
Network
Istio
TCP
summary
最近给线上服务引入了 istio,发现 个别服务的一些请求刚好延迟
40ms
左右,初步排查下来是因为 Delay Ack 和 TCP 的 Nagle 算法共同左右导致的。。。type
Post
最近给线上服务引入了 istio,发现 个别服务的一些请求刚好延迟
40ms
左右,初步排查下来是因为 Delay Ack 和 TCP 的 Nagle 算法共同左右导致的,
如图所示:
如上图中所示,红框中第 1 个数字是
duration
整体的延迟, 第 2 个数字是 x-envoy-upstream-server-time
参考 envoy 文档 介绍:
Contains the time in milliseconds spent by the upstream host processing the request and the network latency between Envoy and upstream host. This is useful if the client wants to determine service time compared to network latency between client and Envoy. This header is set on responses.
我按照我的理解简单画了个图:
即
duration - x-envoy-upstream-server-time
为花费在 envoy 上的时间。抓包。
可以看到 server No.26 的 包发出去之后,envoy 等了 40ms (31.275-31.235)左右才发出了 ack (No.28)。而且 server 后续的响应一直在等这个 ack。这里有两个问题
- 为什么 40ms 后才发出 ack
- 当有 数据需要发到对方的时候,ACK会随着数据一块发送
- 在等待期间收到新数据 - 发送
- 超时 - 发送
TCP Delay ACK
在收到数据包的时候, 会根据一定的规则来检查是否需要发送 ACK包,用若干 ack 报文组合在一起成为单个报文,从而减少协议开销。
具体规则如下:
- 为什么 server 端会因为这个 ack 而阻塞。
- 当新包大于
MSS
,立即发出 - 带包含有
FIN
flag,立即发出 - 当
TCP_CORK
未设置,TCP_NODELAY
设置了,立即发出 - 当
TCP_CORK
未设置,但是所有包都有 ACK,或者说所有小包都有 ACK,立即发出 - 其他情况都会阻塞。例如上述例子中含有一个 未被确认的小包,只到收到 ACK 中才会发出后续包。
TCP Nagle Algo
tcp nagle 算法用来解决 tcp 的拥塞问题。规定在任意时刻,最多只能拥有一个未被 ack 的包,
假设此时服务端需要有更多的包要返回都会阻塞合成一个大包。
参考linux 代码 注释,解释 TCP Nagle 何时阻塞,何时立即发包。
这两个算法共同作用,导致了服务端 40ms 延迟。
- 客户端因为有 delay ack 一直在等新包产生才会 ack。
- 服务端因为开启了 Nagle 算法,需要等 上个包 的 ack 才会发新包
可见陷入了死锁,直到 delay ack timeout 才打破僵局。
如何解决
- 设置 TCP_NODELAY 参数
- Go 中次 flag 默认被设置
- Node 中此 flag 可以手动配置
可以看到 如果含有 TCP_NODELAY 参数,包会立即被发出去。
为什么开启 isito 才有这个问题。
其实一直都有这个问题,只是 istio 开启后问题被放大。可以看到在抓包图里,TCP MSS 协商的 6w+,远高于常规的 1500,这是因为 istio 开启后,业务容器和 lo 网卡交互,lo 的默认 mtu 是 65536,放大了该问题。
2023 更新:关于禁用 nagle 算法是否正确
禁用 nagle算法 或者 设置 TCP_NODELAY 是否是正确,以及是否会带来坏处。
我觉得 nagle算法 弊大于利,至少在内网场景下是这样的,网络质量很高,无需关心拥塞这个问题。
此问题还在 HN 上被热烈讨论,详见 ⬇️