vlambda博客
学习文章列表

TCP已有SO_KEEPALIVE参数,为什么还要在应用层加入心跳?

TCP已有SO_KEEPALIVE参数,为什么还要在应用层加入心跳?

本篇文章大概1300字,阅读时间大约5分钟


从本篇开始,开启一个新的专题——总结Netty如何配置TCP的重要参数,以及这些参数的背后意义和调优方案。本文主要总结了TCP的探活机制的问题。

TCP已有SO_KEEPALIVE参数,为什么还要在应用层加入心跳?

都知道TCP协议是一种面向连接的协议,它会在两个节点之间建立一条虚拟链路,一般的,连接可以通过三次握手建立,之后节点之间就可以通信。如果双方较长时间都不向对方发送数据,那么该条TCP链路就会长时间处于空闲状态,如果要想知道网络传输层的TCP链路的状态,那么就必须有一种探活机制,参数SO_KEEPALIVE就是干这个的。


不仅是网络传输层,在应用层一般也会设计独立的探活机制,也叫心跳程序,一般的,大家讨论心跳是说的应用层的心跳那这又是为什么呢?回答这个问题,个人认为必须分清心跳需求是在哪一个层面,是网络传输层还是在应用层?


在网络传输层,TCP的KEEPALIVE机制被实现在服务器侧(即被动连接侧),服务端充当主角,客户端充当KEEPALIVE的响应角色,是配角,之所以它被实现在服务端,是因为一般情况下服务端寿命长,产品级的应用往往需要24*365不间断提供服务,而客户端大部分都是“朝生夕死”,如下:

TCP已有SO_KEEPALIVE参数,为什么还要在应用层加入心跳?

比如家里断网,断电,手机被偷而被关机。。。经常被动掉线,此时客户端机器的TCP协议栈没来得及发FIN包去正常关闭TCP链路,甚至连RST包都来不及或者没机会发。。。这就导致服务端积累大量“僵尸”链路,即该链路在服务端的状态仍然是ESTABLISHED,服务端认为它们仍然“正常的存活”,不会去主动清理它们,但实际上它们已经不可用了。久而久之,服务端的资源会被大量占用。以Linux系统为例,一个Socket文件描述符大概占3Bytes内存,如果服务端是Java程序,那么JVM的Socket对象本身也会占内存,所以3bytes是非常保守的估计了。于是,协议设计者就为TCP协议搞了一个SO_KEEPALIVE参数,并且配套实现了传输层的心跳程序,它会检测TCP链路的空闲时间,如果持续空闲了120分钟,那么服务端的TCP协议栈会主动发一个keepalive报文段给客户端,对方没有回复,默认会重传9次,每次重传间隔是75s,9*75s后客户端仍然没响应,服务端会主动关闭该链路。期间,如果距离上一次传输数据后120分钟内又发生了数据传输,那么TCP心跳程序会将自己的定时器重置,反复执行以上流程。


这看起来非常美好,但往往事与愿违,因为这个空闲触发到链路关闭的时间太长了,极限值大约是2.1875小时(7200+75*9),所以它根本满足不了应用层需求,在大流量的产品中,2个多小时足以积累大量“僵尸”连接,所以很少有应用只依赖它,取而代之的是在应用层实现独立的心跳程序。


以上,只是从TCP协议探活机制缺陷的角度来阐述,其实还有一个重要原因,即所谓的单一职责,分层设计思想的考虑。因为网络传输层只负责通信,它并不关心业务。也就是说TCP的探活程序只能保证链路正常存活,但无法得知通信双方的应用层是否正常,比如通信双方虽然能互相交流数据,但服务端可能已经报了空指针异常,或者OOM了,或者死锁了,响应码5xx了。。。


综上,可以得知网络协议的心跳程序针对的是协议本身所在的层,按照计算机网络分层模型,各层应该负责维护自己本层的数据传输的状态,故应用层也需要有独立的心跳程序,设计一套符合自身业务特点的探活和空闲检测机制。空闲检测的时间可以灵活定制,比如秒级,或者几分钟时间,这样就能满足大流量的服务端及时发现和清理失效的链路。而TCP的探活时间,是系统级的参数,如果随意定制,那么很可能导致整个系统出现问题。


小结如下:

1、分层思想,单一职责

2、时效性考虑

3、系统级参数——牵一发动全身,不灵活,以Linux为例,它有三个参数可以影响TCP协议的探活程序:

  • tcp_keepalive_time 默认7200s:即空闲检测时间,距离上次传送报文段后,7200s未收到新报文段,就开始检测

  • tcp_keepalive_intvl 默认75s:即空闲检测后,每隔75s发送一个心跳包

  • tcp_keepalive_probes 默认9次:即触发空闲检测后,一共发送9次心跳包,对方仍然未响应,服务端才主动关闭连接

4、TCP协议的心跳程序是一个可选项,默认不会开启。


END


点亮在看,你最好看

~

阅读原文,获得更多精彩内容