今天公司技術支持的童鞋報告一個客戶的服務不工作了,緊急求助,於是遠程登陸上服務器排查問題。
查看采集數據的tomcat日志,習慣性的先翻到日志的最后去查看有沒有異常的打印,果然發現了好幾種異常信息,但是最多還是這個:
24-Nov-2016 09:54:21.116 SEVERE [http-nio-8081-Acceptor-0] org.apache.tomcat.util.net.NioEndpoint$Acceptor.run Socket accept failed
java.io.IOException: Too many open files
at sun.nio.ch.ServerSocketChannelImpl.accept0(Native Method)
at sun.nio.ch.ServerSocketChannelImpl.accept(ServerSocketChannelImpl.java:241)
at org.apache.tomcat.util.net.NioEndpoint$Acceptor.run(NioEndpoint.java:688)
at java.lang.Thread.run(Thread.java:745)
“Too manay open files” 問題很明顯啊,文件描述符超出限制導致無法打開文件或創建網絡連接,這個問題又會導致一些其它問題的產生,肯定是ulimit沒有優化,於是檢查ulimit的設置;
[root@sdfassd logs]# ulimit -a
core file size (blocks, -c) 0
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 62819
max locked memory (kbytes, -l) 64
max memory size (kbytes, -m) unlimited
open files (-n) 65535
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 10240
cpu time (seconds, -t) unlimited
max user processes (-u) 62819
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited
open files竟然是65535,已經做過了優化,是不是先啟動的tomcat等服務,然后才對ulimit做的優化?有可能,這樣的話重啟一下服務就ok了,於是將全部服務重啟了一遍,運行正常了,不一會報表就顯示數據了,然后告訴技術支持,問題已經解決了,然后就去處理別的case了;
結果還不到20分鍾,技術支持說,報表又沒有數據了,於是又打數據采集的應用的tomcat日志查看,發現了一堆異常,全都是一個錯:
24-Nov-2016 09:54:24.574 WARNING [http-nio-18088-exec-699] org.apache.catalina.core.StandardHostValve.throwable Exception Processing ErrorPage[exceptionType=java.lang.Throwable, location=/views/error/500.jsp]
org.apache.catalina.connector.ClientAbortException: java.io.IOException: Broken pipe
at org.apache.catalina.connector.OutputBuffer.realWriteBytes(OutputBuffer.java:393)
at org.apache.tomcat.util.buf.ByteChunk.flushBuffer(ByteChunk.java:426)
at org.apache.catalina.connector.OutputBuffer.doFlush(OutputBuffer.java:342)
at org.apache.catalina.connector.OutputBuffer.close(OutputBuffer.java:295)
at org.apache.catalina.connector.Response.finishResponse(Response.java:453)
at org.apache.catalina.core.StandardHostValve.throwable(StandardHostValve.java:378)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:174)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79)
at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:610)
at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:610)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:88)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:537)
at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1085)
at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:658)
at org.apache.coyote.http11.Http11NioProtocol$Http11ConnectionHandler.process(Http11NioProtocol.java:222)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1556)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1513)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:745)
這個異常非常多,看報錯信息,是tomcat的connector在執行寫操作的時候發生了Broken pipe異常,connector是tomcat處理網絡請求的,難道是網絡出問題了,但是為什么發生異常的都是寫,讀就沒問題呢?為了判斷是不是網絡問題,於是用wget命令在本地訪問了一下服務器的一個接口,結果發現等了好久都沒有響應,正常情況下應該是馬上就有響應的,這說明不是網絡的原因,是服務器的問題,又用命令查看了下當前tcpip連接的狀態:
[root@sdfassd logs]# netstat -n | awk '/^tcp/ {++state[$NF]} END {for(key in state) print key,"\t",state[key]}'
CLOSE_WAIT 3853
TIME_WAIT 40
ESTABLISHED 285
LAST_ACT 6
CLOSE_WAIT 狀態的連接竟然有3853個,這太不正常了,這說明是客戶端先關閉了連接,服務器端沒有執行關閉連接的操作,導致服務器端一直維持在CLOSE_WAIT的狀態,如果不對操作系統的keepalive做優化,這個狀態默認會維持兩個小時,查看了下系統的設置:
[root@sdfassd logs]# sysctl -a |grep keepalive
net.ipv4.tcp_keepalive_time = 7200
net.ipv4.tcp_keepalive_probes = 9
net.ipv4.tcp_keepalive_intvl = 75
果然是7200秒,這就解釋通了,為什么第一次查看tomcat日志最后報錯都是“Too manay open files”異常,一定是在兩個小時內,close_wait狀態暴增,導致文件描述符超過了65535的最大限制;
而這個狀態應該就是broken pipe 異常導致的,是什么導致的broken pipe異常呢?為什么探針關閉了連接,但是數據采集服務器卻沒有關閉連接?報異常的是tomcat的connector,tomcat不可能會忘記調用close方法去關閉連接,排除了程序的問題,也想不出來是什么導致的了;
於是去拿了往采集服務器上傳數據的探針的日志查看,竟然有大量的一個異常:
2016-11-24 16:27:36,217 [TingYun Harvest Service 1] 166 WARN - Error occurred sending metric data to TingYun. There can be intermittent connection failures. Please wait for a short period of time: java.net.SocketTimeoutException: Read timed out
java.net.SocketTimeoutException: Read timed out
at java.net.SocketInputStream.socketRead0(Native Method) ~[na:1.7.0_60]
at java.net.SocketInputStream.read(SocketInputStream.java:152) ~[na:1.7.0_60]
at java.net.SocketInputStream.read(SocketInputStream.java:122) ~[na:1.7.0_60]
at com.tingyun.agent.libs.org.apache.http.impl.io.SessionInputBufferImpl.streamRead(SourceFile:136) ~[tingyun-agent-java.jar:2.1.3]
.................
都是read time out異常,那么問題就明確了, 是探針端讀取超時了,斷開了連接,而這時候數據采集服務器還在處理請求,它並不知道探針端已經斷開了連接,處理完請求后再將處理結果發給探針,就broken pipe了;
原來這個異常是客戶端讀取超時關閉了連接,這時候服務器端再向客戶端已經斷開的連接寫數據時就發生了broken pipe異常!
探針讀超時的時間是2分鍾,服務器為什么這么長的時間都沒有響應呢?於是使用jstack命令導出了tomcat的線程棧信息進行分析,最后發現代碼中有耗時的操作加了鎖,導致線程阻塞(保密原因,在這里就不貼代碼了);
這里總結一下,給我發私信的有些朋友沒有get到Broken piple問題的重點,並不是只有超時才會導致這個問題,只要是連接斷開,再往這個斷開的連接上去執行寫操作,都會出現這個異常,客戶端超時斷開只是其中的一種情況:
另外,當看到“Too manay open files”異常的時候,通常做法除了檢查ulimit系統限制外,還應該看一下進程打開的文件句柄數,cat /proc/sys/fs/file-nr命令查看系統總句柄數,當前應用打開的文件句柄數使用ls -l /proc/<pid>/fd | wc -l命令,這里還好忽略了這一步,否則可能又要花費一些時間來查找系統真正的問題;
通過這個案例可知,排查問題時,在有些情況下,你第一眼看到的異常信息未必就是問題的根源所在,而是后續一些連鎖反應,尤其是當大量出現同一個異常的情況下,不要看最后一條異常日志,應該先去日志里面查找第一出現該異常的位置,看看這個異常發生之前系統的狀況;
java tcp/ip異常
1 java.net.SocketTimeoutException .
這 個異 常比較常見,socket 超時。一般有 2 個地方會拋出這個,一個是 connect 的 時 候 , 這 個 超 時 參 數 由connect(SocketAddress endpoint,int timeout) 中的后者來決定,還有就是 setSoTimeout(int timeout),這個是設定讀取的超時時間。它們設置成 0 均表示無限大。
2 java.net.BindException:Address already in use: JVM_Bind
該 異 常 發 生 在 服 務 器 端 進 行 new ServerSocket(port) 或者 socket.bind(SocketAddress bindpoint)操作時。
原因:與 port 一樣的一個端口已經被啟動,並進行監聽。此時用 netstat –an 命令,可以看到一個 Listending 狀態的端口。只需要找一個沒有被占用的端口就能解決這個問題。
3 java.net.ConnectException: Connection refused: connect
該異常發生在客戶端進行 new Socket(ip, port)或者 socket.connect(address,timeout)操作時,原 因:指定 ip 地址的機器不能找到(也就是說從當前機器不存在到指定 ip 路由),或者是該 ip 存在,但找不到指定的端口進行監聽。應該首先檢查客戶端的 ip 和 port是否寫錯了,假如正確則從客戶端 ping 一下服務器看是否能 ping 通,假如能 ping 通(服務服務器端把 ping 禁掉則需要另外的辦法),則 看在服務器端的監聽指定端口的程序是否啟動。
4 java.net.SocketException: Socket is closed
該異常在客戶端和服務器均可能發生。異常的原因是己方主動關閉了連接后(調用了 Socket 的 close 方法)再對網絡連接進行讀寫操作。
5 java.net.SocketException: Connection reset 或者Connect reset by peer:Socket write error
connection reset by peer在調用write或者read的時候都會出現。按照glibc的說法,是such as by the remote machine rebooting or an unrecoverable protocol violation。從字面意義上來看,是表示遠端機器重啟或者發生不可恢復的錯誤。從我的測試來看,目前只出現在對端直接kill掉進程的情況。這兩種情況有什么不同呢?對比tcpdump的截包圖來看,直接kill掉遠端進程的話,遠端並沒有發送FIN序號,來告訴對方,我已經關閉管道,而是直接發送了RST序號,而遠端如果調用close或者shutdown的話,是會發送FIN序號的。按照TCP的四次揮手來看,是需要FIN這個序號的。個人猜測,如果在本端沒有收到對方的FIN序號而直接收到了RST序號的話,表明對端出現了machine rebooting or an unrecoverable protocol violation,這時候對這個管道的IO操作,就會出現connection reset by peer錯誤。
該異常在客戶端和服務器端均有可能發生,引起該異常的原因有兩個,第一個就是假如一端的 Socket 被關閉(或主動關閉或者因為異常退出而引起的關閉), 另一端仍發送數據,發送的第一個數據包引發該異常(Connect reset by peer)。另一個是一端退出,但退出時並未關閉該連接,另 一 端 假 如 在 從 連 接 中 讀 數 據 則 拋 出 該 異 常(Connection reset)。簡單的說就是在連接斷開后的讀和寫操作引起的。
還有一種情況,如果一端發送RST數據包中斷了TCP連接,另外一端也會出現這個異常,如果是tomcat,異常如下:
org.apache.catalina.connector.ClientAbortException: java.io.IOException: Connection reset by peer
阿里的tcp方式的健康檢查為了提高性能,省去揮手交互,直接發送一個RST來終斷連接,就會導致服務器端出現這個異常;
對於服務器,一般的原因可以認為:
a) 服務器的並發連接數超過了其承載量,服務器會將其中一些連接主動 Down 掉.
b) 在數據傳輸的過程中,瀏覽器或者接收客戶端關閉了,而服務端還在向客戶端發送數據。
6 java.net.SocketException: Broken pipe
更詳細連接在這里:https://www.cnblogs.com/metoy/p/6565486.html
該異常在客戶端和服務器均有可能發生。在拋出SocketExcepton:Connect reset by peer:Socket write error 后,假如再繼續寫數據則拋出該異常。前兩個異常的解決方法是首先確保程序退出前關閉所有的網絡連接,其次是要檢測對方的關閉連接操作,發現對方 關閉連接后自己也要關閉該連接。
broken pipe只出現在調用write的時候。broken pipe的意思是對端的管道已經斷開,往往發生在遠端把這個讀/寫管道關閉了,你無法在對這個管道進行讀寫操作。從tcp的四次揮手來講,遠端已經發送了FIN序號,告訴你我這個管道已經關閉,這時候,如果你繼續往管道里寫數據,第一次,你會收到一個遠端發送的RST信號,如果你繼續往管道里write數據,操作系統就會給你發送SIGPIPE的信號,並且將errno置為Broken pipe(32),如果你的程序默認沒有對SIGPIPE進行處理,那么程序會中斷退出。一般情況下,可以用signal(SIGPIPE,SIG_IGN)忽略這個信號,這樣的話程序不會退出,但是write會返回-1並且將errno置為Broken pipe(32)。broken pipe只會出現在往對端已經關閉的管道里寫數據的情況下(在收到對端的RST序號后第一次寫不會出現broke pipe,而是write返回-1,這時候正確的做法應該是本端也close這個管道,如果繼續write,那么就會出現這個錯誤)。
java.net.SocketException: Broken pipe (Write failed)
at java.net.SocketOutputStream.socketWrite0(Native Method)
at java.net.SocketOutputStream.socketWrite(SocketOutputStream.java:111)
at java.net.SocketOutputStream.write(SocketOutputStream.java:155)
at sun.security.ssl.OutputRecord.writeBuffer(OutputRecord.java:431)
at sun.security.ssl.OutputRecord.write(OutputRecord.java:417)
at sun.security.ssl.SSLSocketImpl.writeRecordInternal(SSLSocketImpl.java:886)
at sun.security.ssl.SSLSocketImpl.writeRecord(SSLSocketImpl.java:857)
at sun.security.ssl.AppOutputStream.write(AppOutputStream.java:123)
at org.apache.http.impl.io.SessionOutputBufferImpl.streamWrite(SessionOutputBufferImpl.java:124)
at org.apache.http.impl.io.SessionOutputBufferImpl.write(SessionOutputBufferImpl.java:160)
at org.apache.http.impl.io.ContentLengthOutputStream.write(ContentLengthOutputStream.java:113)
at org.apache.http.impl.io.ContentLengthOutputStream.write(ContentLengthOutputStream.java:120)
at org.apache.http.entity.StringEntity.writeTo(StringEntity.java:167)
at org.apache.http.impl.DefaultBHttpClientConnection.sendRequestEntity(DefaultBHttpClientConnection.java:156)
at org.apache.http.impl.conn.CPoolProxy.sendRequestEntity(CPoolProxy.java:160)
at org.apache.http.protocol.HttpRequestExecutor.doSendRequest(HttpRequestExecutor.java:238)
at org.apache.http.protocol.HttpRequestExecutor.execute(HttpRequestExecutor.java:123)
at org.apache.http.impl.execchain.MainClientExec.execute(MainClientExec.java:272)
at org.apache.http.impl.execchain.ProtocolExec.execute(ProtocolExec.java:185)
at org.apache.http.impl.execchain.RetryExec.execute(RetryExec.java:89)
at org.apache.http.impl.execchain.RedirectExec.execute(RedirectExec.java:110)
at org.apache.http.impl.client.InternalHttpClient.doExecute(InternalHttpClient.java:185)
at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:83)
at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:108)
對於 4 和 5 這兩種情況的異常,需要特別注意連接的維護。在短連接情況下還好,如果是長連接情況,對於連接狀態的維護不當,則非常容易出現異常。基本上對長連接需要做的就是:
a) 檢測對方的主動斷連(對方調用了 Socket 的 close 方法)。因為對方主動斷連,另一方如果在進行讀操作,則此時的返回值是-1。所以一旦檢測到對方斷連,則主動關閉己方的連接(調用 Socket 的 close 方法)。
b) 檢測對方的宕機、異常退出及網絡不通,一般做法都是心跳檢測。雙方周期性的發送數據給對方,同時也從對方接收“心跳數據”,如果連續幾個周期都沒有收到 對方心跳,則可以判斷對方或者宕機或者異常退出或者網絡不通,此時也需要主動關閉己方連接;如果是客戶端可在延遲一定時間后重新發起連接。雖然 Socket 有一個keep alive 選項來維護連接,如果用該選項,一般需要兩個小時才能發現對方的宕機、異常退出及網絡不通。
7 java.net.SocketException: Too many open files
原因: 操作系統的中打開文件的最大句柄數受限所致,常常發生在很多個並發用戶訪問服務器的時候。因為為了執行每個用戶的應用服務器都要加載很多文件(new 一個socket 就需要一個文件句柄),這就會導致打開文件的句柄的缺乏。
解決方式:
a) 盡量把類打成 jar 包,因為一個 jar 包只消耗一個文件句柄,如果不打包,一個類就消耗一個文件句柄。
b) java 的 GC 不能關閉網絡連接打開的文件句柄,如果沒有執行 close()則文件句柄將一直存在,而不能被關閉。
也可以考慮設置 socket 的最大打開 數來控制這個問題。對操作系統做相關的設置,增加最大文件句柄數量。
ulimit -a 可以查看系統目前資源限制,ulimit -n 10240 則可以修改,這個修改只對當前窗口有效。
8 Cannot assign requested address
1. 端口號被占用,導致地址無法綁定:
java.net.BindException: Cannot assign requested address: bind:是由於IP地址變化導致的;
2. 服務器網絡配置異常:
/etc/hosts 中配置的地址錯誤;
3.還有一種情況是執行ipconfig 發現沒有環路地址,這是因為環路地址配置文件丟失了;
————————————————
版權聲明:本文為CSDN博主「朱清震」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/zqz_zqz/java/article/details/52235479