服务器出现大量close_wait,我们来说说到底是怎么回事?(以tomcat为例)

本文围绕服务器出现大量close_wait状态连接的问题展开。先介绍后端服务及客户端情况,分析问题可能是服务端接口耗时久,客户端超时断开连接所致。通过抓包测试验证猜测,最后提出治本方法是检查代码,找出耗时原因。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、问题描述

最近一直忙得很,好久没写博客。前两天,微信收到个好友申请,说是想问问close_wait的事情。

 

找他问了些详细信息,大概了解到,他们后端服务是tomcat 7, jdk 7,centos,传统的spring + hibernate + spring mvc 结构。

业务不清楚,客户端主要是微信小程序。

目前的症状就是,服务器上有大量的close_wait状态的连接,在他们的服务器上执行 netstat 命令,如下图:

 

从上图看出,他们的服务器ip为 172.18.206.252(反正是内网ip,不用打码了吧),端口是443,那应该就是https服务了。

客户端ip没有重样的,应该都是些全国各地的ip了。

close_wait的危害在于,在一个端口上打开的文件描述符超过一定数量,(在linux上默认是1024,可修改),新来的socket连接就无法建立了。

会报:Too many open files。

 

二、问题分析

我回想了一下,之前在博客园上是写了一篇close_wait相关的,叫:tcp连接出现close_wait状态?可能是代码不够健壮

在那篇文章里,虽然我那也是服务端出现的,但是服务端其实是作为客户端,去调用大数据服务。严格来说,和今天遇到的场景不一样:

1、之前博客里的场景:服务端调用大数据,大数据关闭连接,(发起fin,服务器回ack)。此时,因为代码不严谨,服务端没有再向大数据发起close请求,

所以服务端与大数据的连接,在服务端上表现为close_wait。在大数据那边,状态应该是属于FIN_WAIT_2。参考下图:

 

 2、这次遇到的场景:是作为小程序的客户端访问了服务器,服务器不知道为啥处于close_wait。

所以,一开始我也没有思路,网上查了下,有人说是tomcat 的https有bug,更多的直接教你怎么用运维手段解决。

 

后来,这个朋友提到,他们服务器的一个接口,是会去调用微信服务器,生成二维码,而且,他们的监控显示,该方法耗时较长。

这时,我想到一个问题是:如果服务端在处理过程中,耗时较长,(进入死循环、等锁、下游服务响应慢等),假设20s才返回,

但是客户端明显不可能等那么久,一般5-10s就超时了。超时了,客户端发起fin,服务器回ack,此时,

服务器端应该就是close_wait。

 

在网上搜索时,也发现网上其实有这方面案例了,比如:

一次服务端大量CLOSE_WAIT问题的解决

 

我大概率估计,就是这个原因。但猜测只是猜测,还是要实践一下。

 

三、验证猜测

1、准备工作

我这边的一台开发服务器是windows的,装了wireshark,方便抓包,上面装了tomcat 8.5,一会直接把war包丢进去跑就完了。

我的打算是,修改目前工程的一个controller接口,让其睡眠30s再返回。 客户端的话,我用了httpclient,写了个测试类,直接去调用服务器的controller接口。

然后,用netstat观察该连接的状态变化,同时,wireshark辅助查看网络包的发送情况。

 

controller代码如下:

 

客户端代码:

pom.xml加上:

            <dependency>
                <groupId>org.apache.httpcomponents</groupId>
                <artifactId>httpclient</artifactId>
                <version>4.5.3</version>
            </dependency>

 

工具类如下:

import com.alibaba.fastjson.JSON;
import com.ceiec.base.common.utilities.AppConstants;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.time.StopWatch;
import org.apache.http.HttpEntity;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicHeader;
import org.apache.http.protocol.HTTP;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.HashMap;

public final class MyHttpUtils {



    private static int TIMEOUT = 5000;

    private static final String APPLICATION_JSON = "application/json";

    private static final String CONTENT_TYPE_TEXT_JSON = "text/json";

    private static Logger logger = LoggerFactory.getLogger(MyHttpUtils.class);

