简体   繁体   English

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

[英]ServiceStack.Redis: PooledRedisClientManager creating way too many connections

I think I'm doing something wrong here. 我想我在这里做错了。 Before I start, a little bit of context. 在我开始之前,先介绍一下。

Our company works with a tool called GeneXus: It's one of those code-generator tools, which has been used for years and years. 我们公司使用一种名为GeneXus的工具:它是那些代码生成器工具之一,已经使用多年。 It generates C# code so we can build our own assemblies and make it work with that tool. 它生成C#代码,因此我们可以构建自己的程序集并使其与该工具一起使用。 Our application deals a lot with SOAP calls, and it also makes some good use of Redis. 我们的应用程序对SOAP调用有很多帮助,它也很好地利用了Redis。 In fact, Redis is a main piece of the whole code infrastructure. 事实上,Redis是整个代码基础架构的主要部分。

To make it work with Genexus, we had to create a wrapper class around the ServiceStack.Redis library, so it can be used within our GeneXus code. 为了使它与Genexus一起工作,我们必须围绕ServiceStack.Redis库创建一个包装类,因此它可以在我们的GeneXus代码中使用。 That's how we use it inside 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 

That was a simple example, but our wrapper is consistently used like this: Check to see if it's online, do several things and then close the client. 这是一个简单的例子,但我们的包装器一直使用如下:检查它是否在线,做几件​​事然后关闭客户端。

The call to .Close() calls the .Dispose() method under the hood. .Close()调用会调用.Dispose()下的.Dispose()方法。

And this is how we manage the client creation in the wrapper. 这就是我们如何在包装器中管理客户端创建。

First, we have a RedisProvider class which is a singleton. 首先,我们有一个RedisProvider类,它是一个单例。 Doing some tests, we ensured that the pool is created only once. 做一些测试,我们确保只创建一次池。 We create a pool instance like this, inside the singleton RedisProvider: 我们在单例RedisProvider中创建这样的池实例:

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

and this RedisProvider class also has a method like this: 这个RedisProvider类也有这样的方法:

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

What we discovered so far: 到目前为止我们发现了什么

We did some tests using Apache JMeter against our SOAP webservice, simulating 50 users or so. 我们使用Apache JMeter对我们的SOAP Web服务进行了一些测试,模拟了50个左右的用户。 This is what we discovered so far: 这是我们到目前为止发现的:

  • The problem only happens inside an IIS ASP.NET application. 该问题仅发生在IIS ASP.NET应用程序中。 Testing it on a Console Application with heavy concurrency fails to reproduce the problem. 在具有大量并发性的控制台应用程序上测试它无法重现该问题。
  • The pool itself is being created only once. 池本身只创建一次。 The whole application shares this single instance. 整个应用程序共享此单个实例。
  • In the GeneXus example above, it's completely proven that a single connection is used across the calls, from &RedisClient.Check() to &RedisClient.Close() . 在上面的GeneXus示例中,完全证明了在调用之间使用单个连接,从&RedisClient.Check()&RedisClient.Close()
  • BUT when another &RedisClient.Check() is called, normally another connection is created (and apparently it doesn't reuse the previously closed client) and we end up having thousands and thousands (assuming a pool limit of 5000) of TCP connections (which is kinda huge) in a Close Wait state, which don't get reused. 但是当调用另一个&RedisClient.Check() ,通常会创建另一个连接(显然它不会重用以前关闭的客户端),并且我们最终会有数千个(假设池限制为5000)的TCP连接(其中处于“ Close Wait状态,有些不可重复使用。
  • When it hits the pool limit, we have some handling logic (which I didn't put here) to just create a new connection using new RedisClient() after the pool timeouts, which one might think it's not the smartest way to handle that, but well... It does that for a while, and then all those thousands of connections in a Close Wait state starts to close, and then the pool starts working again. 当它达到池限制时,我们有一些处理逻辑(我没有放在这里)只是在池超时后使用new RedisClient()创建一个新连接,人们可能会认为这不是处理它的最聪明的方法,但是......它确实存在了一段时间,然后处于Close Wait状态的所有数千个连接开始关闭,然后池再次开始工作。

My question is: Why isn't it reusing the TCP connections? 我的问题是:为什么不重用TCP连接? It works fine in a Console Application simulation, but when we put it to work on our Genexus application using IIS, it just keeps creating those connections. 它在控制台应用程序模拟中工作正常,但是当我们使用IIS将它用于我们的Genexus应用程序时,它只是不断创建这些连接。

Did I just got this pool thing wrong all the time, or am I doing something wrong? 我是不是一直把这个游泳池弄错了,或者我做错了什么?

Note: For now I'm providing all these info, but if you need more, no problem. 注意:现在我提供所有这些信息,但如果你需要更多,没问题。 I just don't know what more to provide. 我只是不知道还能提供什么。

Edit: Solved. 编辑:解决了。 My code was trying to be too smart. 我的代码试图太聪明了。 I dumbed it down and now it's working properly, though I still don't understand what I was doing wrong. 我愚蠢了,现在它正常工作,但我仍然不明白我做错了什么。 Also, my assumption that absolutely all the connections to Redis were being closed right after being used turned out to be wrong. 此外,我认为绝对所有与Redis的连接在被使用后立即被关闭都是错误的。

The typical usage pattern for accessing a client is to use a using statement, ie: 访问客户端的典型使用模式是使用using语句,即:

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

Calling Dispose() is what releases the client back into the pool. 调用Dispose()是将客户端释放回池中的原因。

Connection Pool Stats 连接池统计信息

You can view a snapshot of internal stats of the connection pool by printing out the Dictionary returned by GetStats() : 您可以通过打印GetStats()返回的Dictionary来查看连接池内部统计信息的快照:

redisManager.GetStats().PrintDump();

Redis Stats Redis统计

You can also view an overall stats of all Redis Client activity with the Global: 您还可以使用全局查看所有Redis客户端活动的总体统计信息:

RedisStats.ToDictionary().PrintDump();

I'd also consider reducing your connection pool size as a connection pool of 5000 is close to not having a connection pool at. 我还考虑减少连接池大小,因为连接池5000接近没有连接池。 I'd be aiming for ~2-3x of your active connections. 我的目标是〜2-3倍的活动连接。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM