文章目录
转载请注明出处
省流
同时启用Keep-Alive机制与User-Timeout机制。
关于TCP
协议层保活与应用层保活
在TCP中,保活机制是用来检测连接的存活性,并在需要时终止或重新建立连接的一种机制。在TCP协议中,保活机制可以在协议层和应用层两个不同的层次上实现:其中协议层保活是在TCP协议层实现的一种保活机制。它是由TCP协议栈自动管理的,无需应用程序干预。而应用层保活是在应用程序层面实现的一种保活机制。它通常由应用程序自己管理,而不依赖于TCP协议栈。
本文不讨论这两种方式的优劣以及适用场景,只讨论如何在协议层覆盖全场景的保活。
TCP超时重传机制
下面的讨论中,会频繁提到这一机制,因此需要提前进行一些简单说明,如果你对此不陌生,可以跳过这段说明。
TCP超时重传机制是TCP协议中用于确保数据传输可靠性的重要机制之一。在TCP连接中,发送方会周期性地发送数据包到接收方,并等待接收方的确认(ACK)。如果发送方在一定时间内未收到接收方的确认,就会认为数据包丢失或损坏,触发超时重传机制。超时重传机制触发后,发送方会重新发送一份数据包,并等待ACK,这一过程将持续若干次,直到重传成功或是失败一定次数后认定为失败。
协议层保活的两种机制
TCP Keep-Alive
什么是TCP Keep-Alive
RFC 11221对于TCP Keep-Alives机制有如下描述:
A “keep-alive” mechanism periodically probes the otherend of a connection when the connection is otherwiseidle, even when there is no data to be sent.
“keep-alive” 机制会定期探测连接的另一端,当连接在其他情况下处于空闲状态时,即使没有数据要发送,也会进行探测。
不过RFC 1122并没有详细说明如何实现TCP Keep-Alives机制,甚至出于一些原因这个机制本身并不是标准的一部分,是否存在取决于对于TCP协议的实现。不过它指出,一些TCP实现中,包含了这些机制,以及它们的工作原理:
Some TCP implementations, however, have included a keep-alive mechanism. To confirm that an idle connection is still active, these implementations send a probe segment designed to elicit a response from the peer TCP.
某些 TCP 实现中,包含了一个 “keep-alive” 机制。为了确认一个空闲的连接仍然是活动的,这些实现会发送一个探测段,旨在引发对端 TCP 的响应。
Linux所实现的TCP协议符合这份描述。此外,RFC 1122中还说明了keep-alive的适用场景:
A TCP keep-alive mechanism should only be invoked in server applications that might otherwise hang indefinitely and consume resources unnecessarily if a client crashes or aborts a connection during a network failure.
TCP keep-alive 机制应当只在服务器应用程序中被调用,这些应用程序可能会在客户端在网络故障期间崩溃或中止连接时,无谓地无限期挂起并消耗资源。
经典TCP Keep-Alive的陷阱
乍一看,只使用这个机制就可以轻易做到保活,覆盖所有的场景,包括但不限于自身或是对端的网络意外中断。但实际上,这是一个经典的误区。考虑如下场景,一个TCP套接字两端存在持续的数据交换,其中某一端网络故障,另一端所配置的Keep-Alive机制是否会被启动?答案是否定的。因为Keep-Alive机制是用于“确认一个空闲的连接仍然是活动的“。
那么什么是空闲?空闲实际上是指双方都没有任何数据要发送,也没有任何数据待确认,因为这意味着仍有数据需要被发送。
事实上,TCP超时重传机制触发时,存在需要确认的数据,此时套接字并不会被视为空闲,因而也就无法触发Keep-alive机制2。这一点可以通过抓包得到证实:
先是空闲状态下,某一端网络中断:
可以看到,连接建立后,由于没有数据要发送,断开网络后,Keep-Alive探测包持续被发送,最终因无法收到回复被判定为超时。
然后是持续交换数据时,某一端网络中断:
可以看到,由于数据长时间未被确认,触发了重传机制,经过相当长的一段时间后也没能触发Keep-Alive机制。
因此,TCP Keep-Alive 只能用于检测空闲连接是否还有效,无法处理持续数据交换中某一端突然网络中断的情况。
TCP User Timeout
由于Keep-Alive只适用于空闲连接的检测,因此我们需要一种针对非空闲连接的断链检测机制,这样组合起来就可以覆盖全场景了,这就是本节要介绍的User Timeout机制3。
什么是TCP User Timeout
TCP User Timeout在RFC 54824中被引入,其对于这项参数的概括如下:
The TCP user timeout controls how long transmitted data may remain unacknowledged before a connection is forcefully closed.
TCP 用户超时控制了传输数据在未被确认前可以保持未确认状态的最长时间。
换句话说,对于任意传输数据,如果在某个设定的时间内没有收到其ACK,则可以认为该TCP连接超时了。Linux实现了这个TCP选项,可以通过以下方式进行设置TCP_USER_TIMEOUT
:
int timeout = 5 * 1000;
setsockopt(fd, SOL_TCP, TCP_USER_TIMEOUT, &timeout, sizeof(timeout)));
对于这个选项,man tcp中有如下描述:
Increasing user timeouts allows a TCP connection to survive extended periods without end-to-end connec‐
tivity. Decreasing user timeouts allows applications to “fail fast”, if so desired. Otherwise, fail‐
ure may take up to 20 minutes with the current system defaults in a normal WAN environment.
增加用户超时时间允许TCP连接在没有端到端连接的情况下持续存在一段时间。减少用户超时时间可以使应用程序在需要时**“快速失败”**。否则,在正常的广域网环境中,失败可能需要长达20分钟的系统默认值。
这段话的重点在于**“快速失败”**,那么什么是快速失败?不妨先看看正常失败,由于TCP存在重传机制,如果一定时间内没有收到发送数据包的ACK,则会触发这种机制,TCP会重新发送一份该数据,并等待对应的ACK,重传会持续若干次,最终仍无法收到ACK则会认定为重传失败,套接字被系统关闭 ,默认情况下,这个过程将长达20分钟,显然这并不快速。但如若通过设置这项参数为5s,那么根据此前的定义,一旦5s内没有收到某个数据包的ACK,TCP连接就被认为是超时,这显然快得多。
适用场景
User timeout机制的适用场景与Keep-Alive恰恰相反:它用于检测非空闲连接是否还有效。
它的触发依赖于双方有持续的数据交换,更确切的说是,一方要存在等待ACK的包,对于空闲的套接字而言,由于没有数据要发送,自然也没有需要ACK,因而这种机制不会被触发,而对于Keep-Alive所发送的探测包,这个选项是无效的,man tcp中对此有特殊说明:
The option has no effect on when TCP retransmits a packet, nor when a keepalive probe is sent.
这个选项对于TCP重传以及keep-alive所发送的探测包无效。
双剑合璧,协议层优雅检测断链
综上所述,Keep-Alive机制与User-Timeout机制存在互补的关系,其中Keep-avlie可以用于检测空闲状态下套接字是否有效,而User-Timeout则可以用于检测非空闲状态下套接字是否有效,这两种机制单独拿出来都无法覆盖所有的场景,但若是同时启用则可以覆盖全部场景,可以做到优雅的TCP协议层保活。