背景

最近在使用springboot(Windows下)连接redis(云服务器)开发时发现一些问题:连接成功的情况下,在一段时间未交互数据后,再次通过连接与Redis传输数据回出现异常java.io.IOException: 远程主机强迫关闭了一个现有的连接。
于是我上网找了一些博客主要是两种:

  • 可能是客户端连接太多了,开启timeout设置或tcp-keepalive
  • 将配置的的tcp-keepalive设置为60(可能之前是300)

这两个设置是什么意思呢

  • timeout,单位是秒,如果客户端连接空闲时间达到这个时间,就释放掉这个连接
  • tcp-keepalive,单位是秒,空闲时间达到这个时间就发送一个tcp-keepalive心跳包,用于检测客户端(在这里就是我的springboot程序)是否在线

探索

为了弄清楚原因,我开始对Linux用tcpdump抓包,在Windows下用wireshark抓包分析,发现一些问题

  • 我的设置是tcp-keepalive 300,在到达300s后,Linux的tcpdump抓到redis发送了心跳包,但是并未得到客户端回应,Windows的wireshark也没有抓到有收到redis的心跳包

  • 此后,redis每100秒重发一次心跳包,直到600秒发送rst,释放了tcp连接,而Windows还是无感知,连接状态是establish,因此,第10分钟(600秒)后,客户端用原来的连接与Linux的redis通信,不会得到ack回应,于是不断重发,直到超出阈值,springboot出现远程主机关闭连接的异常,然后重新进行三次握手重建连接

  • 并非要到10分钟连接才会失效,据我的测试,在240s后,Windows的springboot尝试通过连接与Linux的redis通信,但是发送不会得到ack,于是重发,直到超过阈值释放,重新三次握手。
    此时,springboot不会出现刚刚那个异常,但是是打印如下日志:
    在这里插入图片描述

    此时,Linux端还保留原来的连接,直到10分钟后(就像上面那样)。

  • 如果使用本地Windows的redis服务器,即使tcp-keepalive是300,redis在300s发送的心跳包就能被wireshark抓到,并且得到回应,保持连接,也就是表现正常。

由上面几点分析,可以认为两端的tcp通信受到阻碍。240s后,无论是Linux通过tcp连接给本地Windows发(tcp-keepalive),还是Windows给Linux发,都是无法得到回应。并且是Linux和本地的开发机之间才会出现,在一段时间不通信后,tcp连接就失效了。

推测原因

为了检验这个是底层tcp出现了问题,于是使用golang写了个小程序,连接后不发心跳包,等到250s后发送消息。
测试结果结果是:同样出现了发送数据未得到ack,重试几次后释放,tcpdump也没抓到对应数据。也就是我这边发的数据那边根本就没收到。
由于这个时间比较固定,在我的环境中,240秒后连接就失效了,我推测是某些地方设置了过期时间,再由同一个本地环境中的redis和springboot表现正常,我的推测是这可能是NAT(网络地址转换协议)的会话过期导致的
这个协议的作用是在本地多个设备使用同一个公网IP的情况下,通过一些配置路由到对应的子网设备。

解决

在我这里,只要把tcp-keepalive设置为240秒以下就好了

结束

推测的原因没有亲自去证实,希望有大佬能够证实或指出错误。

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