Redis 客户端未被 ASP.NET 应用程序删除,运行 在 Azure 上

Redis clients not being dropped by ASP.NET app, running on Azure

我们在 Azure 应用服务上有一个 .NET 4.6.1 ASP.NET Web 表单应用程序 运行ning。我说的是 Web 表单,但该应用程序还内置了 Web Api 2,以及两个分别执行多个任务的 WebJob 项目。

我们使用 StackExchange.Redis 进行缓存。我们还使用 Redis 作为 SessionState。我提到这一点,因为问题始于 Redis 连接的建立,除非我们重新启动 ASP.NET 应用程序,否则这些连接不会消失。我们使用 Lazy 模式重新共享 ConnectionMultiplexer。本地测试确认连接确实在各个请求中共享。

对我们的 Redis 配置相当有信心,我们开始查看与 ASP.NET 应用程序相关的指标。线程数反映了我们的 Redis 客户端图。问题是,我不知道在线程数方面什么是正常的。但我希望他们在几天内达到 dispose/disappear/die。不建立。

我们广泛使用 Async/Await,但我们通常不直接处理线程。最后一行可能听起来有点天真,对不起不知道怎么说。我们往往会在工作日看到最繁忙的时间,而在晚上的时间最少 activity。

我们很茫然,我们做错了什么?我说得对吗,我们是否应该看到站点上的线程数随着 activity 的减少而下降?也许我已经说了一些你可以指着它说 "What the hell are you doing?" 的东西,那就太好了。不过,我真正想要的是关于我们可能对这种线程构建负责的内容以及我们如何管理它的建议。


更新 03/08

第二张图片中的线程图表正在通过 SUM 聚合。如果将聚合更改为 MIN、MAX 或 AVG,您会看到更合理的线程数,这表明线程已被正确处理。这显然是网络应用程序的一大安慰。

我假设,尽管我的谷歌搜索未能证实这一点,但 SUM 表示自上次应用程序重新启动以来创建的线程总数。如果我的假设是正确的,那么 Web 应用程序线程计数的总和与 AVG Redis 客户端计数相匹配这一事实证实了我们最初的怀疑,即我们的连接多路复用器不仅不共享连接,而且当操作完成。

这是我们的 Redis class,它看起来与互联网上的其他 1000 个示例相同,至少据我所知:

Imports System.Configuration
Imports StackExchange.Redis

Public Class RedisCache

    Private Shared ReadOnly Property LazyConnection As New Lazy(Of ConnectionMultiplexer)(Function()
            Dim cacheConnection = ConfigurationManager.AppSettings("CacheConnection")
            Dim multiplexer = ConnectionMultiplexer.Connect(cacheConnection.ToString())
            multiplexer.PreserveAsyncOrder = False

            Return multiplexer
        End Function)

    Public Shared ReadOnly Property Connection As ConnectionMultiplexer
        Get
            Return LazyConnection.Value
        End Get
    End Property

    Public Shared ReadOnly Property UseCache As Boolean
        Get
            Return ConfigurationManager.AppSettings("CacheConnection") IsNot Nothing
        End Get
    End Property

End Class

正如我已经提到的,我们也将 Redis 用于会话状态,为此我们有一个非常基本的配置:

<sessionState
    mode="Custom"
    customProvider="RedisSessionProvider"
    <providers>
        <add name="RedisSessionProvider"
            type="Microsoft.Web.Redis.RedisSessionStateProvider"
            port="6380"
            host="***"
            accessKey="***"
            applicationName="***"
            retryTimeoutInMilliseconds="5000"
            ssl="true"/>
    </providers>
</sessionState>

有没有人知道什么可以延长这些客户的生命?


更新 03/08 第二部分

我一直在考虑如何缩小问题的范围。最简单的起点是 "is it Cache, Session or Redis as whole?" 为此,我们将启动一个额外的 Redis 服务器,在下一个版本中将有缓存或会话状态指向它。希望这些服务器中的一个会表现出我们正在尝试修复的相同行为,而另一个表现得更好。两者中的前者将是我们努力的方向。

我在我们的测试环境中做的另一件小事是创建一个测试 属性,它生成一个新的 GUID,与 Connection 属性 作品:

Private Shared ReadOnly Property LazyGuid As New Lazy(Of Guid)(Function() Guid.NewGuid())

Public Shared ReadOnly Property Guid As Guid
    Get
        Return LazyGuid.Value
    End Get
