一、生产case描述
当SpringCloudGateway下游应用发生阻塞(如full-gc)时, SpringCloudGateway的TCP连接数瞬时大幅度增长且长时间无法恢复,导致对外提供接口耗时骤增。图中tcp连接数下降是因服务重启所致;
二、case原因分析(ps:项目使用的SpringCloudGateway默认配置)
- SpringCloudGateway调用下游服务时会默认使用httpClient连接池,连接池默认最大连接数maxConnections=2^23 -1,默认maxIdleTime=null不会回收空闲connection,connection默认连接超时connectTimeout=45s,responseTimeout=null(默认数据从源码得知);
- 下游业务(如: trading)发生了阻塞(full-gc)时,接口响应缓慢,httpClient连接池原有连接被占用,新的业务请求创建新tcp连接,导致连接数暴涨现象(如图);
在下游服务回复后,httpClient连接池中闲置的connection也无法释放因为maxIdleTime=null不会回收闲置的连接(图中连接数下降是重启服务所致); - 当业务服务迟迟不能完成时(可能是连接时间过长,可能是业务服务处理时间过长),当前无配置超时时间,直接线程被hold住,引发TCP连接数暴涨,机器IO负载增高、处理速度变慢,最终导致接口性能下降;
三、修复建议
- 修改gateway的连接超时时间,空闲连接回收策略,httpClient响应超时时间;在项目配置文件中新增下列配置:
spring.cloud.gateway.httpclient.connect-timeout = 200 ---------------连接超时时间,原默认超时时间45s,会导致大量连接无法获得有效响应(45s远远大于上游服务的接口熔断时间);
与下游服务连接超时时间保持一致
spring.cloud.gateway.httpclient.response-timeout = PT10S ---------------响应读取超时时间,原默认为Null,没有具体限制,会导致大量连接被占用,连接池连接不够用创建新的tcp连接;
如果下游服务使用了异步框架如Spring WebFlux,则该时间不能设置太短。对于一些耗时的接口而言因为时间太短gateway会主动中断链接,业务还未执行完成便因为链接中断触发业务流中断;
spring.cloud.gateway.httpclient.pool.max-idle-time = PT10S ------------HttpClient连接池连接最大空闲时间,原默认为Null,不会回收闲置连接,导致大量闲置tcp连接无法释放,占用服务器资源,接口性能下降;暂定10s,可根据生产服务监控数据适当调整,流量冲击或者服务阻塞后的一段时间内,若流量恢复正常,tcp连接数较高、接口性能下降等可适当调小max-idle-time;若流量冲击持续时间较长(>10s),可根据实际情况适当调大max-idle-time;(ps: max-live-time也可以作为释放条件,但是优先级没有max-idle-tim高,原因如下图) - spring.cloud.gateway.httpclient.pool.eviction-interval = PT30S ------------ 定时回收HttpClient连接池中连接的时间间隔,默认=PT0S是不会生效;netty 连接池中的连接只有在取和放的时候才会根据max-idle-time判断连接是否需要释放,本次修改连接池的回收策略为LIFO后只会使用最新鲜的连接,可能会导致连接池早期连接无法触发max-idle-time而无法释放,故需要开启定时回收;(这个配置是很多网上文档会忽略)
- 上述通过配置connection、response和闲置连接回收策略和最大空闲时间控制httpClient连接池的连接数在一个合理可控的数值,且在流量冲击后也会快速回到正常范围内,保障gateway服务的性能;
- 在SpringCloudGateway启动的时候加上jvm参数: -Dreactor.netty.pool.leasingStrategy=lifo; 网关默认使用的是FIFO释放连接池连接的策略,换句话说就是只要连接池有空闲连接,就会使用连接池中最早的空闲连接;而LIFO释放策略则是优先使用连接池中最近的可用空闲连接;
根据上述配置假设:下游服务A full_gc阻塞时最大gateway最大连接数是5k,那再出现类似的情况也不释放连接,因为gateway默认的是fifo策略,正常情况下的qps在2.5k,设置max_idle_time=10s,5k个连接一直都不会满足max_idle_time=10s的条件;
yml文件可以参考下列配置:
spring:
cloud:
gateway:
httpclient:
connect-timeout: 200
response-timeout: PT10S
pool:
max-idle-time: PT10S
eviction-interval: PT30S
注意:修改释放连接池连接的策略需要重启服务(FIFO -> LIFO)