2 minutes
Tcp_user_timeout
背景
最近小伙伴讲到 TCP_USER_TIMEOUT
这个tcp 参数, 今天特意学习下
深入
- TCP_USER_TIMEOUT 用来做什么?和其他参数会不会相互作用?
TCP_USER_TIMEOUT 是用来超时检测. 当上个健康的package 发出去一段时间后依旧没有消息返回, 会触发 TCP_USER_TIMEOUT 机制. 当时这个不是100%触发, 比如 下游 recv buffer 满了, 导致 client send buffer 也堵塞重传 (swnd=0), 这个时候 TCP_USER_TIMEOUT 是不会计数的
- 什么场景用?
client 最好都添加上, 但是如果有 keepalive 的设计, 需要保证:
TCP_USER_TIMEOUT >= TCP_KEEPIDLE + TCP_KEEPINTVL * TCP_KEEPCNT.
文章学习
文章: https://blog.cloudflare.com/when-tcp-sockets-refuse-to-die/
TCP_USER_TIMEOUT: 文章里讲到 linux kernel 5.2 并没有效果, 对 client connect sync retry 没有限制
TCP_DEFER_ACCEPT:
final-ack 没有传递的时候, server 还是会将 fd 将 sync-recv 移动到 estab
因此, 设计了 TCP_DEFER_ACCEPT 来避免这个情况
java tcp_so_timeout
tcp keepalive 的学习
SO_KEEPALIVE = 1 - Let's enable keepalives.
TCP_KEEPIDLE = 5 - Send first keepalive probe after 5 seconds of idleness.
TCP_KEEPINTVL = 3 - Send subsequent keepalive probes after 3 seconds.
TCP_KEEPCNT = 3 - Time out after three failed probes.
不同场景的讨论:
-
空闲连接下, TCP_USER_TIMEOUT 会影响到 KEEPALIVE, 导致 keepalive 的连接被关闭. 也就是说 心跳消息不会重置 TCP_USER_TIMEOUT 的超时 计算; 如果 TCP_USER_TIMEOUT 设置的特别大, 还会使 TCP_KEEPCNT 无效.
-
send unack 消息的连接, TCP_USER_TIMEOUT 会影响到 KEEPALIVE, 导致连接因为长期没有接收到 相应而关闭
-
server 处理慢, client sender 被流控. 这个时候 server read buffer 被填满然后流控了, 返回滑动窗口 0, sender send buffer 也是满的, 这个时候 TCP_USER_TIMEOUT 会暂停直到 win不为0 才重新计算.
所以总结下来, TCP_USER_TIMEOUT 是不受重传影响的, 重传不会导致 TCP_USER_TIMEOUT 更新. 文章中还提供, 对于慢连接(server处理慢), 不在使用应用的超时, 而是使用 TCP_USER_TIMEOUT, 这样更加有效
推荐配置:
- Enable TCP keepalives
- Set TCP_USER_TIMEOUT to TCP_KEEPIDLE + TCP_KEEPINTVL * TCP_KEEPCNT.
- 一般使用 TCP keepalives and user-timeout 足够了, 如果希望降低资源及时清理超时的连接, 可以 ioctl (统计unsent+unacked) 和 TCP_INFO (tcp unsent)
学习
对于我们而言, 这个有什么意义? 主要是 TCP_USER_TIMEOUT, 没有设置的情况下, 如果下游主机直接挂了, client 没有感知, 那么 client 还在不断的发送数据 直到 sendbuffer 满了, 这个时候 write timeout 超时 才会导致连接关闭, 但是这个时间 已经很长了, 对业务是有损的. 因此 通过设置 TCP_USER_TIMEOUT 能够解决问题
补充
在 grpc 的实现中, 当开启keepalive的时候, 会将超时设置到 TPCUserTimeout 中
if kp.Time != infinity {
if err = syscall.SetTCPUserTimeout(conn, kp.Timeout); err != nil {
return nil, connectionErrorf(false, err, "transport: failed to set TCP_USER_TIMEOUT: %v", err)
}
keepaliveEnabled = true
}