背景
今天下午遇到同事求助,說是服務端出現了好幾個java.io.IOException: Broken pipe這樣的異常,讓我幫忙看一下,這個問題對於我們做服務端開發的技術人員是很容易遇到的,特此記錄一下。
探究
問題堆棧
org.apache.catalina.connector.ClientAbortException: java.io.IOException: Broken pipe at org.apache.catalina.connector.OutputBuffer.realWriteBytes(OutputBuffer.java:356) at org.apache.catalina.connector.OutputBuffer.flushByteBuffer(OutputBuffer.java:825) at org.apache.catalina.connector.OutputBuffer.append(OutputBuffer.java:730) at org.apache.catalina.connector.OutputBuffer.writeBytes(OutputBuffer.java:391) at org.apache.catalina.connector.OutputBuffer.write(OutputBuffer.java:369) at org.apache.catalina.connector.CoyoteOutputStream.write(CoyoteOutputStream.java:96) at org.springframework.session.web.http.OnCommittedResponseWrapper$SaveContextServletOutputStream.write( OnCommittedResponseWrapper.java:620) at org.springframework.security.web.util.OnCommittedResponseWrapper$SaveContextServletOutputStream.write( OnCommittedResponseWrapper.java:639) at org.springframework.security.web.util.OnCommittedResponseWrapper$SaveContextServletOutputStream.write( OnCommittedResponseWrapper.java:639) at com.fasterxml.jackson.core.json.UTF8JsonGenerator._flushBuffer(UTF8JsonGenerator.java:2085) at com.fasterxml.jackson.core.json.UTF8JsonGenerator._writeBytes(UTF8JsonGenerator.java:1173) at com.fasterxml.jackson.core.json.UTF8JsonGenerator.writeFieldName(UTF8JsonGenerator.java:256) at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:725) at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:719) at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:155) at com.fasterxml.jackson.databind.ser.impl.IndexedListSerializer.serializeContents(IndexedListSerializer.java:119) at com.fasterxml.jackson.databind.ser.impl.IndexedListSerializer.serialize(IndexedListSerializer.java:79) at com.fasterxml.jackson.databind.ser.impl.IndexedListSerializer.serialize(IndexedListSerializer.java:18) at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:727) at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:719) at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:155) at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:727) at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:719) at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:155) at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider._serialize(DefaultSerializerProvider.java:480) at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider.serializeValue(DefaultSerializerProvider.java:319) at com.fasterxml.jackson.databind.ObjectWriter$Prefetch.serialize(ObjectWriter.java:1396) at com.fasterxml.jackson.databind.ObjectWriter.writeValue(ObjectWriter.java:913) at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.writeInternal( AbstractJackson2HttpMessageConverter.java:286) at org.springframework.http.converter.AbstractGenericHttpMessageConverter.write(AbstractGenericHttpMessageConverter.java:102) at org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor.writeWithMessageConverters( AbstractMessageConverterMethodProcessor.java:272) at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.handleReturnValue( RequestResponseBodyMethodProcessor.java:180) at org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite.handleReturnValue( HandlerMethodReturnValueHandlerComposite.java:82) at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle( ServletInvocableHandlerMethod.java:119) at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod( RequestMappingHandlerAdapter.java:877) at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal( RequestMappingHandlerAdapter.java:783) at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:991) at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:925) at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:974) at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:877) at javax.servlet.http.HttpServlet.service(HttpServlet.java:707) at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:851) at javax.servlet.http.HttpServlet.service(HttpServlet.java:790) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
着重關注點
堆棧信息中有一行比較關鍵,在OutputBuffer :: realWriteBytes這個方法中出現了異常,在方法內部拋出了ClientAbortException:
at org.apache.catalina.connector.OutputBuffer.realWriteBytes(OutputBuffer.java:356)
OutputBuffer#realWriteBytes
/** * Sends the buffer data to the client output, checking the * state of Response and calling the right interceptors. * * @param buf the ByteBuffer to be written to the response * * @throws IOException An underlying IOException occurred */ public void realWriteBytes(ByteBuffer buf) throws IOException { if (closed) { return; } if (coyoteResponse == null) { return; } // If we really have something to write if (buf.remaining() > 0) { // real write to the adapter try { coyoteResponse.doWrite(buf); } catch (IOException e) { // An IOException on a write is almost always due to // the remote client aborting the request. Wrap this // so that it can be handled better by the error dispatcher. throw new ClientAbortException(e); } } }
在上面的代碼中,調用coyoteResponse.doWrite(buf);
方法時,會拋出ClientAbortException
異常
我們仔細閱讀以下上述注釋:
An IOException on a write is almost always due to the remote client aborting the request. Wrap this so that it can be handled better by the error dispatcher. 進行寫操作時出現的IOException幾乎總是由於遠程客戶端中止請求而導致的。將其包裝起來,以便錯誤調度程序可以更好地處理它。
結論
有位大神將此問題對應的流程圖畫的很好,特摘錄過來:秋夜無霜
如上圖:
調用方通過restTemplate封裝的http連接池,這時候連接設置connectTimeout=3000,readTimeout=3000,然后網絡請求http請求+端口調用到服務方,然后調用一個api(uri),這時候響應的時候,然后Spring框架對響應進行JSON序列化,然后寫入Socket,這時候調用方(客戶端)突然Socket端中斷連接,而Server端正在進行輸出流寫,導致如上異常問題。
然后針對當前業務場景,看了今天300個請求,有不到10個出現這種問題,超時大多3、4秒樣子,我們通知調用方增大連接池的超時時間,就不再出現這個問題。
最近氣溫變化大,冷暖轉換明顯,由於沒有及時增添衣物,注意保暖,翎野君感染風寒,現在正在調養恢復中,希望大家也多多注意,春捂秋凍。
參考文章