    /**
     * POST方式提交请求
     * 
     * @param url 请求地址
     * @param json JSON格式请求内容
     * @throws IOException 
     */
    public static String doPost(String url, String json){
        if (json == null) {
            HashMap<String, String> map = new HashMap<>();
            json = JSON.toJSONString(map);
        }
        //计时
        StopWatch timer = new StopWatch();
        timer.start();

        RequestConfig defaultRequestConfig = RequestConfig.custom().setSocketTimeout(TIMEOUT).setConnectTimeout(TIMEOUT).setConnectionRequestTimeout(TIMEOUT).build();
        CloseableHttpClient httpClient = HttpClients.custom().setDefaultRequestConfig(defaultRequestConfig).build();
        HttpPost httpPost = new HttpPost(url);
        httpPost.addHeader(HTTP.CONTENT_TYPE, APPLICATION_JSON);
        StringEntity stringEntity = new StringEntity(json, AppConstants.UTF8);
        stringEntity.setContentType(CONTENT_TYPE_TEXT_JSON);
        stringEntity.setContentEncoding(new BasicHeader(HTTP.CONTENT_TYPE, APPLICATION_JSON));
        httpPost.setEntity(stringEntity);
        httpPost.setConfig(defaultRequestConfig);

        CloseableHttpResponse response = null;
        String responseContent = "";
        try {
            response = httpClient.execute(httpPost);
            int status = response.getStatusLine().getStatusCode();
            logger.debug("response status: " + status);
            if (status >= 200 && status < 300) {
                HttpEntity entity = response.getEntity();
                if (entity == null) {
                    return null;
                }
                responseContent = EntityUtils.toString(entity,"UTF-8");
                return responseContent;
            } else {
                throw new ClientProtocolException("Unexpected response status: " + status);
            }
        } catch (Exception e) {
            logger.error("error occured.{}",e);
            throw new RuntimeException(e);
        } finally {
            timer.stop();
            logger.info("doPost. requestUrl:{}, param:{},response:{},took {} ms", url,json,responseContent,timer.getTime());

            IOUtils.closeQuietly(response);
            IOUtils.closeQuietly(httpClient);
        }
    }


}

 

Test类:

public class Test {
    public static void main(String[] args) {
            MyHttpUtils.doPost("http://192.168.19.94:8080/CAD_WebService/getValue.do",null);
    }
}

 

好了,在正式开始之前,说下MyHttpUtils中标红的那个方法:RequestConfig.custom().setSocketTimeout(TIMEOUT) 这个setSocketTimeout表示设置等待服务器端响应超时的时间,这里设为5000,意为5s

 

 

2.测试验证

这里,准备就绪了,马上开始,下面是我这边的抓包:

 

这个图,咱们先看抓包:

这个包,是在服务器192.168.19.94上抓的。抓的是服务器上8080端口,和我本地pc 10.15.4.46之间的网络包。

我们分析下:

序号为1/2/3的包: 三次握手,建立连接;

序号4的包:发起http请求,请求的controller方法,会睡眠30s

序号5的包:对序号4的包的ack。注意,此时时间为14:02:11

序号6的包:此时时间已经过去5s,客户端等不及了,(就你猴急?),于是不耐烦了,老子不等了,发了个fin过来,要分手。

序号7的包:服务器说:要分手?知道了。

 

此时服务器在干嘛,不好意思,要睡30s,这时才睡了5s,还没醒。

 

说完了包,我们再看看那个cmd,里面展示的是8080端口上的连接,可以看出来,此时该连接正处于close_wait状态。

 

。。。

25s后。。。

25s后,服务器终于醒了,睡得不错,给客户端发响应吧(序号8),但是呢,9号包可以看出来,我的开发机给服务器回了rst。

意思就是:我不认识你。(因为前面我的开发机就提了分手。。。)

 

3.gif图完整回放

由于是一边写一边截图的,所以有些图等写完了再想看的时候,已经没有了。

 

下面截个完整的,这里,把客户端超时改为20s,方便查看:

1、服务端视角,可以看到,超时前,为established,超时后,为close_wait。

 

2.客户端视角

 

 

四、说到底,问题怎么解决

扯了那么多,服务器出现大量close_wait,到底怎么解决? 指标不治本的方法我就不说了,直接网上搜下,改改linux参数即可。

这篇博客主要是治本,讲了close_wait出现的一种情况:

服务端接口耗时较长,客户端主动断开了连接,此时,服务端就会出现 close_wait。

 

那怎么解决呢?看看代码为啥耗时长吧。

另外,如果代码不规范的话,说不定在收到对方发起的fin后,自己根本就不会给人家发fin。(比如netty自己开发的框架那种)

没啥好说的,检查自己的代码吧,反正close_wait基本就是自己这边的问题了。

如果觉得有点帮助,麻烦点个推荐哈。

 

ps:我这里用chrome测过,用fiddler的composer也搞过,发现有些客户端会一直等响应,过了很久才会主动去发fin,所以用了httpclient测试。

pps:tomcat在什么情况会主动发起fin?其实我也想讲讲,因为和http的connection:keep-alive这些,都有点关系,放这篇文章的话,主题就有点不集中,放下篇吧。

转载于:https://www.cnblogs.com/grey-wolf/p/10936657.html

<think>好的,我需要解决用户关于Linux服务器上出现大量CLOSE_WAIT状态的问题。首先,我得回顾一下TCP连接的状态转换过程,尤其是CLOSE_WAIT和TIME_WAIT的区别。根据引用[5],CLOSE_WAIT出现在被动关闭连接的一方,也就是服务器在接收到客户端的FIN报文后进入这个状态,等待应用程序关闭连接。而TIME_WAIT则是主动关闭连接的一方在发送最后一个ACK后进入的状态,用来处理可能延迟的报文。 用户的问题是服务器上有大量CLOSE_WAIT,这说明服务器端的应用程序没有正确关闭连接。引用[4]提到,检测到对端关闭后,node.js会自动关闭本端socket,但如果应用程序没有执行close操作,就会导致CLOSE_WAIT堆积。因此,根本原因可能在于代码中没有正确释放连接资源。 接下来,我需要整理解决方案的步骤。首先,应该检查应用程序代码,确保所有socket连接在使用后正确关闭,特别是数据库连接、文件句柄等资源。引用[3]指出服务端使用短连接时,主动关闭连接导致TIME_WAIT,但这里的问题不同,是CLOSE_WAIT,所以重点在代码层面的资源释放。 然后,查看系统参数,虽然引用[2]提到TIME_WAIT可以通过优化服务器参数解决,但CLOSE_WAIT更多是应用程序的问题,系统参数调整可能有限,但可以检查文件描述符限制,比如ulimit -n的设置,避免资源耗尽。 接下来,网络排查。使用netstat或ss命令查看具体连接情况,确认是哪些进程导致CLOSE_WAIT,结合lsof或/proc文件系统定位到具体进程和代码位置。如,使用netstat -ae | grep CLOSE_WAIT,类似引用[1]中的命令,但替换为CLOSE_WAIT状态。 另外,引用[3]提到服务端主动关闭连接导致TIME_WAIT,但这里的情况是服务端被动关闭,所以需要确保应用程序在收到FIN后及时close。如果是HTTP服务,检查是否使用了Keep-Alive,适当调整超时时间,避免连接长时间未关闭。 可能还需要使用压力测试工具模拟高并发场景,观察CLOSE_WAIT的增长情况,验证修复措施是否有效。同时,监控工具如Prometheus或Grafana可以帮助实时监控连接状态变化。 最后,总结解决方案步骤,从代码检查、资源释放、系统参数、网络排查到监控优化,逐步排除问题。需要提醒用户,CLOSE_WAIT问题通常由应用程序未正确关闭连接引起,与TIME_WAIT不同,后者更多是系统参数调整的问题,如引用[2]和[3]所述。 需要确保回答结构清晰,分步骤说明,并引用相关参考资料。同时,生成的相关问题要围绕TCP状态、区别、其他状态的解决方案等。</think>### 处理Linux服务器大量CLOSE_WAIT连接的解决方案 #### 1. **理解CLOSE_WAIT状态的本质** CLOSE_WAIT是TCP连接关闭过程中的一个中间状态,表示**被动关闭方(通常是服务器)已收到主动关闭方(客户端)的FIN报文**,但尚未关闭本端连接[^5]。大量CLOSE_WAIT连接的堆积表明**应用程序未正确关闭socket连接**,导致资源无法释放[^4]。 --- #### 2. **排查与解决步骤** ##### **步骤1:检查应用程序代码** - **核心问题**:未显式调用`close()`方法释放连接。 - **常见场景**: - 数据库连接未关闭 - HTTP请求未关闭响应流 - 文件句柄泄漏 - **代码修复示**(以Python为): ```python # 错误示:未关闭socket def handle_request(client_socket): data = client_socket.recv(1024) # 处理请求但未关闭连接 # 正确示:显式关闭连接 def handle_request(client_socket): try: data = client_socket.recv(1024) # 处理请求 finally: client_socket.close() # 确保关闭 ``` ##### **步骤2:检查系统资源限制** - 使用命令查看当前进程打开的文件描述符数量: ```bash lsof -p <进程PID> | wc -l ``` - 调整系统文件描述符限制(临时生效): ```bash ulimit -n 65535 # 修改为更高限制 ``` ##### **步骤3:网络连接分析** - 使用`netstat`或`ss`命令定位问题: ```bash netstat -antop | grep CLOSE_WAIT # 查看所有CLOSE_WAIT连接 ss -s | grep CLOSE_WAIT # 更高效的统计方式 ``` - 结合`lsof`追踪具体进程: ```bash lsof -i :<端口号> # 查看占用端口的进程 ``` ##### **步骤4:优化连接管理策略** - **HTTP服务场景**: - 禁用不必要的Keep-Alive:减少长连接占用时间。 - 设置合理的超时时间(如Nginx配置): ```nginx keepalive_timeout 5; # 5秒后关闭空闲连接 ``` --- #### 3. **对比CLOSE_WAIT与TIME_WAIT的差异** | 状态 | 触发方 | 原因 | 解决方案 | |------------|----------|-------------------------------|------------------------------| | CLOSE_WAIT | 被动关闭 | 应用程序未关闭连接 | 修复代码逻辑 | | TIME_WAIT | 主动关闭 | 系统等待延迟报文 | 调整`tcp_tw_reuse`等内核参数[^3] | --- #### 4. **监控与验证** - **实时监控命令**: ```bash watch -n 1 "netstat -ant | grep CLOSE_WAIT | wc -l" # 每秒统计CLOSE_WAIT数量 ``` - **验证修复效果**: - 重启应用后观察CLOSE_WAIT是否减少。 - 使用压力测试工具(如`ab`、`wrk`)模拟高并发场景。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值