.net ServiceStack.Redis 性能調優


     最近在debug生產環境的問題時,發現了ServiceStack 4.0.60版本RedisClient存在一個非常嚴重的性能問題。在高並發下,PooledRedisClientManager.GetClientRedis.DisposeClient會導致High CPU,並且持續非常長的時間才能自動修復下面是Demo程序壓測還原問題后,工具的分析結果。

 

通過分析源代碼發現:原來獲取RedisClient的邏輯中通過鎖方式實現,並且當連接被占滿后再獲取連接時,需要循環遍歷數組中所有的連接對象判斷是否有可用連接,會非常消耗CPU。Dispose方法也存在循環遍歷的問題。嘗試了很多種修改方案后,都不盡人意,果斷把這兩段邏輯重寫,下面是相關代碼,已經經過壓測。

 

 

 

PooledRedisClientManager.cs:

private ConcurrentQueue<RedisClient> deactiveClientQueue = new ConcurrentQueue<RedisClient>(); private static object lckObj = new object(); private static object waitObj = new object(); private int redisClientSize = 0; private int maxRedisClient = 500; //PooledRedisClientManager的構造函數中初始化此值:maxRedisClient = this.Config.MaxWritePoolSize;
//GetReadOnlyClient方法也可按此方式修改 public IRedisClient GetClient() { RedisClient client = null; var poolTimedOut = false; DateTime startTime = DateTime.Now; while (true) { bool getResult = deactiveClientQueue.TryDequeue(out client); if (getResult == false) { if (redisClientSize >= maxRedisClient) { Thread.Sleep(3); if (PoolTimeout.HasValue) { // wait for a connection, cry out if made to wait too long if ((DateTime.Now - startTime).TotalMilliseconds >= PoolTimeout.Value) { poolTimedOut = true; break; } } } else { client = CreateRedisClient(); if (client != null) return client; } } else { if (client != null) { InitClient(client); return client; } else { client = CreateRedisClient(); if (client != null) return client; } } } if (poolTimedOut == true) { throw new TimeoutException(PoolTimeoutError); } return client; } private RedisClient CreateRedisClient() { if (redisClientSize >= maxRedisClient) return null; lock (lckObj) { if (redisClientSize >= maxRedisClient) return null; Random dom = new Random((int)DateTime.Now.Ticks); var newClient = InitNewClient(RedisResolver.CreateMasterClient(dom.Next(100))); newClient.OnDispose += (isRecycle) => { if (isRecycle == true) { try { deactiveClientQueue.Enqueue(newClient); } catch { lock (lckObj) { redisClientSize--; } } } else { lock (lckObj) { redisClientSize--; } } }; redisClientSize++; return newClient; } }
RedisClient.cs:
public event RedisClientDisposeEventHandler OnDispose; public override void Dispose() { if (OnDispose != null) OnDispose(this.HadExceptions == false); base.Dispose(); }
RedisClient.cs:    
public delegate void RedisClientDisposeEventHandler(bool isRecycle);

下面是修改前后的結果對比:

1.100個線程,每個線程完成2000次Redis調用,每次調用GetClient。 改造前12s,改造后8.5s,提升近50%。老版本CPU消耗稍高,並具有持續性。

 

2.200個線程,每個線程完成2000次Redis調用,每次調用GetClient。 改造前378s,改造后19s,提升提升近20倍。老版本CPU消耗非常高(解決100%),並具有持續性。新版本CPU占用了僅有原來的一半。

 

3.300個線程,每個線程完成2000次Redis調用,每次調用GetClient。 改造前1580s(26分鍾),改造后29s,提升提升近55倍老版本CPU消耗非常高(解決100%),並具有持續性。新版本CPU占用了僅有原來的一半。

 

    通過上述三個場景的測試可以看出,當RedisClient訪問壓力持續增加時,原版本的響應時間呈現指數性增長,當達到一定壓力時,RedisClient訪問幾乎阻塞,需要非常長時間才能緩解。重構后的RedisClient在性能上有大幅度提升,特別是在高並發下的性能表現,直接秒殺原版本!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM