問題產生
這兩天業務系統在redis的使用過程中,當並行客戶端數量達到200+之后,產生了大量timeout異常,典型的異常信息如下:
Timeout performing HVALS Parser2#Hash#VersionState, inst: 1, mgr: ExecuteSelect, err: never, queue: 2, qu: 0, qs: 2, qc: 0, wr: 0, wq: 0, in: 0, ar: 0, clientName: GS-SERVER-2894, IOCP: (Busy=0,Free=1000,Min=8,Max=1000), WORKER: (Busy=0,Free=32767,Min=8,Max=32767), Local-CPU: 0% (Please take a look at this article for some common client-side issues that can cause timeouts: https://github.com/StackExchange/StackExchange.Redis/tree/master/Docs/Timeouts.md) No connection is available to service this operation: HVALS Parser2#Hash#VersionState SocketFailure on HVALS
運行環境:
Redis服務器版本:2.8.19 .net Framework版本:4.5.2 StackExchange.Redis版本:1.1.603
問題分析
首先定位問題,排除Redis基礎組件本身問題,使用redis提供的benchmark工具進行測試:
redis-benchmark -h 10.200.XX.XX -p 30301 -q -n 100000 -c 300 -d 10240
由於redis使用docker提供服務因此端口映射到30301,模擬100000次真實場景請求,300並發,每次請求數據大小10K。
經測試,對常見的hset,hget,sadd,spop等短耗時操作,TPS均保持在25K-30K之間,因此初步排除docker提供的redis服務的問題。
因此問題大致定位在了業務系統代碼中,即使用的redis客戶端工具(StackExchange.Redis)這部分。
通過StackExchange對Timeout異常的相關信息(可見這個問題挺常見)及stackoverflow等站點上的相關內容,產生timeout異常比較常見的原因包含(但不限於):
1:服務器資源不足;
2:耗時過長的指令(StackExchange客戶端連接的只讀屬性TimeoutMilliseconds=1000);
3:StackExchange阻塞任務過多(異常信息中的qs值持續增長),對這種情況,StackExchange給出的建議是實現一個復用器池(ConnectionMultiplexer Pool)並動態選取最低負載的復用器進行連接,避免一個鏈接timeout導致所有鏈接阻塞的情況;
4:CLR中Thread Pool最小按需創建線程數過小導致的等待成本(500ms),可見StackExchange的ConnectionMultiplexer使用的是線程池;
此外,在StackExchange.Redis項目的Issue中與Time out相關的主題中,也有人提及在更新StackExchange.Redis的客戶端版本后(1.1.605+)該異常不再出現的問題。
問題解決
根據以上可能產生問題的原因,對業務代碼做出以下修改:
1:不再繼續使用StackExchange.Redis內部封裝的主/從線程池,明確主/從ConnectionMultiplexer對象;
2:修改CLR的Thread Pool最小創建線程閾值數量;
3:在ConnectionMultiplexer單例視線中加入對其IsConnected屬性的判斷,在其連接斷開后手動釋放資源並重新連接;
4:避免如HVALS等可能導致慢操作的指令;
5:升級StackExchange.Redis版本至1.1.605+(目前最新版本1.1.608)。
修改完畢后進行測試,time out異常目前已排除。
2016-10-17 補充
StackExchange.Redis客戶端(V1.1.608)經測試有這么一個特質,即,由complexer單例對象創建的IDatabase對象,在產生Timeout異常后會導致這個complexer單例對象超時,即由其創建的新的IDatabase對象也會繼續Timeout,相當蛋疼。。。
實際使用過程中,由於網絡各種原因,出現Timeout異常難免,但是無法恢復確實很麻煩。測試complexer對象無法dispose,close后重建。。