PooledRedisClientManager是ServiceStack.Redis的串連池管理類,通過串連池可以實現更高效的Redis操作.但PooledRedisClientManager相關GetClient的設計似乎存在一些問題,如果你只Pool只指向一台Redis這倒不會有什麼問題,但如果指向多台Redis那就可能產生悲劇的事情.下面解釋一下指向多台Redis存在的一些問題.
具體代碼
1 /// <summary> 2 /// Called within a lock 3 /// </summary> 4 /// <returns></returns> 5 private RedisClient GetInActiveWriteClient() 6 { 7 var desiredIndex = WritePoolIndex % writeClients.Length; 8 //this will loop through all hosts in readClients once even though there are 2 for loops 9 //both loops are used to try to get the prefered host according to the round robin algorithm10 for (int x = 0; x < ReadWriteHosts.Count; x++)11 {12 var nextHostIndex = (desiredIndex + x) % ReadWriteHosts.Count;13 var nextHost = ReadWriteHosts[nextHostIndex];14 for (var i = nextHostIndex; i < writeClients.Length; i += ReadWriteHosts.Count)15 { 16 if (writeClients[i] != null && !writeClients[i].Active && !writeClients[i].HadExceptions)17 return writeClients[i];18 else if (writeClients[i] == null || writeClients[i].HadExceptions)19 {20 if (writeClients[i] != null)21 writeClients[i].DisposeConnection();22 var client = RedisClientFactory.CreateRedisClient(nextHost.Host, nextHost.Port);23 24 if (nextHost.RequiresAuth)25 client.Password = nextHost.Password;26 27 client.Id = RedisClientCounter++;28 client.ClientManager = this;29 client.NamespacePrefix = NamespacePrefix;30 client.ConnectionFilter = ConnectionFilter;31 32 writeClients[i] = client;33 34 return client;35 }36 }37 }38 return null;39 }工作原理
以上代碼的原理非常簡單,就是輪循不同host下的可用串連,如果相關串連可用測直接返回.如果串連損耗則釋放重新建立.
存在問題
如果只使用一個host倒沒什麼問題,但使用多個host的時候那你會發現如果其中一台的redis服務異常那對應的host還是會被輪循到,那就會導致輪循到應該服務的操作所有都異常,還有更悲劇的情況就是當一台伺服器擋機了,就會導致串連到對應host的串連建立逾時導致程式長時間等待然後報錯,這情況對於並發應來說算是一個非常悲劇的事情.
解決方案
其實可以針對host來劃分節點,每個節點存自有的串連池.當相關host的串連操作出現網路異常的時候,應該把host從當前循環配置資源中排除.這樣可以快速地保證操作會馬上遷移到正常的host上面去.
建立一個host恢複機制,PooledRedisClientManager應該存在一個內部機制對損壞的host 進行檢測,通過connect到redis執行ping指令來確認host是否正常,如果正常馬上把host恢複到輪循範圍內.
思考
作者在串連的儲存上並沒有使用Stack,其實使用Stack在設計和管理上更簡單,也是許是為不想在串連push到池中存線上程同步處理環節的開銷.