今天解析服務在查詢Redis的Set數據過程中拋出timeout exception,產生異常的方法是:
db.SetMembers(key);
這個API返回結果是指定set內的所有kv對象; 解決這個問題的方法僅僅是使用另一個api:
db.SetScan(key);
這個API也是返回set內所有的kv對象。 從功能上來說這2個API是一樣的,但是其返回對象,前者是RedisValue[],后者是IEnumerable。但是在今天的實際場景中的結果是不同的。 從返回類型能看出的是,前者是一個同步查詢的API,而后者是一個lazy的查詢,在今天的實際場景中,Set內數據量大概為20+M,單次request同步查詢timeout也屬正常。 以上是這個問題本身的原因及解決方案。
* * *
更深入一步,這兩個同樣功能的API的執行機制反映出stackexchange封裝API的思想,考慮redis是一個單線程的操作,如果在redis中執行耗時較長的操作,將會阻塞其它的請求。stackexchange中一個ConnectionMultiplexer封裝一個TCP連接,之前某次早會也說過,ConnectionMultiplexer的成本很高,一般是以單例的形式存在的,到這里就能解釋為什么stackexchange會直接對慢查詢拋出timeout,而並不是request后等待其timeout,原因很簡單,單個TCP連接同一時間只能處理一個request/response,如果處理了慢查詢,后續的request就會被阻塞,這點在stackexchange拋出的timeout異常中也有側面證明:qs數值表示了當前ConnectionMultiplexer所使用的TCP連接阻塞的request數量。 而同時stackexchange的API封裝是比較“智能”的,還是以今天出異常的API:SetMembers來說明,在set內數據量較小的時候,正常返回所有KV沒有問題,但是數據量一大,比如今天20+M的情況,stackexchange就直接不會發送這個request並拋出timeout。這種“智能”也給項目開發測試工作帶來隱患:測試中因為數據量較小,這個問題不會凸顯,而在生產環境中則一定會出現問題,並且是數據量到一定程度后才出現。 因此Redis的種種慢查詢,如Key *,Keys以及今天的SetMembers,都屬於需要小心處理的隱患。牢記Redis單線程的特征,盡量控制耗時的慢查詢,以免降低Redis的整體性能。 為便於排查Redis的問題減小Codereview工作量,也可以考慮將Redis相關操作在項目代碼結構中集中管理。
