记一次Broken pipe问题的思考

  1. 业务场景
  2. 问题
  3. 猜测思路
  4. 解决过程
    1. Tomcat源码
    2. 超时时间

​ 今天遇到一个Broken pipe的问题,觉得挺有意思的就记录一下,因为这整个过程还是涉及挺多相关知识的(nginx 、tomcat 、内存查看等),例如是为了找到运行机器ip,通过查询nginx的access.log 获取错误机器ip, 收获挺多。

业务场景

​ springmvc 发送请求,根据筛选查询MongoDB ,然后将查询结果导出Execl,响应时间比较久,

然后就返回错误。

问题

Caused by: java.net.SocketException: Broken pipe
   at java.net.SocketOutputStream.socketWrite0(Native Method) ~[?:1.6.0_25]
   at java.net.SocketOutputStream.socketWrite(SocketOutputStream.java:92) ~[?:1.6.0_25]
   at java.net.SocketOutputStream.write(SocketOutputStream.java:136) ~[?:1.6.0_25]
   at org.apache.coyote.http11.InternalOutputBuffer.realWriteBytes(InternalOutputBuffer.java:216) ~[tomcat-coyote.jar:7.0.82]
   at org.apache.tomcat.util.buf.ByteChunk.flushBuffer(ByteChunk.java:487) ~[tomcat-coyote.jar:7.0.82]
   at org.apache.tomcat.util.buf.ByteChunk.append(ByteChunk.java:373) ~[tomcat-coyote.jar:7.0.82]
   at org.apache.coyote.http11.InternalOutputBuffer$OutputStreamOutputBuffer.doWrite(InternalOutputBuffer.java:239) ~[tomcat-coyote.jar:7.0.82]
   at org.apache.coyote.http11.filters.IdentityOutputFilter.doWrite(IdentityOutputFilter.java:93) ~[tomcat-coyote.jar:7.0.82]
   at org.apache.coyote.http11.AbstractOutputBuffer.doWrite(AbstractOutputBuffer.java:192) ~[tomcat-coyote.jar:7.0.82]
   at org.apache.coyote.Response.doWrite(Response.java:506) ~[tomcat-coyote.jar:7.0.82]
   at org.apache.catalina.connector.OutputBuffer.realWriteBytes(OutputBuffer.java:405) ~[catalina.jar:7.0.82]
   ... 67 more

猜测思路

​ 因为在选择当天数据的时候导出是没有问题的,而选择7天的时候就报错了。初步的现状认定就是响应时间长,或者文件过大导致。

猜测一: 查询出来的数据太多,导致内存不足,一直full gc ,导致响应变慢

​ 通过jstat -gcutil pid 命令查看,发现并没有吃满内存,只是young gc 频繁了点。

猜测二: 查询出来的数据太大,tomcat容器有限制

​ 又想是否是导出文件太大,tomcat 会对这个限制,然后去google了一波‘tomcat response body size limit’ 发现并没有相关资料

有的只是tomcat会对post 请求body做限制,默认是2m

Having said that*, if this is the issue, you should have got an exception on the lines of `Post data too big` in tomcat

于是上官网http://tomcat.apache.org/tomcat-7.0-doc/connectors.html#HTTP (从错误信息里获取到的版本是7.0.82) 也没有发现什么说明。(简略翻了一下,或许是没找到)

http://tomcat.apache.org/tomcat-7.0-doc/config/http.html ,(这里可以关注下timeout相关配置,另外也可以看下tomcat的异常定义)

Attribute Description
maxPostSize The maximum size in bytes of the POST which will be handled by the container FORM URL parameter parsing. The limit can be disabled by setting this attribute to a value less than zero. If not specified, this attribute is set to 2097152 (2 megabytes). Note that the FailedRequestFilter can be used to reject requests that exceed this limit.

解决过程

Tomcat源码

于是直接下载源码查看,通过在maven配置

  1.  <dependency>
         <groupId>org.apache.tomcat.embed</groupId>
         <artifactId>tomcat-embed-core</artifactId>
         <version>7.0.82</version>
     </dependency>
    

    然后定位到报错位置org.apache.coyote.http11.InternalOutputBuffer.OutputStreamOutputBuffer#doWrite

    image-20190326161033556

这里的socketBuffer,定义如下

/**
 * Socket buffer.
 */
private ByteChunk socketBuffer;

查看ByteChunk的定义,发现它是封装了一层byte[]

- This class is used to represent a chunk of bytes, and
- utilities to manipulate byte[].

再通过错误信息定位到 org.apache.tomcat.util.buf.ByteChunk#append(byte[], int, int)

image-20190326110116891

然后发现有limit !!

进去看,发现其实只是对limit做了长度限制,并不会导致什么异常,这里limit默认是-1,即没有限制,即使设置了也只是写块的时候限制大小,并不会导致异常。

/**
 * Make space for len chars. If len is small, allocate a reserve space too.
 * Never grow bigger than limit.
 */
public void makeSpace(int count) {
    byte[] tmp = null;

    int newSize;
    int desiredSize=end + count;

    // Can't grow above the limit
    if( limit > 0 &&
        desiredSize > limit) {
        desiredSize=limit;
    }

    if( buff==null ) {
        if( desiredSize < 256 )
         {
            desiredSize=256; // take a minimum
        }
        buff=new byte[desiredSize];
    }

    // limit < buf.length ( the buffer is already big )
    // or we already have space XXX
    if( desiredSize <= buff.length ) {
        return;
    }
    // grow in larger chunks
    if( desiredSize < 2 * buff.length ) {
        newSize= buff.length * 2;
        if( limit >0 &&
            newSize > limit ) {
            newSize=limit;
        }
        tmp=new byte[newSize];
    } else {
        newSize= buff.length * 2 + count ;
        if( limit > 0 &&
            newSize > limit ) {
            newSize=limit;
        }
        tmp=new byte[newSize];
    }

    System.arraycopy(buff, start, tmp, 0, end-start);
    buff = tmp;
    tmp = null;
    end=end-start;
    start=0;
}

再根据错误信息 定位到如下

image-20190326110936819

发现是在 从服务向客户端写buffer的时候出错。于是猜测是由于查询时间过长, 客户端由于超时断开,服务端还在写数据,这个时候就会报出这个错误。

超时时间

  1. 查看客户端的超时时间,使用的是chrom(https://cs.chromium.org/chromium/src/net/socket/client_socket_pool.cc?sq=package:chromium&l=25

image-20190326113723854

​ 默认是5分钟,实际请求并没有怎么长,等待个十秒左右就返回错误了

  1. 于是想到还有个nginx配置,查看发现配置为

keepalive_timeout 8 8;

这里通过查询相关资料发现

keepalive_timeout timeout [header_timeout];

​ 第一个参数:设置keep-alive客户端连接在服务器端保持开启的超时值(默认75s);值为0会禁用keep-alive客户端连接;

​ 第二个参数:可选、在响应的header域中设置一个值“Keep-Alive: timeout=time”;通常可以不用设置;

​ 注:keepalive_timeout默认75s

​ 所以这里的超时时间是8s,于是修改keepalive_timeout为60s后,刷新配置,重新测试,发现可以导出了。


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 951488791@qq.com

文章标题:记一次Broken pipe问题的思考

字数:1.3k

本文作者:zhengyumin

发布时间:2019-03-26, 10:53:37

最后更新:2020-03-08, 23:13:34

原始链接:http://zyumin.github.io/2019/03/26/%E8%AE%B0%E4%B8%80%E6%AC%A1Broken-pipe%E9%97%AE%E9%A2%98%E7%9A%84%E6%80%9D%E8%80%83/

版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。