浅谈springcloud gateway 连接保活问题

 更新时间:2021年07月16日 09:44:07   作者:zhangbaolin  
这篇文章主要介绍了springcloud gateway 连接保活问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

项目中使用了springcloud gateway作为网关,上游与负载均衡服务器连接。

近期通过监控系统观察,发现网关与上游负载均衡服务器保持的TCP连接有300+,初步怀疑是调用方未释放连接

用如下方法进行分析:

1)周期性采集当前建立的连接及端口数据

首先是每隔10分钟连续采集2两个小时,发现在两个小时之内新出现的端口不到12个,再逐步缩短采样周期,到最后每秒采集一次,分析发现每秒种建立一个连接,同时关闭一个连接,当仍存在300+连接,这些连接对应的端口称为不活跃端口,记录下这300+不活跃端口。

2)为了进一步分析,用whireshark抓包

发现绝大部分情况下都是正常的连接和关闭,但这300+个不活跃端口对应的连接上没有任何数据,这300+个不活跃对应的连接称为不活跃连接。同步赶紧上马接口调用实时监控功能,发现实际的调用数量却非常少(每分钟不足10个)。

3)与上游的负载均衡工程师一起检查

从负载均衡服务器看到的活跃连接也是个位数,并且并未找到在网关上的不活跃端口。也就是说在负载均衡服务器已经已经拆除了与网关上的不活跃连接对应的连接。咨询负载均衡工程师,负载均衡设备对于1超过1个小时的不活跃连接会主动拆除。

经过以上分析,确定是外部系统经过负载均衡设备与网关建立连接后,并未进行任何操作,但网关会一直维护这个连接,导致网关的连接数持续上升。

为解决这个问题,需要首先回顾一下传统的TCP长连接维护机制

针对长连接的维护,传统的TCP服务采用心跳来维持,比如服务端每分钟发送一个心跳报文,并启动计数器并设置为1,客户端收到后回应一个报文,服务端收到回复报文后重置计数器,如果为收到应答,则一分钟再发送一个心跳报文,同时计数器加1,连续发送三个心跳报文并且未收到映带,则服务端则认为客户端已经失联,会主动拆除这个连接,以避免不必要的资源占用。

我们现在使用的springcloud gateway,显然很难直接修改源码增加以上的心跳机制,所以我又想到了操作系统协议栈的连接保活机制。

TCP协议栈的保活机制与应用层的长连接维护机制类似(当然,应用层的TCP长连接维护机制就是从协议栈的保护机制学习来的'&'),只不过是在协议栈层面完成,这样避免了应用层实现负载的长连接维护

保活机制如下:

1)服务器端判断一个连接在指定的时间内

(缺省为2小时)没有任何数据,则发送一个探测报文,并启动定时器

2)如果客户端在正常运行并且网络可达

则客户端则回复一个响应报文,服务端认为客户端正常,则重新开始计时。

如果客户端主机崩溃或网络不可达,服务端将收不到应答,定时器超时后(一般为75秒),服务端将再次发送探测报文,如此连续发送若干次(一般为10次),如果均未收到应答,则服务端将主动关闭连接。

当然,如果中间有任何一次服务端收到应答,则认为连接正常,不再发送探测报文。

使用如下命令可以查看以上保活时间、发送探测报文的间隔和次数:

#sysctl -a|grep keepalive
net.ipv4.tcp_keepalive_time = 7200(单位为秒)
net.ipv4.tcp_keepalive_probes = 9 
net.ipv4.tcp_keepalive_intvl = 75  (单位为秒)

关于保活参数中两个小时的时间设置存在争议,通常人们希望这个值可以小很多,比如分钟级,但保活间隔时间是系统级别的变量,如果改变该值会影响所有使用该功能的用户。

所以,Host Requirements RFC提出一个实现方式,保活间隔是可配置的,但缺省不小于两个小时,并且需要应用程序设置才启用。

如果使用协议栈的保活功能,那么缺省的两个小时的时间还是太长,如果缩短这个时间会有什么影响,并无把握。

所以还是先想其他办法,从网上看到可以通过以下代码修改网关对长连接的维护办法,以下代码是设置保活时间为3分钟,如果3分钟内连接上没有数据,网关将主动关闭连接:

配置文件:

server:
    netty:
        idie-timeout: 300
@Configuration
public class NettyConfig {
    @Bean
    publiWebServerFactoryCustomizer<NettyReactiveWebServerFactory> idleTimeoutCustomizer(
        @Value("${server.netty.idle-timeout}") Duration idleTimeout) {
        return factory -> factory.addServerCustomizers(
            server -> server.tcpConfiguration(
                tcp->tcp.bootstrap(
                   bootstrap->bootstrap.childHandler(new ChannelInitializer<Channel>() {
                       @Override
                       protected void initChannel(Channel channel) {
                           channel.pipeline().addLast(
                               new IdleStateHandler(0, 0, idleTimeout.toNanos(), NANOSECONDS) {
                                   private final AtomicBoolean closed = new AtomicBoolean();
                                   @Override
                                   protected void channelIdle(
                                       ChannelHandlerContext ctx, IdleStateEvent evt) {
                                           if (closed.compareAndSet(false, true)) {
                                               ctx.close();
                                           }
                                       }
                                   }
                               );
                           }
                       }))));
    }
}

