TCP Keepalive机制刨根问底

TCP Keepalive机制刨根问底!

Tcp Keepalive的起源

TCP协议中有长链接和短链接之分。短链接环境下,数据交互完毕后,主动释放链接;html

双方创建交互的链接,可是并非一直存在数据交互,有些链接会在数据交互完毕后,主动释放链接,而有些不会,那么在长时间无数据交互的时间段内,交互双方都有可能出现掉电、死机、异常重启,仍是中间路由网络无端断开、NAT超时等各类意外。java

当这些意外发生以后,这些TCP链接并将来得及正常释放,那么,链接的另外一方并不知道对端的状况,它会一直维护这个链接,长时间的积累会致使很是多的半打开链接,形成端系统资源的消耗和浪费,为了解决这个问题,在传输层能够利用TCP的保活报文来实现,这就有了TCP的Keepalive(保活探测)机制。linux

Tcp Keepalive存在的做用

探测链接的对端是否存活

 在应用交互的过程当中,可能存在如下几种状况:数据库

  1. 客户端或服务端意外断电,死机,崩溃,重启。
  2. 中间网络已经中断,而客户端与服务器并不知道。

           
利用保活探测功能,能够探知这种对端的意外状况,从而保证在乎外发生时,能够释放半打开的TCP、
链接。ubuntu

防止中间设备因超时删除链接相关的链接表

中间设备如防火墙等,会为通过它的数据报文创建相关的链接信息表,并未其设置一个超时时间的定时器,若是超出预约时间,某链接无任何报文交互的话,中间设备会将该链接信息从表中删除,在删除后,再有应用报文过来时,中间设备将丢弃该报文,从而致使应用出现异常,这个交互的过程大体以下图所示:
image.png服务器

