SpringCloudGateway tcp连接无法回收的分析与修复
scg tcp连接不回收的分析与修复
一、springcloud版本
第一个版本:Hoxton.SR12
第二个版本:2021.0.2
在第一个版本出问题后升级了第二个版本,可是问题还是依旧出现。
二、网关功能
包含数据加解密、websocket转发这两个额外的功能;
三、 生产描述
2,网关基本上每分钟都有人访问,tcp连接数持续增长,到65535左右后,不再接受新的请求,服务就无法访问,只能重启
四、 解决方式一(tcp连接还是不回收,对我的网关没效果)
https://blog.csdn.net/weixin_43142697/article/details/122605048,参照了这个里面的修改意见,我在nacos里面增加了网关配置,可是观察了快两个小时,没有一个链接回收掉,以下是我的配置:
cloud:
gateway:
httpclient:
pool:
max-idle-time: PT1S
eviction-interval: PT30S
connect-timeout: 20000
response-timeout: PT30S
使用 ss -aoen|grep 443|grep ESTAB 命令获取以下图片内容,左侧红圈中是用户ip,隔一段时间就使用该命令查看,发现ip一个不少,不知道是我配的有问题还是什么原因,我也暂时没有时间细究了,等有时间我要再定位下看看
五、解决方式二(验证通过)
1、使用netstat -tnpoa|sed -n -e 2p -e /443/p,看下图,查看这些tcp连接的状态,无一例外都是ESTABLISHED off 先看看这个的英文解释: keepalive - when the keepalive timer is ON for the socket on - when the retransmission timer is ON for the socket off - none of the above is ON 其实很明白,说这些连接当前的监听器既不是keepalive 也不是on,换句话说就是没有用于回收的监听器,连接永远无法回收 2、再使用ss -aoen|grep 443|grep ESTAB查看有没有监听器Timer,看下图,右侧红圈中只有ino,没有出现timer字段,也就是没有用上tcp的keepalive功能,说明确实链接没有用到监听回收器 3、不禁要疑问下,会不会是代码的问题,不然springcloud gateway不可能犯这种低级错误,但时间紧,也不能再细想了,但有时间我还得看看自己的代码坑。 4、我们看下正常情况下的tcp链接的keepalive功能,可以看到下图中的 Timer:(keepalive,119min,0),说明这几个tcp都是会被服务器探测是否失效的,其中119min是指119分钟后开始探测该链接是否有效,0代表第一次探测失败后的重试探测次数。
5、通过sysctl -a |grep keepalive,可以查看到linux下默认的配置,虽然这里看到配置了keepalive,但是Tcp进程必须额外开启keepalive,才能生效。 tcp_keepalive_time:表示多长时间后,开始探测TCP链接是否有效,一般系统默认两小时。 tcp_keepalive_probes:表示如果探测失败的话,会继续探测 9 次。 tcp_keepalive_intvl:tcp_keepalive_probes的探测时间间隔为 75 秒。 如果服务的访问量比较大,建议将tcp_keepalive_time按需设置,比如五分钟,十分钟 6、既然需要开启tcp的keepalive功能,就需要对springcloud gateway中关于tcp的源码进行分析了,在网上搜索gateway tcp相关的源码就可以搜索到TcpServerBind这个类,其实tcp相关的代码都在reactor.netty.tcp这个包下,也很好找,我们看下TcpServerBind这个类,可以发现使用了ChannelOption.SO_REUSEADDR: 这个参数表示允许重复使用本地地址和端口,比如,某个服务器进程占用了TCP的80端口进行监听,此时再次监听该端口就会返回错误,使用该参数就可以解决问题,该参数允许共用该端口,这个在服务器程序中比较常使用,比如某个进程非正常退出,该程序占用的端口可能要被占用一段时间才能允许其他进程使用,而且程序死掉以后,内核一需要一定的时间才能够释放此端口,不设置SO_REUSEADDR就无法正常使用该端口。
static final TcpServerBind INSTANCE = new TcpServerBind();
final TcpServerConfig config;
TcpServerBind() {
Map<ChannelOption<?>, Boolean> childOptions = new HashMap<>(2);
childOptions.put(ChannelOption.AUTO_READ, false);
childOptions.put(ChannelOption.TCP_NODELAY, true);
this.config = new TcpServerConfig(
Collections.singletonMap(ChannelOption.SO_REUSEADDR, true),
childOptions,
() -> new InetSocketAddress(DEFAULT_PORT));
}
TcpServerBind(TcpServerConfig config) {
this.config = config;
}
7、而我们现在配置另一个参数:Channeloption.SO_KEEPALIVE,对应于套接字选项中的SO_KEEPALIVE,服务器会启动定时器去探测该tcp连接的有效性。当设置该选项以后,如果在两小时内没有数据的通信时,TCP会自动发送一个活动探测数据报文。 那在哪里设置这个ChannelOption.SO_KEEPALIVE呢,NettyReactiveWebServerFactory这个类会负责 配置一些启动参数,比如里面的NettyServerCustomizer,字面意思就是netty服务器自定义,那很明显,我可以设置一些自定义属性
public void setServerCustomizers(Collection<? extends NettyServerCustomizer> serverCustomizers) {
Assert.notNull(serverCustomizers, "ServerCustomizers must not be null");
this.serverCustomizers = new LinkedHashSet<>(serverCustomizers);
}
public void addServerCustomizers(NettyServerCustomizer... serverCustomizers) {
Assert.notNull(serverCustomizers, "ServerCustomizer must not be null");
this.serverCustomizers.addAll(Arrays.asList(serverCustomizers));
}
8、再继续看NettyServerCustomizer 这个接口的实现,可以看到继承了函数,并且入参和出参都是HttpServer,
@FunctionalInterface
public interface NettyServerCustomizer extends Function<HttpServer, HttpServer> {
}
9、再继续看 HttpServer,代码如下,在类里面搜索关键字childOption和tcp,我们很容易定位到以下代码,可以看到注释中其实告诉我们怎么设置childOption了
@Deprecated
@SuppressWarnings("ReturnValueIgnored")
public final HttpServer tcpConfiguration(Function<? super TcpServer, ? extends TcpServer> tcpMapper) {
Objects.requireNonNull(tcpMapper, "tcpMapper");
HttpServerTcpConfig tcpServer = new HttpServerTcpConfig(this);
tcpMapper.apply(tcpServer);
return tcpServer.httpServer;
}
10、源码观察到这里,那就把写好的类贴在这里,代码如下:
@Component
public class NettyServerChannelOptionCustomization extends ReactiveWebServerFactoryCustomizer {
public NettyServerChannelOptionCustomization(ServerProperties serverProperties) {
super(serverProperties);
}
@SuppressWarnings("deprecation")
@Override
public void customize(ConfigurableReactiveWebServerFactory factory) {
super.customize(factory);
NettyReactiveWebServerFactory nettyFactory = (NettyReactiveWebServerFactory) factory;
nettyFactory.setResourceFactory(null);
nettyFactory.addServerCustomizers(server -> server
.tcpConfiguration(tcpServer -> tcpServer.childOption(ChannelOption.SO_KEEPALIVE, true)));
}
}
11、将上面代码放在网关代码里面重新部署上线,tcp趋势图如下,部署一段时间后,用户访问开始激增,并且随着时间推移,用户访问量减缓后,可以看到tcp回收很明显,有明显的下降趋势
|