系统上线后,通过监控系统发现网关连接数并未持续增长,刚松一口气,线上业务系统频频报错,请求网关失败,赶紧安排网络抓包,然后马上回退恢复业务。

然后对网络抓包进行分析,截图如下:

从抓包结果来看,客户端和网关经过3次握手后,建立了连接,但后面的建立SSL的过程中,网关返回了400 Bad Request,所以导致业务系统请求失败(业务系统使用https请求网关),怀疑是上面的代码中的配置覆盖了配置文件中SSL的相关配置,所以导致SSL连接未建立。

我们优秀的工程师,本着锲而不舍的精神对gateway进行源码分析,经过对代码的分析,发现确实是这个配置覆盖了原有的SSL配置,导致SSL配置未生效所致,所以对以上代码进行改写,具体如下:

@Configuration
public class NettyConfig {
    @Bean
    publiWebServerFactoryCustomizer<NettyReactiveWebServerFactory> idleTimeoutCustomizer(
        @Value("${server.netty.idle-timeout}") Duration idleTimeout) {
        return factory -> factory.addServerCustomizers(
            server -> server.tcpConfiguration(
                tcp->tcp.bootstrap(bootstrap->{
                  //增加如下代码,从而可保持原有配置并追加保活
                   BootstrapHandlers.updateConfiguration(bootstrap, "IdleStateHandler",
                      (connectionObserver, channel) ->{
                       channel.pipeline().addLast(new IdleStateHandler(0, 0,
                           idleTimeout.toNanos(), NANOSECONDS) {
                                 private final AtomicBoolean closed = new AtomicBoolean();
                                 @Override
                                 protected void channelIdle(ChannelHandlerContext ctx,
                                     IdleStateEvent evt) {
                                     if (closed.compareAndSet(false, true)) {
                                         ctx.close();
                                     }
                                 }
                             });
                         });
                        return bootstrap;
                    }
                )));
    }
}

进行测试验证,一切OK!

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • Java实现多级表头和复杂表头的导出功能

    Java实现多级表头和复杂表头的导出功能

    这篇文章主要为大家详细介绍了Java实现多级表头和复杂表头的导出功能的相关知识,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下
    2024-03-03
  • 深入了解java8的foreach循环

    深入了解java8的foreach循环

    虽然java8出来很久了,但是之前用的一直也不多,最近正好学习了java8。下面给大家分享java8中的foreach循环,感兴趣的朋友一起看看吧
    2017-05-05
  • 一文彻底掌握RocketMQ 的存储模型

    一文彻底掌握RocketMQ 的存储模型

    这篇文章主要介绍了RocketMQ 的存储模型,本文的重点在于分析 BrokerServer 的消息存储模型,笔者按照自己的理解 , 尝试分析 RocketMQ 的存储模型,需要的朋友可以参考下
    2022-12-12
  • SpringBoot 的 web 类型推断详解

    SpringBoot 的 web 类型推断详解

    这篇文章主要介绍了SpringBoot 的 web 类型推断详解的相关资料,需要的朋友可以参考下
    2022-12-12
  • SpringBoot接收参数所有方式总结

    SpringBoot接收参数所有方式总结

    这篇文章主要介绍了SpringBoot接收参数所有方式总结,文中通过代码示例和图文结合的方式给大家介绍的非常详细,对大家的学习或工作有一定的帮助,需要的朋友可以参考下
    2024-07-07
  • JAVA构造函数不能使用void关键字问题

    JAVA构造函数不能使用void关键字问题

    这篇文章主要介绍了JAVA构造函数不能使用void关键字问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-03-03
  • Java基础:彻底搞懂java多线程

    Java基础:彻底搞懂java多线程

    篇文章主要介绍了Java多线程的相关资料,帮助大家更好的理解和学习Java线程相关知识,感兴趣的朋友可以了解下,希望能给你带来帮助
    2021-08-08
  • 再也不用怕! 让你彻底搞明白Java内存分布

    再也不用怕! 让你彻底搞明白Java内存分布

    做Java的大都没有c++ 的那种分配内存的烦恼,因为Java 帮我们管理内存,但是这并不代表我们不需要了解Java的内存结构,因为线上经常出现内存的问题,今天聊一下内存的问题,需要的朋友可以参考下
    2021-06-06
  • Java9中对集合类扩展的of方法解析

    Java9中对集合类扩展的of方法解析

    这篇文章主要介绍了Java9 中对集合类扩展的of方法,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-09-09
  • JavaWeb 简单分页实现代码

    JavaWeb 简单分页实现代码

    这篇文章主要介绍了JavaWeb 简单分页实现代码的相关资料,需要的朋友可以参考下
    2016-11-11

最新评论