Fork me on GitHub

使用ServiceStack.Redis过程中遇到的问题

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测试脚本有问题??时间太紧我抱着试试看看的心里又采用了上面的连接池的方法试了一下,奇迹般的竟然正常了。坑爹啊~来来回回头都搞大了结果就不明不白的好了,时间太匆忙也怪我太马虎中间的信息都没做保留而且影响因素太多没好好的控制,有时间一定要在重现一下弄个究竟。

posted @ 2012-12-10 20:26  zhanjindong  阅读(15499)  评论(7编辑  收藏  举报
TOP