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 algorithm 10 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到池中存在線程同步處理環節的開銷.