Java 如果一个处理程序卡在无限循环中,Netty不会处理所有输入请求

Java 如果一个处理程序卡在无限循环中,Netty不会处理所有输入请求,java,netty,nio,epoll,Java,Netty,Nio,Epoll,我们在应用程序中遇到了一个bug,在处理协议的过程中,一个处理程序进入了无限循环,被困在channelRead()方法中 然而,这开始导致其他(不是所有,而是一些)新连接在连接的某个地方也被卡住。没有螺纹可见,表明连接卡滞。这会逐渐增加已建立连接的数量,然后新连接最终会出现连接困难并开始超时 为什么无限循环中的channelRead中有一个线程会阻塞任何其他连接(大约有32个线程可供处理)?我确认,一旦线程继续,所有卡住的连接将恢复 我用这个简单的例子复制了这个行为: AppServer.jav

我们在应用程序中遇到了一个bug,在处理协议的过程中,一个处理程序进入了无限循环,被困在channelRead()方法中

然而,这开始导致其他(不是所有,而是一些)新连接在连接的某个地方也被卡住。没有螺纹可见,表明连接卡滞。这会逐渐增加已建立连接的数量,然后新连接最终会出现连接困难并开始超时

为什么无限循环中的channelRead中有一个线程会阻塞任何其他连接(大约有32个线程可供处理)?我确认,一旦线程继续,所有卡住的连接将恢复

我用这个简单的例子复制了这个行为:

AppServer.java:

导入io.netty.bootstrap.ServerBootstrap;
导入io.netty.channel.ChannelFuture;
导入io.netty.channel.ChannelOption;
导入io.netty.channel.EventLoopGroup;
导入io.netty.channel.epoll.EpollEventLoopGroup;
导入io.netty.channel.epoll.epollserverssocketchannel;
公共类AppServer{
专用静态最终int HTTP_端口=8080;
public void run()引发异常{
EventLoopGroup bossGroup=new-EpollEventLoopGroup();
EventLoopGroup workerGroup=新的EPolleEventLoopGroup();
试一试{
ServerBootstrap httpBootstrap=新的ServerBootstrap();
httpBootstrap
.group(bossGroup、workerGroup)
.channel(epollserverssocketchannel.class)
.childHandler(新的服务器初始值设定项())
.option(ChannelOption.SO_BACKLOG,512)
.childOption(ChannelOption.SO_KEEPALIVE,true);
//绑定并开始接受传入连接。
ChannelFuture-httpChannel=httpBootstrap.bind(HTTP_-PORT.sync();
//等待服务器套接字关闭
httpChannel.channel().closeFuture().sync();
}
最后{
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
公共静态void main(字符串[]args)引发异常{
新建AppServer().run();
}
}
ServerHandler.java

导入io.netty.buffer.ByteBuf;
导入io.netty.buffer.Unpooled;
导入io.netty.channel.ChannelHandlerContext;
导入io.netty.channel.SimpleChannelInboundHandler;
导入io.netty.handler.codec.http.*;
导入io.netty.util.CharsetUtil;
公共类ServerHandler扩展了SimpleChannelInboundHandler{
公共静态整数计数=0;
@凌驾
受保护的无效channelRead0(ChannelHandlerContext ctx、FullHttpRequest消息){
如果(计数=0){
计数++;
while(true){}
}
ByteBuf content=unmooled.copiedBuffer(“Hello World!”,CharsetUtil.UTF_8);
FullHttpResponse response=new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,HttpResponseStatus.OK,content);
response.headers().set(HttpHeaderNames.CONTENT_TYPE,“text/html”);
response.headers().set(HttpHeaderNames.CONTENT_LENGTH,CONTENT.readableBytes());
写(应答);
ctx.flush();
计数++;
}
}
ServerInitializer.java:

导入io.netty.channel.channel;
导入io.netty.channel.ChannelInitializer;
导入io.netty.channel.ChannelPipeline;
导入io.netty.handler.codec.http.HttpObjectAggregator;
导入io.netty.handler.codec.http.HttpServerCodec;
公共类ServerInitializer扩展了ChannelInitializer{
@凌驾
受保护的通道(通道ch){
ChannelPipeline=通道管道();
addLast(新的HttpServerCodec());
addLast(新的HttpObjectAggregator(Integer.MAX_值));
addLast(新的ServerHandler());
}
}
我只使用xargs和curl
curl-vhttp://[ip]:8080运行许多连接

一段时间后,它会进入超时失败的状态:

nc -vz [ip] 8080
Ncat: Version 7.50 ( https://nmap.org/ncat )
Ncat: Connection timed out.
这在环回接口上没有显示

如果没有线程被卡住,netty就不会在相同的测试中遇到这个问题。它正在处理所有请求,没有连接被卡住

我也尝试过使用Nio。同样的结果

净额4.1

已建立的连接卡滞:

netstat -n | grep 8080 |  sed -E 's/[[:space:]]+/ /g' | cut -d' ' -f 6 | sort | uniq -c
  14551 ESTABLISHED
   6839 TIME_WAIT
卡住的连接如下所示:

curl -v http://localhost:8080
* Rebuilt URL to: http://localhost:8080/
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET / HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.61.1
> Accept: */*
>
连接正常时的tcp转储:

00:25:35.135929 IP 10.94.158.96.50192 > 10.200.154.102.8080: Flags [S], seq 648221383, win 65340, options [mss 1210,nop,wscale 8,nop,nop,sackOK], length 0
00:25:35.135993 IP 10.200.154.102.8080 > 10.94.158.96.50192: Flags [S.], seq 167219764, ack 648221384, win 35844, options [mss 8961,nop,nop,sackOK,nop,wscale 8], length 0
00:25:35.174362 IP 10.94.158.96.50192 > 10.200.154.102.8080: Flags [.], ack 1, win 515, length 0
00:25:35.176419 IP 10.94.158.96.50192 > 10.200.154.102.8080: Flags [P.], seq 1:84, ack 1, win 515, length 83
00:25:35.176440 IP 10.200.154.102.8080 > 10.94.158.96.50192: Flags [.], ack 84, win 140, length 0
00:25:35.177307 IP 10.200.154.102.8080 > 10.94.158.96.50192: Flags [P.], seq 1:77, ack 84, win 140, length 76
00:25:35.216609 IP 10.94.158.96.50192 > 10.200.154.102.8080: Flags [F.], seq 84, ack 77, win 514, length 0
00:25:35.216856 IP 10.200.154.102.8080 > 10.94.158.96.50192: Flags [F.], seq 77, ack 85, win 140, length 0
00:25:35.254134 IP 10.94.158.96.50192 > 10.200.154.102.8080: Flags [.], ack 78, win 514, length 0
连接卡住时的tcp转储:

00:25:38.409177 IP 10.94.158.96.50193 > 10.200.154.102.8080: Flags [S], seq 8750522, win 65340, options [mss 1210,nop,wscale 8,nop,nop,sackOK], length 0
00:25:38.409254 IP 10.200.154.102.8080 > 10.94.158.96.50193: Flags [S.], seq 1214051234, ack 8750523, win 35844, options [mss 8961,nop,nop,sackOK,nop,wscale 8], length 0
00:25:38.446641 IP 10.94.158.96.50193 > 10.200.154.102.8080: Flags [.], ack 1, win 515, length 0
00:25:38.449108 IP 10.94.158.96.50193 > 10.200.154.102.8080: Flags [P.], seq 1:84, ack 1, win 515, length 83
00:25:38.449141 IP 10.200.154.102.8080 > 10.94.158.96.50193: Flags [.], ack 84, win 140, length 0
00:25:39.535154 IP 10.94.158.96.50193 > 10.200.154.102.8080: Flags [.], seq 83:84, ack 1, win 515, length 1
00:25:39.535211 IP 10.200.154.102.8080 > 10.94.158.96.50193: Flags [.], ack 84, win 140, options [nop,nop,sack 1 {83:84}], length 0
00:25:40.641378 IP 10.94.158.96.50193 > 10.200.154.102.8080: Flags [.], seq 83:84, ack 1, win 515, length 1
00:25:40.641404 IP 10.200.154.102.8080 > 10.94.158.96.50193: Flags [.], ack 84, win 140, options [nop,nop,sack 1 {83:84}], length 0
00:25:41.741142 IP 10.94.158.96.50193 > 10.200.154.102.8080: Flags [.], seq 83:84, ack 1, win 515, length 1
00:25:41.741199 IP 10.200.154.102.8080 > 10.94.158.96.50193: Flags [.], ack 84, win 140, options [nop,nop,sack 1 {83:84}], length 0
00:25:42.844891 IP 10.94.158.96.50193 > 10.200.154.102.8080: Flags [.], seq 83:84, ack 1, win 515, length 1
00:25:42.844947 IP 10.200.154.102.8080 > 10.94.158.96.50193: Flags [.], ack 84, win 140, options [nop,nop,sack 1 {83:84}], length 0
00:25:44.035849 IP 10.94.158.96.50193 > 10.200.154.102.8080: Flags [.], seq 83:84, ack 1, win 515, length 1
00:25:44.035869 IP 10.200.154.102.8080 > 10.94.158.96.50193: Flags [.], ack 84, win 140, options [nop,nop,sack 1 {83:84}], length 0
00:25:45.135646 IP 10.94.158.96.50193 > 10.200.154.102.8080: Flags [.], seq 83:84, ack 1, win 515, length 1
00:25:45.135702 IP 10.200.154.102.8080 > 10.94.158.96.50193: Flags [.], ack 84, win 140, options [nop,nop,sack 1 {83:84}], length 0
... repeats ...

这很有道理。。。Netty使用了
EventLoop
的概念,这意味着它在一个循环中处理任务和IO。这里重要的一点是,Netty使用非阻塞IO,这意味着它永远不会阻塞IO,因此可以用一个线程处理多个连接。这允许仅使用少量线程处理1M+连接。这就是说,这也意味着如果你“阻止”一个线程(基本上是用无限循环来阻止的),你也会影响这一个线程处理的所有其他连接。

我知道,基于这一点,该线程还与exec组线程“耦合”:这就是为什么将其添加到管道中不起作用的原因。我不明白的是,我认为
bossGroup
应该处理数据的“接受”和
workerGroup
处理。然而,随着一个
工作组
被阻塞,系统最终无法接受任何新连接。如果
bossGroup
接受,并且它的线程都没有被阻止,为什么它在一段时间后不能接受新的连接?它接受连接,但一旦连接被接受,它将尝试在workerGroup上注册这些连接,而workerGroup本身可能被阻止。但我不明白为什么bossGroup的线程会被卡住。此外,这也不能解释为什么以后连接根本不能被接受,并且任何到该端口的连接都会出现“超时”