文章目录

前言

了解TCP的KeepAlive机制有利于服务器调参。

TCP的KeepAlive

没错,和想象的一样,通过“心跳包”来检查链路是否连通,但在标准的TCP规范中,并没有保活的强制性要求。

传输层KeepAilve缺点

在传输层做保活有很多缺点:

(1)如果中间链路出现短暂的差错(比如某个路由器重启),可能会使得一个非常好的链路被释放掉

(2)心跳包耗费了不必要的带宽,增加了流量费用

(3)在一些复杂的网络环境下(比如某些网络不响应不带数据的报文),TCP保活机制可能会失效。

TCP的KeepAlive机制描述

但事实上,许多实现都提供了KeepAlive的功能(默认关闭)。  

如果一个给定的连接在7200秒(2小时)内没有任何动作,那么服务器就向客户端发送一个探查报文段,此时客户机可能处于如下状态:

(1)客户端正常运行,并可达。客户端响应一个报文,服务端收到报文后知道客户端是正常工作的,则将保活定时器复位,2小时后再次触发。此时间段内如果有任何应用报文通信,都会不断重置定时器的下次探测时间。

(2)客户端崩溃,已经关闭或者正在重启中。此时服务器收不到任何响应,75秒后,服务端将再次发送探查报文,一共会尝试10次探测报文的发送(包括第一次),每次间隔75秒,直到客户端回复响应或者达到10次。如果客户端都没有恢复,则服务端认为客户端已经关闭并终止连接。

(3)客户端崩溃并重启完成。此时服务器会受到客户端的响应,但这个响应是一个复位,使得服务端终止这个连接。

(4)客户端正常运行,但不可达。对服务器来说,处理方式和(2)一致。

传输层KeepAlive优点

(1)在应用层做KeepAlive是最好的事情,但会增加应用开发的设计心跳包复杂度。如果配置传输层就能够检测是否断开连接,应用层似乎就不用写心跳代码了。

(2)传输层的报文更小

Connection reset by peer

当出现情况(3)的时候,任意一端宕机恢复,由于不知道之前的连接信息,所以当之前的连接发过来一条保活探测,就会回送一个复位响应。这个响应将使得对端产生报错,例如:

read error: Connection reset by peer

在使用jmeter进行服务器压力测试中,也会经常看到connection reset by peer,但这个复位报文通常不是保活探测造成的,而是在TCP三次握手后,服务器Accept达到了最大值,直接拒绝连接产生的RST报文。

keepalive参数的修改带来变化

  • tcp_keepalive_time 保活探测时间:默认2小时。越长,则探测到链路断开的敏感度越低;越短,可能在保活上耗费大量带宽。
  • tcp_keepalive_intvl 重发探测报文间隔时间:默认75秒。越长,非法关闭的链路占用资源时间越长;越短,等不到客户端恢复,重试失去意义。
  • tcp_keepalive_probes 重发探测报文次数:默认9次。越多,非法关闭的链路占用资源时间越长;越少,可能只是网络波动就毁掉了一条很好的链路。
  • 为什么协议制定者把探测时间设定为默认2小时,就是为了弱化TCP保活的作用,强迫开发者尽可能在应用层做保活。

Linux的TCPkeepalive参数配置

查看Linux的TCP配置默认值:

7200秒探测一次、未响应重试9次(一共10次)、重试时间间隔75秒。

修改配置:

# 查询保活探测时间
cat /proc/sys/net/ipv4/tcp_keepalive_time
# 修改
sysctl net.ipv4.tcp_keepalive_time=3600

# 查询重试间隔时间
cat /proc/sys/net/ipv4/tcp_keepalive_intvl
# 修改
sysctl net.ipv4.tcp_keepalive_intvl=5

# 查询重试次数
cat /proc/sys/net/ipv4/tcp_keepalive_probes
# 修改
sysctl net.ipv4.tcp_keepalive_probes=3

或者修改/etc/sysctl.conf:

net.ipv4.tcp_keepalive_time=7200
net.ipv4.tcp_keepalive_intvl=75
net.ipv4.tcp_keepalive_probes=9

然后立即生效:

# 立即生效
sysctl -p
# 查看当前配置
sysctl -a | grep keepalive

Netty设置TCPkeepalive

网络通信必学的Netty,提供了.option的方式配置tcp参数:

ServerBootstrap sb = new ServerBootstrap();
sb.group(bossGroup, workerGroup)
     .channel(socketChannelClazz)
     .childHandler(new ChannelInitializer<SocketChannel>() {
                    ………………
      })
     .childOption(ChannelOption.SO_KEEPALIVE, true);
ChannelFuture f = sb.bind(host, port);
f.sync().addListener(FIRE_EXCEPTION_ON_FAILURE);

同时,可以利用Netty的Handler来做应用层的心跳包:

// 60秒没有read、write就触发idle事件
channel.pipeline().addFirst("idleStateHandler", new IdleStateHandler(60, 0, 0));
// 再用一个Handler去检查idle事件,清理资源,断开连接
 @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt){
        if(evt instanceof IdleStateEvent){
           .................
        }
    }


转载请注明出处http://www.bewindoweb.com/262.html | 三颗豆子
分享许可方式知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议
重大发现:转载注明原文网址的同学刚买了彩票就中奖,刚写完代码就跑通,刚转身就遇到了真爱。
你可能还会喜欢
具体问题具体杠