记录SpringCloudGateway的一个隐藏问题

简介: 线上生产环境中,一个SCG接口偶发性出现“Connection reset by peer”错误。排查发现问题是由于Netty的HTTP客户端连接池保持了已由服务端关闭的连接。解决方案是配置连接池以在超时后回收连接(超时时间应小于Tomcat的连接超时时间),并考虑将连接池获取策略从FIFO改为LIFO,以减少使用无效连接的可能性。通过修改Spring Cloud Gateway的HTTP客户端连接池配置和添加JVM启动参数可以实现这一修复。

问题现象:

今天遇到一个SCG的问题,线上生产环境在请求一个接口的时候,偶发性的会出现报错

io.netty.channel.unix.Errors$NativeIoException: readAddress(..) failed: Connection reset by peer

排查过程

看到这个错误的第一反应是连接被服务端关闭了,以为是服务端的问题,但是经过测试,不通过网关直接访问该服务时,是没有任何问题的,因此将目光聚焦到了SCG上,通过排查SCG的日志,发现确实是在网关层出现该问题,具体问题报错如上。

针对这个错误,去百度了一下,发现是因为scg底层netty的问题导致的,参考文章链接:https://blog.csdn.net/qq_22903677/article/details/128990516

然后在netty的github issues上也找到了对应的问题:https://github.com/reactor/reactor-netty/issues/938

大概意思如下:

netty为了节省开销,搞了一个http client的连接池,这个连接池维护了后端tomcat的连接,如果下次有请求过来,会先查连接池里是否有可复用的连接,如果有直接使用该连接

问题原因

问题就出现在这个连接池这,netty搞的这个连接池默认是不释放连接的,从连接池取用连接的策略默认是FIFO先入先出,也就是会取最早的连接,而tomcat连接是有最大空闲连接时间限制的,也就是说tomcat连接可能已经关闭了,但是netty的连接池里还保留这个连接,当netty使用已关闭的连接的时候就会报这个错误。

如下图:

解决办法

那么解决办法很明显了:

  1. 给netty的httpclient连接池增加限制,在连接超时后回收连接,此连接超时时间需小于tomcat连接超时时间
  2. 修改netty的httpclient连接池的获取连接策略,由FIFO改为LIFO,即每次获取最新的连接,最大限度保证获取的连接不是无效连接

具体配置如下:

修改连接池配置:

spring:
  cloud:
    gateway:
      httpclient:
        pool:
          type: fixed
          max-idle-time: 5000
          max-connections: 200
          acquire-timeout: 45000

修改连接池获取策略为LIFO:

增加jvm启动参数 -Dreactor.netty.pool.leasingStrategy=lifo

注:本人只配置了第一个修改连接池属性的参数,并未配置lifo,也可以生效;个人感觉仅配置lifo策略应该还会出现该问题,重点的配置仍然是连接池配置。

PS

一点小探究:

连接池的max-connections参数原理:

因为新增了http clinent的最大连接数的配置,不确定这个配置是否合理,因此看下这个参数的默认值,避免生产环境出现问题:

这个配置是通过SCG的HttpClientProperties类接受的

向下找到pool类

可以看到maxConnections的默认值为ConnectionProvider.DEFAULT_POOL_MAX_CONNECTIONS;

int DEFAULT_POOL_MAX_CONNECTIONS = Integer.parseInt(System.getProperty("reactor.netty.pool.maxConnections", "" + Math.max(Runtime.getRuntime().availableProcessors(), 8) * 2));

而默认最大连接数是跟处理器数量有关系,默认为16(8处理器一下),因此最大连接数设置为200不算小。

目录
相关文章
|
开发框架 程序员 API
【C#】.net core2.1,通过扩展状态代码页方法对404页面进行全局捕抓并响应信息
在开发一个网站项目时,除了异常过滤功能模块,还需要有针对404不存在的api接口和页面处理功能 本篇文章就来讲讲,如何自定义全局请求状态类来统一处理
230 0
|
Java 微服务
自定义ApiBoot Logging链路以及单元ID
`ApiBoot Logging`会为每一个请求都对应创建链路编号(`TraceID`)以及单元编号(`SpanID`),用于归类每一次请求日志,通过一个链路下日志单元的`Parent SpanID`可以进行上下级关系的梳理。
自定义ApiBoot Logging链路以及单元ID
|
JavaScript 前端开发 Go
第三十四章 使用 CSP 进行基于标签的开发 - Hyperevent例子
第三十四章 使用 CSP 进行基于标签的开发 - Hyperevent例子
123 0
|
SQL 前端开发 Java
Swagger直接返回MybatisPlus的Page类文档不展开显示问题剖析
Swagger直接返回MybatisPlus的Page类文档不展开显示问题剖析
513 0
Swagger直接返回MybatisPlus的Page类文档不展开显示问题剖析
|
XML 前端开发 JavaScript
Web---监听用户名注册技术-myAJax-隐藏帧
Web---监听用户名注册技术-myAJax-隐藏帧
137 0