我在Jetty上运行的java Web服务在几个小时后就崩溃了,调查显示许多套接字处于CLOSE_WAIT状态。当它工作正常时,似乎没有处于CLOSE_WAIT状态的套接字,但当它出错时,会有负载。
我发现了这个定义
CLOSE-WAIT:本地端点已收到连接终止请求并确认,例如已执行被动关闭,本地端点需要执行主动关闭才能离开此状态。
在我的服务器上使用netstat时,我看到了一个处于CLOSE_WAIT状态的tcp套接字列表,本地地址是我的服务器,外部地址是我负载均衡器的机器。因此,我认为这意味着客户端(负载均衡器)刚刚以某种不适当的方式终止了其端部的连接,而我的服务器没有正确地关闭其端部连接。
但是,如果我的Java代码不处理低级套接字,我该怎么做呢?
或者负载平衡器终止连接是因为我的服务器在代码中出错导致的早期问题。
听起来像是Jetty或JVM中的一个错误,也许这个解决方法对您有效:http://www.tux.hk/index.php?entry=entry090521-111844
将以下行添加到/etc/sysctl.conf
net.ipv4.tcp_fin_timeout = 30
net.ipv4.tcp_keepalive_intvl = 2
net.ipv4.tcp_keepalive_probes = 2
net.ipv4.tcp_keepalive_time = 1800
然后执行
sysctl -p
或者重新启动
我怀疑这可能是导致服务器代码中出现长时间或无限循环/无限等待的原因,而Jetty根本没有机会关闭连接(除非有某种超时在一定时间后强制关闭套接字)。考虑以下示例:
public class TestSocketClosedWaitState
{
private static class SocketResponder implements Runnable
{
private final Socket socket;
//Using static variable to control the infinite/waiting loop for testing purposes, with while(true) Eclipse would complain of dead code in writer.close() -line
private static boolean infinite = true;
public SocketResponder(Socket socket)
{
this.socket = socket;
}
@Override
public void run()
{
try
{
PrintWriter writer = new PrintWriter(socket.getOutputStream());
writer.write("Hello");
//Simulating slow response/getting stuck in an infinite loop/waiting something that never happens etc.
do
{
Thread.sleep(5000);
}
while(infinite);
writer.close(); //The socket will stay in CLOSE_WAIT from server side until this line is reached
}
catch(Exception e)
{
e.printStackTrace();
}
System.out.println("DONE");
}
}
public static void main(String[] args) throws IOException
{
ServerSocket serverSocket = new ServerSocket(12345);
while(true)
{
Socket socket = serverSocket.accept();
Thread t = new Thread(new SocketResponder(socket));
t.start();
}
}
}
当infinite
-变量设置为true时,Printwriter(和底层套接字)永远不会因为无限循环而关闭。如果我运行此程序并使用telnet连接到套接字,然后退出telnet客户端,netstat
将显示服务器端套接字仍处于CLOSE_WAIT
-状态(我也可以看到客户端套接字处于FIN_WAIT2状态一段时间,但它会消失):
~$ netstat -anp | grep 12345
tcp6 0 0 :::12345 :::* LISTEN 6460/java
tcp6 1 0 ::1:12345 ::1:34606 CLOSE_WAIT 6460/java
服务器端接受的套接字卡在CLOSE_WAIT状态。如果我检查进程的线程堆栈,我可以看到线程在do…while-loop:中等待
~$ jstack 6460
<OTHER THREADS>
"Thread-0" prio=10 tid=0x00007f424013d800 nid=0x194f waiting on condition [0x00007f423c50e000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
at java.lang.Thread.sleep(Native Method)
at TestSocketClosedWaitState$SocketResponder.run(TestSocketClosedWaitState.java:32)
at java.lang.Thread.run(Thread.java:701)
<OTHER THREADS...>
如果我将infinite
-变量设置为false,并执行同样的操作(连接客户端&断开连接),则CLOSE_WAIT
-状态的套接字将显示,直到编写器关闭(关闭底层套接字),然后消失。如果写入程序或套接字从未关闭,即使线程终止,服务器端套接字也将再次卡在CLOSED_WAIT
中(我认为这不应该发生在Jetty中,如果您的方法在某个时刻返回,Jetty可能应该负责关闭套接字)。
所以,我建议你尝试找出罪魁祸首的步骤是
- 将日志记录添加到方法中,查看它们的进展/正在做什么
- 检查您的代码,是否有任何地方的执行可能会陷入无限循环或花费很长时间,从而阻止底层套接字关闭
- 如果问题仍然存在,请在下次出现此问题时,使用
jstack
从正在运行的Jetty进程中获取线程转储,并尝试识别任何"卡住"的线程 - 是否有可能发生某些抛出的东西(OutOfMemoryError或类似的东西)可能不会被调用您方法的底层Jetty架构捕获?我从来没有窥探过Jetty的内部,它很可能会捕捉到
Throwable
,所以这可能不是问题所在,但如果其他一切都失败了,可能值得检查
当线程进入和退出你的方法时,你也可以用之类的东西来命名线程
String originalName = Thread.currentThread().getName();
Thread.currentThread().setName("myMethod");
//Your code...
Thread.currentThread().setName(originalName);
如果有很多线程在运行,则更容易发现它们。
我们的项目中也有同样的问题。我不确定这是否是你的情况,但也许会有所帮助。
原因是大量的请求是由具有同步块的业务逻辑处理的。因此,当客户端发送数据包以断开连接时,绑定到此套接字的线程正忙于等待监视器。
日志显示了org.eclipse.jety.io.WriteFlusher在写入方法处的异常
DEBUG org.eclipse.jetty.io.WriteFlusher - write - write exception
org.eclipse.jetty.io.EofException: null
at org.eclipse.jetty.io.ChannelEndPoint.flush
(ChannelEndPoint.java:192) ~[jetty-io-9.2.10.v20150310.jar:9.2.10.v20150310]
以及org.eclipse.jety.server.HttpOutput at close方法。我认为关闭步骤中的异常是套接字的close_WAIT状态的原因:
DEBUG org.eclipse.jetty.server.HttpOutput - close -
org.eclipse.jetty.io.EofException: null
at org.eclipse.jetty.server.HttpConnection$SendCallback.reset
(HttpConnection.java:622) ~[jetty-server-9.2.10.v20150310.jar:9.2.10.v20150310]
在我们的案例中,快速的解决方案是增加idleTimeout。正确的解决方案(同样在我们的案例中)是代码重构。
因此,我的建议是仔细阅读具有DEBUG级别的Jetty日志,以发现异常并使用VisualVM分析应用程序性能。原因可能是性能瓶颈(同步块?)。
我遇到了类似的问题,虽然罪魁祸首代码可能不同,但症状是1) 服务器(Jetty)正在运行但未处理请求2) 没有异常负载3) CLOSE_WAIT连接太多。
这表明服务器中的所有工作线程都被卡在了某个地方。Jstack线程转储显示,我们所有的工作线程都被困在apacheHttpClient对象中。(由于未关闭的响应对象),并且由于所有线程都在无限等待,因此没有线程可用于处理传入请求。
负载均衡器是否仍在运行?尝试停止负载平衡器,看看这是否是服务器的问题。
这可能意味着您没有清理传入连接。确保套接字在每个事务结束时都已关闭。(最好在服务器代码开头附近的finally块中完成,这样即使发生服务器端异常,连接也会关闭。)