今天遇到一个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配置
<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
这里的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)
然后发现有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;
}
再根据错误信息 定位到如下
发现是在 从服务向客户端写buffer的时候出错。于是猜测是由于查询时间过长, 客户端由于超时断开,服务端还在写数据,这个时候就会报出这个错误。
超时时间
- 查看客户端的超时时间,使用的是chrom(https://cs.chromium.org/chromium/src/net/socket/client_socket_pool.cc?sq=package:chromium&l=25)
默认是5分钟,实际请求并没有怎么长,等待个十秒左右就返回错误了
- 于是想到还有个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