End Property

然后我从 Web 应用程序的不同部分创建了一些调用,调用新的 GUID 属性,以及现有的 Redis 代码:

<Route("guid"), HttpGet>
Public Async Function GetGuid() As Task(Of Tuple(Of String, String))
    Return New Tuple(Of String, String)(RedisCache.Guid.ToString(), If(RedisCache.UseCache, RedisCache.Connection.ClientName, Nothing))
End Function

<Route("guid"), HttpPost>
Public Async Function PostForGuid() As Task(Of Tuple(Of String, String))
    Return New Tuple(Of String, String)(RedisCache.Guid.ToString(), If(RedisCache.UseCache, RedisCache.Connection.ClientName, Nothing))
End Function

<Route("guid/sync"), HttpGet>
Public Function GetSyncGuid() As Tuple(Of String, String)
    Return New Tuple(Of String, String)(RedisCache.Guid.ToString(), If(RedisCache.UseCache, RedisCache.Connection.ClientName, Nothing))
End Function

上面的示例以及嵌入在 ASPX 页面中的一些跨多个会话(和时区)的调用产生了相同的结果。所以目前我强烈怀疑问题出在 RedisSessionStateProvider,或者至少是我们对它的使用。


更新 13/08

首先,我已经 运行 在测试服务器上安装了两个 Redis 实例大约一个星期了。一个接受 Session 请求,另一个接受来自上面 RedisCache class 的请求。两者都保持稳定的 15-20 连接。

虽然是这次更新的重点。我的想法是 运行 CLIENT LIST 在生产 Redis 上。由于客户端数量大约为 1.3k,我设法获取了 850 个客户端行的样本。

在整个样本中,有一个客户端的最后一个命令是 GET,其余的是 UNSUBSCRIBE信息。年龄范围从 5000 秒到 65,000 秒。空闲时间范围为 0 到 60 秒。我了解 UNSUBSCRIBE 命令与 StackExchange.Redis' 处理 pub/sub 功能有关,据我所知,我没有使用这些功能。

为什么这些客户保持活跃并成倍增加?

id=1367825 addr=*** fd=45 name=*** age=465516 idle=56 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 ow=0 owmem=0 events=r cmd=info numops=7680
id=1319911 addr=*** fd=611 name=*** age=489772 idle=48 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 ow=0 owmem=0 events=r cmd=info numops=8082
id=1409149 addr=*** fd=477 name=*** age=444591 idle=34 flags=N db=0 sub=1 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 ow=0 owmem=0 events=r cmd=unsubscribe numops=7328
id=1319912 addr=*** fd=508 name=*** age=489772 idle=38 flags=N db=0 sub=1 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 ow=0 owmem=0 events=r cmd=unsubscribe numops=8072
id=2169495 addr=*** fd=954 name=*** age=59035 idle=56 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 ow=0 owmem=0 events=r cmd=info numops=984
id=2169496 addr=*** fd=955 name=*** age=59035 idle=56 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 ow=0 owmem=0 events=r cmd=info numops=984
id=1219863 addr=*** fd=557 name=*** age=540498 idle=38 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 ow=0 owmem=0 events=r cmd=info numops=8917
id=1032642 addr=*** fd=594 name=*** age=635373 idle=56 flags=N db=0 sub=1 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 ow=0 owmem=0 events=r cmd=unsubscribe numops=10474

更新 14/08

一个潜在的积极发展。我在 Github 上登记了一张票。听起来我们的症状问题已在 2.0 中得到修复。 Clients that never die

我可以告诉你这几个月真是太疯狂了。 StackExchange 团队与 Azure 团队一起发布了 2.0.495,28 days ago. Which was amazing but for our reliance on RedisStateProvider, which in turn depends on the StrongName variant of StackExchange.Redis. The StrongName variant was discontinued in 2.0, so I logged a ticket。您是否相信他们在 15 天前发布了自己的版本。太棒了。

好news/bad新闻情况。更新成功了,耶!它工作得有点太好了,嘘。我们之前在 1.1k+ 连接中大量使用的通信突然被压缩到 40 左右的连接中。超时很多。错误消息非常有用 link though. The suggestion being that a pool of ConnectionMultiplexers might be the answer. Using code based on this ,我们已经实现了我们自己的池,可以使用应用程序设置放大或缩小。该池仍处于测试阶段,因此现在说它是否能解决我们的问题还为时过早,但结果看起来很有希望。