ServiceStack.Redis:PooledRedisClientManager创建了太多连接

时间:2015-11-17 18:28:30

标签: c# iis redis servicestack

我想我在这里做错了什么。在我开始之前,先介绍一下。

我们公司使用名为GeneXus的工具:它是那些代码生成器工具之一,已经使用多年。它生成C#代码,因此我们可以构建自己的程序集并使其与该工具一起使用。我们的应用程序对SOAP调用有很多帮助,它也很好地利用了Redis。事实上,Redis是整个代码基础架构的主要部分。

为了使它与Genexus一起工作,我们必须围绕ServiceStack.Redis库创建一个包装类,因此它可以在我们的GeneXus代码中使用。这就是我们在GeneXus中使用它的方式:

//First we check if Redis is working at all. It just pings the Redis server.
If &RedisClient.Check()

   //Here we make several calls to get and set some data. Like that:

   If &RedisClient.Exists("Some_Key")

       &MyData = &RedisClient.Get("Some_Key")

   Else      

       &MyData = FetchFromSQLServerDatabase()        
       &RedisClient.Set("Some_Key", &MyData)

   EndIf 

   //We are done with Redis, close it.

   &RedisClient.Close()

EndIf 

这是一个简单的例子,但我们的包装器一直使用如下:检查它是否在线,做几件​​事然后关闭客户端。

.Close()的调用会调用.Dispose()方法。

这就是我们如何在包装器中管理客户端创建。

首先,我们有一个RedisProvider类,它是一个单例。做一些测试,我们确保只创建一次池。我们在单例RedisProvider中创建一个像这样的池实例:

Pool = new PooledRedisClientManager(
    poolSize: poolSize,
    poolTimeOutSeconds: timeout,
    readWriteHosts: hosts);

这个RedisProvider类也有这样的方法:

public RedisClient GetClient() => (RedisClient)Pool.GetClient();

到目前为止我们发现了什么:

我们使用Apache JMeter对我们的SOAP Web服务进行了一些测试,模拟了50个左右的用户。这是我们到目前为止所发现的:

  • 问题只发生在IIS ASP.NET应用程序中。在具有大量并发性的控制台应用程序上测试它无法重现该问题。
  • 池本身只创建一次。整个应用程序共享此单个实例。
  • 在上面的GeneXus示例中,完全证明在&RedisClient.Check()&RedisClient.Close()的调用中使用了单个连接。
  • 但是当另一个&RedisClient.Check()被调用时,通常会创建另一个连接(显然它不会重复使用以前关闭的客户端),我们最终会有成千上万个(假设一个Close Wait状态的TCP连接(有点大)的池限制,不会被重用。
  • 当它达到池限制时,我们有一些处理逻辑(我没有放在这里)只是在池超时后使用new RedisClient()创建一个新连接,人们可能认为它不是最聪明的方式处理它,但是......它会暂时执行,然后所有成千上万的{{​​1}}状态的连接开始关闭,然后池再次开始工作。

我的问题是:为什么不重用TCP连接?它在控制台应用程序模拟中运行良好,但是当我们使用IIS将它用于我们的Genexus应用程序时,它只是不断创建这些连接。

我是不是一直把这个游泳池弄错了,或者我做错了什么?

注意:现在我提供所有这些信息,但如果你需要更多,没问题。我只是不知道还能提供什么。

编辑:已解决。我的代码试图太聪明了。我愚蠢了,现在它正常工作,但我仍然不明白我做错了什么。此外,我认为绝对所有与Redis的连接在被使用后立即被关闭都是错误的。

1 个答案:

答案 0 :(得分:3)

访问客户端的典型使用模式是使用using语句,即:

using (var redis = redisManager.GetClient())
{
    //...
}

调用Dispose()是将客户端释放回池中的原因。

连接池统计信息

您可以通过打印GetStats()返回的词典来查看连接池内部统计信息的快照:

redisManager.GetStats().PrintDump();

Redis Stats

您还可以使用全局查看所有Redis客户端活动的总体统计信息:

RedisStats.ToDictionary().PrintDump();

我还考虑减少连接池大小,因为 5000 的连接池接近于没有连接池。我的目标是活跃连接的2-3倍。