ServiceStack.Redis是Redis官網推薦的C#客戶端(這里下載),使用的人也很多。最近項目中也用到了,網上查了一下使用這個客戶端的方法大概有三種:每次訪問新建一個連接,使用連接池和使用長連接(可以看這里)。我一開始使用很簡單(我用的版本是3.9.32.0)封裝了一個RedisHelper類,內部每次訪問new一個RedisClient,並每次用完dispose掉。
public class RedisHelper : IDisposable { public const string DefaultHost = "localhost"; public const int DefaultPort = 6379; #region Field private IRedisClient _redis; private PooledRedisClientManager _prcm; #endregion public RedisHelper() { } public RedisHelper(string host, int port) { _redis = new RedisClient(host, port); } public RedisHelper(string host, int port, int db) { _redis = new RedisClient(host, port); _redis.Db = db; } private bool m_disposed = false; public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (!m_disposed) { if (disposing) { _redis.Dispose(); } m_disposed = true; } } ~RedisHelper() { Dispose(false); } }
但是在實際測試過程中500左右的並發就會出現“每個套接字地址通常只允許使用一次” 的錯誤,很奇怪我每次用完RedisClient都dispose掉了,按理說應該是釋放掉了連接了。看源碼在RedisNativeClient.cs中的下下面幾行代碼:
protected virtual void Dispose(bool disposing) { if (ClientManager != null) { ClientManager.DisposeClient(this); return; } if (disposing) { //dispose un managed resources DisposeConnection(); } } internal void DisposeConnection() { if (IsDisposed) return; IsDisposed = true; if (socket == null) return; try { Quit(); } catch (Exception ex) { log.Error("Error when trying to Quit()", ex); } finally { SafeConnectionClose(); } }
我最終調用的應該是先Quit向Redis發送一個quit命令,然后執行SafeConnectionClose()方法:
private void SafeConnectionClose() { try { // workaround for a .net bug: http://support.microsoft.com/kb/821625 if (Bstream != null) Bstream.Close(); } catch { } try { if (socket != null) socket.Close(); } catch { } Bstream = null; socket = null; }
這個方法是先關閉Bstream(BufferedStream繼承自Stream),這里說為了解決.net的一個bug(看這里)TcpClient關閉方法不會關閉基礎TCP連接,然后關閉socket。正常來講不會出現我遇到的問題,這時我懷疑是不是就是因為.net那個bug導致的而ServiceStack.Redis並沒有解決這個bug或者某些環境下沒有徹底解決,關於那個bug有時間得好好研究下。
不管怎么樣既然使用上面的那種方式不行我就考慮換種方式,這次為了簡單起見和驗證(我懷疑500壓力太大,其實一點也不大)我使用了連接池:
private static PooledRedisClientManager CreateManager(RedisConfig config) { //192.168.32.13:6379;db=1 //PooledRedisClientManager prcm = new PooledRedisClientManager(new List<string> { "192.168.71.64:6380" }, new List<string> { "192.168.71.64:6380" }, // new RedisClientManagerConfig // { // MaxWritePoolSize = 150, // MaxReadPoolSize = 150, // AutoStart = true // }); PooledRedisClientManager prcm = new PooledRedisClientManager(config.Db, config.Host); prcm.ConnectTimeout = config.ConnectTimeout; prcm.PoolTimeOut = config.PoolTimeOut; prcm.SocketSendTimeout = config.SocketSendTimeout; prcm.SocketReceiveTimeout = config.SocketReceiveTimeout; return prcm; } public static PooledRedisClientManager GetPooledRedisClientManager(string connectionString) { lock (_syncObj) { if (!pools.ContainsKey(connectionString)) { pools.Add(connectionString, CreateManager(GetRedisConfig(connectionString))); } else if (pools[connectionString] == null) { pools[connectionString] = CreateManager(GetRedisConfig(connectionString)); } return pools[connectionString]; } }
上面的“每個套接字地址通常只允許使用一次”沒出現但是卻出現了另外一個奇怪的問題:“no more data”,我用工具測試發現連續不斷的訪問redis並不存在問題,但是稍微間隔幾秒在訪問就會包no more data的錯誤,跟蹤源碼:
int c = SafeReadByte(); if (c == -1) throw CreateResponseError("No more data"); private int SafeReadByte() { return Bstream.ReadByte(); }
這個錯誤意思貌似就是從stream里讀取不到數據(看這里)。但實在想不通為什么。因為要上線,所以沒有多深究,接下來嘗試了第三種方法長連接:
protected static RedisClient Redis = new RedisClient("10.0.4.227", 6379);
但是報的錯更是千奇百怪(確實沒有了每個套接字地址通常只允許使用一次這個錯誤)當時太匆忙錯誤信息沒記錄下來,貌似記得有“強迫關閉連接“的問題 ,我開始懷疑這玩意了網上也有人抱怨Redis(這里)。但看了看這位兄弟的問題跟我的應該還是不一樣,再說redis這么“牛”,懷疑別人之前先懷疑自己,於是我懷疑是不是自己代碼有bug但是反復檢查了幾遍卻沒有頭緒,最后在同事的提示下我把重裝了一下Redis(之前是測試人員裝的),結果用上面長連接的方式又測了一下但是仍然有問題,貌似是連接太多沒有釋放的問題(太馬虎了沒記錄啊),難道是loadRunner測試腳本有問題??時間太緊我抱着試試看看的心里又采用了上面的連接池的方法試了一下,奇跡般的竟然正常了。坑爹啊~來來回回頭都搞大了結果就不明不白的好了,時間太匆忙也怪我太馬虎中間的信息都沒做保留而且影響因素太多沒好好的控制,有時間一定要在重現一下弄個究竟。