問題:單服單天過億請求中,出現0.0001%的500怎麼回事?
- 日誌中不定時出現
mysql gone away
,connect timeout
,reset by beer
等錯,原因不明。 - access-log同樣不定時出現5xx錯。
- 感覺MySQL運行正常,沒壓力,沒慢查,sql使用主鍵查詢–不知道問題在哪?
- 連接MySQL的代碼使用keepalive。
這個時候,你是否懷疑過是連接庫的問題?
一個TCP連接池的自我修養
- keepalive特性,idle-connection可設定。
- one retry 特性。
- max-connection 設定。
- read/write timeout 設定。
max-connection 特性是必需的
如果是mysql連接庫,max-connection不設上限,要不就是造成後端的mysql連接過多,拖跨mysql。
如果是http連接庫,就會造成服務應用大量併發請求在等待,不論阻塞、非阻塞同樣會。即使像go語言,這樣原生性非阻塞、輕併發語言。大量的等等請求,一樣會有耗盡內存的風險。換是python、php一個線程就以M爲單位內存計算的,併發更低。
所以必需帶有合理的 setMaxConnection()
接口。
非常重要的容錯
當連接池滿載時,一個新線程請求一個新連接時,採用什麼策略?
等待?
還是直接返回try-later?
read/write timeout 也是必需的
任何的TCP連接,都可能出現的讀/寫超時,對上層來說就是出錯了,如果timeout很長,即直接阻塞了此線程或微線程。大量阻塞同樣地引發內存風險,也不存在語言上的差別。所以,合理的timeout值,特別是read timeout,經常地出現一種,對方服務器處理慢,不能及時返回結果,而client端斷開後,對方繼續完成了工作,而client方以爲沒完成,而重發請求,如果沒防重入,會造成數據不一致。 爲什麼重發?參考one-retry特性說明。
keepalive+ idle-connection 作爲可選特性
追求高性能,TCP的keepalive,連接重用
是必需的。
而idle-connection作爲閒時保持最低的連接數量,當上層業務要使用時馬上可用。
還有一種做法是 connection-keep-timeout, 即每個連接閒下來後,多長時間後自動關閉。
MySQL中就可以使用 set timeout=n;
顯式設置每個連接的timeout值,否則使用服務端默認統一值。
one retry 特性,在keepalive打開時,必需加上
one-retry特性,並不是每個程序員都注意並知道的一個常規特性。
而此特性正是解決 我們項目中 0.0001% 5xx 問題
的關鍵。
一旦開啓idle-connection特性後, TCP客端端會保持一定數量在連接在連接池中,上層要使用時返回一個連接。
而像MySQL的通信協議中是有Ping指令的,就是用於測試連接是否有效。所以當返回一個連接時,如果是MySQL,就可以用Ping測試一下,確保返回給上層的是可用連接。
原因是連接池中的連接不一定有效,即使你代碼中監聽了socket的close事件,但由於服務端可能使用了LVS這樣的虛擬IP,很可能出現服務端單方面斷開了TCP連接,客戶端是沒法感知(注:這裏並不是說使用LVS就會有這種現象,只能用於舉例說明,服務端可能使用了客戶端無法感知的連接層)。除此以後,TCP斷開時4次握手不完全,也是很正常的現象。
爲了上層能獲得一個可用連接池,沒有ping協議的HTTP協議怎麼辦?
可以使用one-retry特性:
上層發送一個請求調用,由 連接池
執行, 如出現 reset by beer
或 mysql was gone away
情況的,可以新建一次連接,重發一次請求
。我把這種行爲稱爲one-retry
, 上層不必感知。若重試的請求,也出來同樣的錯誤,就應該向上層返回或異常了–視語言而定。