浅谈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!

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

(0)

相关推荐

  • Springcloud GateWay网关配置过程图解

    一般为了不暴露自己的端口信息等,会选择架构一个网关在前面进行阻挡,起到保护的作用.附上一张工作示列图. 1.配置网关9527 gateway作为网关需要和其他的应用一样需要注册进eureka中进行管理,先创建应用gateway9527 pom文件,关键是gateway依赖 <dependencies> <dependency> <groupId>com.bai</groupId> <artifactId>cloud-api-common</

  • 详解SpringCloudGateway内存泄漏问题

    SpringCloudGateway内存泄漏问题 项目完善差不多,在进入压力测试阶段期间,发现了gateway有内存泄漏问题,问题发现的起因是,当时启动一台gateway,一台对应的下游应用服务,在压力测试期间,发现特别不稳定,并发量时高时低,而且会有施压机卡住的现象,然后找到容器对应的宿主机,并使用container stats命令观察内存,经过观察发现,压力测试时内存会暴涨,并由于超过限制最大内存导致容器挂掉(这里由于用的swarm所以会自动选择节点重启)最终发现由于之前测试服务器配置低,所

  • 解决spring cloud gateway 获取body内容并修改的问题

    之前写过一篇文章,如何获取body的内容. Spring Cloud Gateway获取body内容,不影响GET请求 确实能够获取所有body的内容了,不过今天终端同学调试接口的时候和我说,遇到了400的问题,报错是这样的HTTP method names must be tokens,搜了一下,都是说https引起的.可我的项目还没用https,排除了. 想到是不是因为修改了body内容导致的问题,试着不修改body的内容,直接传给微服务,果然没有报错了. 问题找到,那就好办了,肯定是我新构

  • Spring Cloud Gateway全局通用异常处理的实现

    为什么需要全局异常处理 在传统 Spring Boot 应用中, 我们 @ControllerAdvice 来处理全局的异常,进行统一包装返回 // 摘至 spring cloud alibaba console 模块处理 @ControllerAdvice public class ConsoleExceptionHandler { @ExceptionHandler(AccessException.class) private ResponseEntity<String> handleAc

  • spring cloud gateway请求跨域问题解决方案

    这篇文章主要介绍了spring cloud gateway请求跨域问题解决方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 代码如下 @Configuration public class CorsConfig implements GlobalFilter, Ordered { private static final String ALL = "*"; private static final String MAX_AGE =

  • 浅谈springcloud gateway 连接保活问题

    项目中使用了springcloud gateway作为网关,上游与负载均衡服务器连接. 近期通过监控系统观察,发现网关与上游负载均衡服务器保持的TCP连接有300+,初步怀疑是调用方未释放连接 用如下方法进行分析: 1)周期性采集当前建立的连接及端口数据 首先是每隔10分钟连续采集2两个小时,发现在两个小时之内新出现的端口不到12个,再逐步缩短采样周期,到最后每秒采集一次,分析发现每秒种建立一个连接,同时关闭一个连接,当仍存在300+连接,这些连接对应的端口称为不活跃端口,记录下这300+不活跃

  • 浅谈SpringCloud之Ribbon详解

    一.什么是负载均衡 负载均衡:建立在现有网络结构之上,它提供了一种廉价有效透明的方法扩展网络设备和服务器的带宽.增加吞吐量.加强网络数据处理能力.提高网络的灵活性和可用性. 现在网站的架构已经从C/S模式转变为B/S模式,C/S模式是有一个专门的客户端,而B/S模式是将浏览器作为客户端.当用户在浏览器上输入一个网址按下回车键后,就会产生一个请求,在远方的服务器会处理这个请求,根据这个请求来生成用户想要的页面,然后将这个页面响应给浏览器,这样用户就能看到他想要看到的东西.我们知道,一台服务器处理数

  • 浅谈springcloud常用依赖和配置

    spring cloud常用依赖和配置整理 常用依赖 // pom.xml <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="

  • 浅谈SpringCloud之zuul源码解析

    zuul各版本实现存在一些微小的变化,总的实现思想未改变,以spring-cloud-netflix-core-1.3.6.RELEASE为例 一.zuul的重要的初始化类 org.springframework.cloud.netflix.zuul.ZuulServerAutoConfiguration org.springframework.cloud.netflix.zuul.ZuulProxyAutoConfiguration org.springframework.cloud.netf

  • 浅谈SpringCloud feign的http请求组件优化方案

    1 描述 如果我们直接使用SpringCloud Feign进行服务间调用的时候,http组件使用的是JDK的HttpURLConnection,每次请求都会新建一个连接,没有使用线程池复用.具体的可以从源码进行分析 2 源码分析 我们在分析源码很难找到入口,不知道从何开始入手,我们在分析SpringCloud feign的时候可用在配置文件下面我讲一下个人的思路. 1 首先我点击@EnableFeignClients 看一下这个注解在哪个资源路径下 如下图所示: 2 找到服务启动加载的配置文件

  • 浅谈SpringCloud实现简单的微服务架构

    Spring Cloud是一系列框架的有序集合.它利用Spring Boot的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册.配置中心.消息总线.负载均衡.断路器.数据监控等,都可以用Spring Boot的开发风格做到一键启动和部署.Spring并没有重复制造轮子,它只是将目前各家公司开发的比较成熟.经得起实际考验的服务框架组合起来,通过Spring Boot风格进行再封装屏蔽掉了复杂的配置和实现原理,最终给开发者留出了一套简单易懂.易部署和易维护的分布式系统开发工具包. 接下

  • 浅谈Spring-cloud 之 sleuth 服务链路跟踪

    这篇文章主要讲述服务追踪组件zipkin,Spring Cloud Sleuth集成了zipkin组件. 一.简介 Add sleuth to the classpath of a Spring Boot application (see below for Maven and Gradle examples), and you will see the correlation data being collected in logs, as long as you are logging re

  • 浅谈JS中的三种字符串连接方式及其性能比较

    工作中经常会碰到要把2个或多个字符串连接成一个字符串的问题,在JS中处理这类问题一般有三种方法,这里将它们一一列出顺便也对它们的性能做个具体的比较. 第一种方法 用连接符"+"把要连接的字符串连起来: str="a"; str+="b"; 毫无疑问,这种方法是最便捷快速的,如果只连接100个以下的字符串建议用这种方法最方便. 第二种方法 以数组作为中介用 join 连接字符串: var arr=new Array(); arr.push(a);

  • 浅谈js停止事件冒泡 阻止浏览器的默认行为(阻止超连接 #)

    在前端开发工作中,由于浏览器兼容性等问题,我们会经常用到"停止事件冒泡"和"阻止浏览器默认行为". 1..停止事件冒泡 JavaScript代码 //如果提供了事件对象,则这是一个非IE浏览器 if ( e && e.stopPropagation ) //因此它支持W3C的stopPropagation()方法 e.stopPropagation(); else //否则,我们需要使用IE的方式来取消事件冒泡 window.event.cancel

  • 浅谈sql连接查询的区别 inner,left,right,full

    --table1 表 ID NAME QQ PHONE 1 秦云 10102800 13500000 2 在路上 10378 13600000 3 LEO 10000 13900000 4 秦云 0241458 54564512 --table2 表 ID NAME sjsj gly 1 秦云 2004-01-01 00:00:00.000 李大伟 2 秦云 2005-01-01 00:00:00.000 马化腾 3 在路上 2005-01-01 00:00:00.000 马化腾 4 秦云 20

随机推荐