问题现象:
今天遇到一个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使用已关闭的连接的时候就会报这个错误。
如下图:
解决办法
那么解决办法很明显了:
- 给netty的httpclient连接池增加限制,在连接超时后回收连接,此连接超时时间需小于tomcat连接超时时间
- 修改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不算小。