这种状况在有防火墙的应用环境下很是常见,这会给某些长时间无数据交互可是又要长时间维持链接的应用(如数据库)带来很大的影响,为了解决这个问题,应用自己或TCP能够经过保活报文来维持中间设备中该链接的信息,(也能够在中间设备上开启长链接属性或调高链接表的释放时间来解决。网络

常见应用故障场景:socket

某财务应用,在客户端须要填写大量的表单数据,在客户端与服务器端创建TCP链接后,客户端终端使用者将花费几分钟甚至几十分钟填写表单相关信息,终端使用者终于填好表单所需信息后,点击“提交”按钮,结果,这个时候因为中间设备早已经将这个TCP链接从链接表中删除了,其将直接丢弃这个报文或者给客户端发送RST报文,应用故障产生,这将致使客户端终端使用者全部的工做将须要从新来过,给使用者带来极大的不便和损失。 tcp

TCP保活报文交互过程

TCP保活的交互过程大体以下图所示:
                 ide

TCP保活可能带来的问题:

  1. 中间设备因大量保活链接,致使其链接表满,网关设备因为保活问题,致使其链接表满,没法新建链接(XX局网闸故障案例)或性能降低严重
  2. 正常链接被释放

      当链接一端在发送保活探测报文时,中间网络正好因为各类异常(如链路中断、中间设备重启等)而没法将保活探测报文正确转发至对端时,可能会致使探测的一方释放原本正常的链接,可是这种可能状况发生的几率较小,
另外,通常也能够增长保活探测报文发生的次数来减小这种状况发生的几率和影响。

TCP Keepalive协议解读

下面协议解读,基于 RFC1122#TCP Keep-Alives  (注意这是协议的解读站在协议的角度)

  1. TCP Keepalive 虽不是标准规范,但操做系统一旦实现,默认状况下须为关闭,能够被上层应用开启和关闭
  2. TCP Keepalive必须在 没有任何数据(包括ACK包)接收以后的周期内才会被发送,容许配置,默认值不可以小于2个小时
  3. 不包含数据的ACK段在被TCP发送时没有可靠性保证,意即一旦发送,不确保必定发送成功。系统实现不能对任何特定探针包做死链接对待
  4. 规范建议keepalive保活包不该该包含数据,但也能够包含1个无心义的字节,好比0x0。
  5. SEG.SEQ = SND.NXT-1,即TCP保活探测报文序列号将前一个TCP报文序列号减1。SND.NXT = RCV.NXT,即下一次发送正常报文序号等于ACK序列号;总之保活报文不在窗口控制范围内 有一张图,能够很容易说明,但请仔细观察Tcp Keepalive部分:
  6. image.png

TCP Keepalive 须要注意的点(协议层面)

  1. 不太好的TCP堆栈实现,可能会要求保活报文必须携带有1个字节的数据负载
  2. TCP Keepalive应该在服务器端启用,客户端不作任何改动;若单独在客户端启用,若客户端异常崩溃或出现链接故障,存在服务器无限期的为已打开的但已失效的文件描述符消耗资源的严重问题。
  3. 但在特殊的NFS文件系统环境下,须要客户端和服务器端都要启用Tcp Keepalive机制。
  4. TCP Keepalive不是TCP规范的一部分,有三点须要注意:

    • 在短暂的故障期间,它们可能引发一个良好链接(good connection)被释放(dropped)
    • 它们消费了没必要要的宽带
    • 在以数据包计费的互联网消费(额外)花费金钱

Tcp keepalive 如何使用

如下环境是在Linux服务器上进行,应用程序若想使用须要设置SO_KEEPALIVE套接口选项才可以生效。

系统内核参数配置

  1. tcp_keepalive_time,在TCP保活打开的状况下,最后一次数据交换到TCP发送第一个保活探测包的间隔,即容许的持续空闲时长,或者说每次正常发送心跳的周期,默认值为7200s(2h)。
  2. tcp_keepalive_probes 在tcp_keepalive_time以后,没有接收到对方确认,继续发送保活探测包次数,默认值为9(次)
  3. tcp_keepalive_intvl,在tcp_keepalive_time以后,没有接收到对方确认,继续发送保活探测包的发送频率,默认值为75s。
发送频率tcp_keepalive_intvl乘以发送次数tcp_keepalive_probes,就获得了从开始探测到放弃探测肯定链接断开的时间;

若设置,服务器在客户端链接空闲的时候,每90秒发送一次保活探测包到客户端,若没有及时收到客户端的TCP Keepalive ACK确认,将继续等待15秒*2=30秒。总之能够在90s+30s=120秒(两分钟)时间内可检测到链接失效与否。

如下改动,须要写入到/etc/sysctl.conf文件:

net.ipv4.tcp_keepalive_time=90
net.ipv4.tcp_keepalive_intvl=15
net.ipv4.tcp_keepalive_probes=2

保存退出,而后执行sysctl -p生效

可经过 sysctl -a | grep keepalive 命令检测一下是否已经生效。

针对已经设置SO_KEEPALIVE的套接字,应用程序不用重启,内核直接生效。

Java/netty服务器如何使用

只须要在服务器端一方设置便可,客户端彻底不用设置,好比基于netty 4服务器程序:

ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
             .channel(NioServerSocketChannel.class)
             .option(ChannelOption.SO_BACKLOG, 100)
             // 心跳监测
             .childOption(ChannelOption.SO_KEEPALIVE, true)
             .handler(new LoggingHandler(LogLevel.INFO))
             .childHandler(new ChannelInitializer<SocketChannel>() {
                 @Override
                 public void initChannel(SocketChannel ch) throws Exception {
                     ch.pipeline().addLast(
                             new EchoServerHandler());
                 }
             });

            // Start the server.
            ChannelFuture f = b.bind(port).sync();

            // Wait until the server socket is closed.
            f.channel().closeFuture().sync();

Java程序只能作到设置SO_KEEPALIVE选项,至于TCP_KEEPCNT,TCP_KEEPIDLE,TCP_KEEPINTVL等参数配置,只能依赖于sysctl配置,系统进行读取

其余语言怎么使用:连接

TcpKeepLive常见的使用模式

默认状况下使用keepalive周期为2个小时,如不选择更改属于误用范畴,形成资源浪费:内核会为每个链接都打开一个保活计时器,N个链接会打开N个保活计时器。

优点很明显:

  1. TCP协议层面保活探测机制,系统内核彻底替上层应用自动给作好了
  2. 内核层面计时器相比上层应用,更为高效
  3. 上层应用只须要处理数据收发、链接异常通知便可
  4. 数据包将更为紧凑

关闭TCP的keepalive,彻底使用业务层面心跳保活机制 彻底应用掌管心跳,灵活和可控,好比每个链接心跳周期的可根据须要减小或延长

业务心跳 + TCP keepalive一块儿使用,互相做为补充,但TCP保活探测周期和应用的心跳周期要协调,以互补方可,不可以差距过大,不然将达不到设想的效果

朋友的公司所作IM平台业务心跳2-5分钟智能调整 + tcp keepalive 300秒,组合协做,听说效果也不错。

虽说没有固定的模式可遵循,那么有如下原则能够参考

  1. 不想折腾,那就弃用TCP Keepalive吧,彻底依赖应用层心跳机制,灵活可控性强
  2. 除非能够很好把控TCP Keepalive机制,那就能够根据须要自由使用吧

TcpKeepLive 注意事项

咱们知道TCP链接关闭时,须要链接的两端中的某一方发起关闭动做,若是某一方忽然断电,另一端是没法知道的。tcp的keep_alive就是用以检测异常的一种机制。

有三个参数:

  • 发送心跳消息的间隔
  • 未收到回复时,重试的时间间隔
  • 重试的次数

  若是是Linux操做系统,这三个值分别为

huangcheng@ubuntu:~$ cat /proc/sys/net/ipv4/tcp_keepalive_time
7200
huangcheng@ubuntu:~$ cat /proc/sys/net/ipv4/tcp_keepalive_intvl
75
huangcheng@ubuntu:~$ cat /proc/sys/net/ipv4/tcp_keepalive_probes
9

       也就意味着每隔7200s(两个小时)发起一次keepalive的报文,若是没有回应,75秒后进行重试,最多重试9次即认为链接关闭。

       这三个选项分别对应TCP_KEEPIDLE、TCP_KEEPINTL和TCP_KEEPCNT的选项值,经过setsockopt进行设置。

可是,tcp本身的keepalive有这样的一个bug:
**
正常状况下,链接的另外一端主动调用colse关闭链接,tcp会通知,咱们知道了该链接已经关闭。

可是若是tcp链接的另外一端忽然掉线,或者重启断电,这个时候咱们并不知道网络已经关闭。

而此时,若是有发送数据失败,tcp会自动进行重传

重传包的优先级高于keepalive,那就意味着,咱们的keepalive老是不能发送出去。 而此时,咱们也并不知道该链接已经出错而中断。在较长时间的重传失败以后,咱们才会知道

为了不这种状况发生,咱们要在tcp上层,自行控制。

对于此消息,记录发送时间和收到回应的时间。若是长时间没有回应,就多是网络中断。若是长时间没有发送,就是说,长时间没有进行通讯,能够自行发一个包,用于keepalive,以保持该链接的存在。

测试结果

  按照例子的值在一端的socket上开启keep alive,而后阻塞在一个recv或者不停的send,这个时候拔了网线,测试从拔掉网线到recv/send返回失败的时间。

       在linux kernel里头的测试发现,对于阻塞型的socket,当recv的时候,若是没有设置keep alive,即便网线拔掉或者ifdown,recv很长时间不会返回,最长达17分钟,虽然这个时间比linux的默认超时时间短了不少。

可是若是设置了keep alive,基本都在keepalive_time +keepalive_probeskeepalive_intvl =33秒内返回错误。*
       
可是对于循环不停send的socket,当拔掉网线后,会持续一段时间send返回成功(0~10秒左右,取决 于发送数据的量),而后send阻塞,由于协议层的buffer满了,在等待buffer空闲,大概90秒左右后才会返回错误。
由此看来,send的时候,keep alive彷佛没有起到做用,这个缘由是由于重传机制。能够经过给send以前设置timer来解决的。

相关文章
相关标签/搜索