java.sql.SQLRecoverableException: IO Error: Broken pipe
Table of Contents
1 錯誤信息
ERROR [com.alibaba.druid.util.JdbcUtils] - close connection error java.sql.SQLRecoverableException: IO Error: Broken pipe
2 分析
遇到這個問題,一般是程序訪問服務(比如數據庫)時遇到的。這之間存在着好幾個網絡 通信節點:
程序 –> 連接池 –> 網卡 –> 防火牆 –> 路由器 –> 防火牆 —> 服務端網卡 —> 服務端靜態路由 –> 服務端防火牆 –> 服務監聽 –> 服務
除了程序與連接池(一般集成在一起),其他任何一個節點中斷連接,都有可能引發這個問 題,尤其是防火牆。而一般將某個連接中斷原因,是因為這個連接空閑了太長的時間(保持 連接卻不做任何事情)。網絡防火牆、tcp網絡、服務器本地防火牆、監聽這幾個節點上都 有空閑連接控制。
下面分別通過配置連接池、服務器上的tcp網絡、數據庫層面、來解決空閑連接被異常中 斷的問題。
2.1 連接池
現在國內大都使用druid 作為程序的連接池。那么該連接池針對空閑連接提供了檢測和 校驗機制。比如在申請使用該連接時,先測試該連接是否可用,定時檢查空閑連接是否 可用等,空閑連接定時執行無意義的SQL以保證會話被驗證為alive,推薦配置如下:
spring.datasource.druid.test-while-idle=true spring.datasource.druid.validation-query=SELECT 1 FROM DUAL spring.datasource.druid.validation-query-timeout=1000 sping.datasource.druid.min-Evictable-Idle-Time-Millis=300000 sping.datasource.druid.time-Between-Eviction-Runs-Millis=3000 spring.datasource.druid.keep-alive=true spring.datasource.druid.remove-abandoned=true spring.datasource.druid.remove-abandoned-timeout=3600 spring.datasource.druid.log-abandoned=true
一般使用以上配置后,就不會再出現連接中斷的問題。
2.2 TCP網絡
-
keepalive 類Unix系統上,TCP 連接的 keepalive 可以在應用層實現,也可以在 TCP 中提供。 這個問題存在爭議,因此 TCP 連接的保活探測並不是 TCP 規范中的一部分。但為了方便 ,幾乎所有類 Unix 系統均在 TCP 中提供了相應的功能。
常見類UNIX系統中的tcp keepalive:
操作系統 保活定時器 AIX # no -a | grep keep tcp_keepcnt = 8 tcp_keepidle = 14400 tcp_keepintvl = 150 Linux # sysctl -A | grep keep net.ipv4.tcp_keepalive_intvl = 75 net.ipv4.tcp_keepalive_probes = 9 net.ipv4.tcp_keepalive_time = 7200 FreeBSD #sysctl -A | grep net.inet.tcp net.inet.tcp.keepidle=… net.inet.tcp.keepintvl=… 不同系統上的各參數的時間單位不盡相同。在 AIX 上, tcp_keeidle/tcp_keepinit/tcp_keepintvl 的時間單位是 0.5 秒;而在 Linux 上, net.ipv4.tcp_keepalive_intvl 和 net.ipv4.tcp_keepalive_time 的時間單位則為秒。並 且,上述參數僅對運行在其上的服務器應用連接有效。
- note
- 在 Solaris 上可通過“ndd /dev/tcp \?”命令顯示上述類似參數信息,而在 HP
Unix 上則可通過 nettune 或 ndd 命令進行查詢。
由於所有類 Unix 系統上均支持這種功能,因此,在接下來的部分中我們將基於 AIX 系統 具體講述上述參數的意義和作用機制。
控制參數 參數說明 tcp_keepcnt 關閉一個非活躍連接之前進行探測的最大次數,默認為 8 次 tcp_keepidle 對一個連接進行有效性探測之前運行的最大空閑時長,默認值為 14400(即 2 個小時) tcp_keepintvl 兩個探測的時間間隔,默認值為 150 即 75 秒 我們要通過設置這些參數,使其控制時間間隔要小於防火牆設置的最大空閑時間,如果不了 解防火牆的設置,可以將該tcp_keepintvl的值設置為3分鍾以內,一般網絡防火牆對於 空閑會話的限制不會短於這個時間。
-
tcp retries 這里有另外一個現象,當連接被異常中斷,但是程序這端的服務器沒有收到相關終止信息 時,由原來存在的會話繼續發送報文時,不會得到反饋,超過一定時間后,TCP會重新發 送該報文,直到超過最大允許重發次數。所以,有些時間,程序收到broken pipe 信息 時,是在一段時間以后(常見的是15分鍾)。而在測試、開發人員的眼中,就是業務從開 始執行到報錯, 中間等待了很久,比如15分鍾。這里涉及到Linux內核對tcp 重發報文 次數的控制: net.ipv4.tcp_retries2 ,可以通過文件/proc/sys/net/ipv4/tcp_retries2 進行臨時調整。
其規則是:配置重傳次數小於9的話,就是指數增長時間,如果大於9的話,就是最大超時時間.
TCP_RTO_MIN=(HZ/5)=0.2s
TCP_RTO_MAX=(120HZ)=120s
linear_backoff_thresh = ilog2(1205)=ilog2(0x258)=9
timeout:未超過linear_backoff_thresh=9的部分按TCP_RTO_MIN 2的指數倍增長,超過 的部分按TCP_RTO_MAX線性增長
比如:
sysctl_tcp_retries2=9,則timeout=1023TCP_RTO_MIN=204.6s sysctl_tcp_retries2=11時,timeout=1023TCP_RTO_MIN+2TCP_RTO_MAX=448.6s
針對這個問題,我們可以將重傳次數設置得小一些。比如設置為3.
2.3 數據庫監聽
數據庫也會把一些長時間沒有任何操作的會話給kill掉,並不會給出任何的反饋。當程序 使用長連接,再次請求這些會話時,就會遇到報錯。從數據庫角度來講,可以把空閑時間 設置得更長一些,但是這樣是存在風險的,日積月累后,數據庫中可能存在大量的空閑連 接,由於數據庫一般會限制最大連接數的。如果大量的空閑連接存在,可能導致新的連接 無法建立。
- ORACLE oracle中對空閑會話的檢測是在$ORACLE_HOME/network/admin/sqlnet.ora中配置
的,參數是sqlnet.expire_time ,單位是秒。比如:
sqlnet.expire_time=180
- MySQL 設置wait_timeout 指定空閑時間,單位是秒,最長不建議超過1天。
Created: 2019-09-26 Thu 21:20