服務響應時間過長

同事反映線上服務特別慢,訪問接口一直無響應,直到504網關超時。上線趕緊看了看CPU以及內存使用率,發現CPU、內存正常。

查看服務日誌有大量斷開的管道:

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.ExceptionHandlerExceptionResolver.doResolveHandlerMethodException(ExceptionHandlerExceptionResolver.java:400)
    at org.springframework.web.servlet.handler.AbstractHandlerMethodExceptionResolver.doResolveException(AbstractHandlerMethodExceptionResolver.java:61)
    at org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver.resolveException(AbstractHandlerExceptionResolver.java:139)
    at org.springframework.web.servlet.DispatcherServlet.processHandlerException(DispatcherServlet.java:1255)
    at org.springframework.web.servlet.DispatcherServlet.processDispatchResult(DispatcherServlet.java:1062)
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1008)
    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.doGet(FrameworkServlet.java:866)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:635)
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:851)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:742)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at java.lang.Thread.run(Thread.java:748)
Caused by: java.io.IOException: 斷開的管道
    at sun.nio.ch.FileDispatcherImpl.write0(Native Method)
    at sun.nio.ch.SocketDispatcher.write(SocketDispatcher.java:47)
    at sun.nio.ch.IOUtil.writeFromNativeBuffer(IOUtil.java:93)
    at org.apache.catalina.connector.OutputBuffer.doFlush(OutputBuffer.java:318)
    ... 48 common frames omitted

當前tomcat使用nio模型處理網絡請求,斷開管道(Broken pipe)原因是當客戶端發起請求,由於服務端迴應慢或者其他原因,服務端返回響應的時候,客戶端已經斷開了連接。但是服務端仍然向斷開的連接返回數據,會產生這個異常。

查看nginx日誌,大量的連接超時:

2019/12/27 17:10:56 [error] 8937#0: *3282524 connect() failed (110: Connection timed out) while connecting to upstream, client: ****.****.****.****, server: ****.com, request: "POST /***/*** HTTP/1.1", upstream: "http://****:****/****/****", host: "****:****"

這種情況不能單純增加各階段的超時時間或增大句柄數解決,因爲沒有定位真正原因,但是修改句柄數量治標不治本。

找原因,打印線程狀態,如下:

"http-nio-8090-exec-4" #39 daemon prio=5 os_prio=0 tid=0x00007f807443a800 nid=0x1fc3 runnable [0x00007f7f987d4000]
   java.lang.Thread.State: RUNNABLE
    at sun.nio.ch.FileDispatcherImpl.read0(Native Method)
    at sun.nio.ch.SocketDispatcher.read(SocketDispatcher.java:39)
    at sun.nio.ch.IOUtil.readIntoNativeBuffer(IOUtil.java:223)
    at sun.nio.ch.IOUtil.read(IOUtil.java:197)
    at sun.nio.ch.SocketChannelImpl.read(SocketChannelImpl.java:380)
    - locked <0x00000007b95b3dc8> (a java.lang.Object)
    at oracle.net.nt.TimeoutSocketChannel.read(TimeoutSocketChannel.java:144)
    at oracle.net.ns.NIOHeader.readHeaderBuffer(NIOHeader.java:82)
    at oracle.net.ns.NIOPacket.readFromSocketChannel(NIOPacket.java:139)
    at oracle.net.ns.NIOPacket.readFromSocketChannel(NIOPacket.java:101)
    at oracle.net.ns.NIONSDataChannel.readDataFromSocketChannel(NIONSDataChannel.java:80)
    at oracle.jdbc.driver.T4CMAREngineNIO.prepareForReading(T4CMAREngineNIO.java:98)
    at oracle.jdbc.driver.T4CMAREngineNIO.unmarshalUB1(T4CMAREngineNIO.java:534)
    at oracle.jdbc.driver.T4CTTIfun.receive(T4CTTIfun.java:485)
    at oracle.jdbc.driver.T4CTTIfun.doRPC(T4CTTIfun.java:252)
    at oracle.jdbc.driver.T4C8Oall.doOALL(T4C8Oall.java:612)
    at oracle.jdbc.driver.T4CPreparedStatement.doOall8(T4CPreparedStatement.java:226)
    at oracle.jdbc.driver.T4CPreparedStatement.doOall8(T4CPreparedStatement.java:59)
    at oracle.jdbc.driver.T4CPreparedStatement.executeForDescribe(T4CPreparedStatement.java:747)
    at oracle.jdbc.driver.OracleStatement.executeMaybeDescribe(OracleStatement.java:904)
    at oracle.jdbc.driver.OracleStatement.doExecuteWithTimeout(OracleStatement.java:1082)
    at oracle.jdbc.driver.OraclePreparedStatement.executeInternal(OraclePreparedStatement.java:3780)
    at oracle.jdbc.driver.T4CPreparedStatement.executeInternal(T4CPreparedStatement.java:1343)
    at oracle.jdbc.driver.OraclePreparedStatement.executeQuery(OraclePreparedStatement.java:3822)
    - locked <0x00000007b81b8518> (a oracle.jdbc.driver.T4CConnection)

發現有大量Oracle連接相關,且狀態爲RUNABLE,線程dump有locked字樣,但是這種情況查看oracle數據庫是否有死鎖,不會發現死鎖。

在當前例子中,java只能檢測到java程序中的鎖,例如synchronized以及線程wait等,它對數據庫中的鎖一無所知。同樣,數據庫能檢測到數據庫實例中產生的鎖,它對java線程的鎖一無所知。java鎖和數據庫中的鎖是兩個完全不同的鎖。

在本例線程dump中,表示在地址爲0x00000007b81b8518的T4CConnection類型的對象上持有一個鎖,只意味着這個線程正在某個連接中執行代碼。不表示當前線程被其他線程阻塞,否則當前線程狀態應該是WAITING或BLOCKED,不是RUNABLE。它正在運行並從網絡套接字讀取來自數據庫的響應內容。

分析:數據庫中SQL執行時間過長,且被大量調用,最終定位到了SQL,因此線上表數據量巨大,執行一次耗時難以忍受的長,而且這類接口在大屏上每隔幾秒就刷新一次,導致前幾次的接口還沒響應,後幾次的請求又過來了,項目線程被耗盡,沒有多餘資源去響應登陸等請求,最終造成504錯誤。

其餘請求打過來之後,因爲服務端遲遲不能響應,最終客戶端關閉了連接,但是服務器端沒有關閉連接,服務器端造成大量CLOSE_WAIT的狀態線程,出現斷開的連接錯誤。如果句柄設置過少,還會導致句柄數限制錯誤。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章