StackExchange.Redis 超时问题。

20

生产环境在Azure上,使用Redis Cache Standard 2.5GB

示例1

System.Web.HttpUnhandledException (0x80004005): 抛出类型为'System.Web.HttpUnhandledException'的异常。---> StackExchange.Redis.RedisTimeoutException: 执行SETNX User.313123时超时,inst: 49, mgr: Inactive, err: never, queue: 0, qu: 0, qs: 0, qc: 0, wr: 0, wq: 0, in: 0, ar: 0, clientName: PRD-VM-WEB-2, serverEndpoint: Unspecified/Construct3.redis.cache.windows.net:6380, keyHashSlot: 15649, IOCP: (Busy=0,Free=1000,Min=1,Max=1000), WORKER: (Busy=1,Free=32766,Min=1,Max=32767)(请查看此文章,了解可能导致超时的常见客户端问题: http://stackexchange.github.io/StackExchange.Redis/Timeouts) StackExchange.Redis.ConnectionMultiplexer.ExecuteSyncImpl[T](Message message, ResultProcessor1 processor, ServerEndPoint server) in c:\code\StackExchange.Redis\StackExchange.Redis\StackExchange\Redis\ConnectionMultiplexer.cs:line 2120 at StackExchange.Redis.RedisBase.ExecuteSync[T](Message message, ResultProcessor1 processor, ServerEndPoint server) in c:\code\StackExchange.Redis\StackExchange.Redis\StackExchange\Redis\RedisBase.cs:line 81

示例2

StackExchange.Redis.RedisTimeoutException: 在执行GET ForumTopic.33831操作时超时,实例:1,管理器:未激活,错误:从未出现,队列:2,输入/输出完成端口(IOCP):(繁忙=0,空闲=1000,最小=1,最大=1000),工作线程(WORKER):(繁忙=1,空闲=32766,最小=1,最大=32767),客户端名称:PRD-VM-WEB-2,服务器端点:Unspecified/Construct3.redis.cache.windows.net:6380,键哈希插槽:5851。(请参阅此文章,查看一些可能导致超时的常见客户端问题:http://stackexchange.github.io/StackExchange.Redis/Timeouts) 在 StackExchange.Redis.ConnectionMultiplexer.ExecuteSyncImpl[T](Message message, ResultProcessor`1 processor, ServerEndPoint server) 中,文件路径为 c:\code\StackExchange.Redis\StackExchange.Redis\StackExchange\Redis\ConnectionMultiplexer.cs 的第 2120 行。 在 StackExchange.Redis.RedisBase.ExecuteSync[T](Message message, ResultProcessor`1 processor, ServerEndPoint server) 中,文件路径为 c:\code\StackExchange.Redis\StackExchange.Redis\StackExchange\Redis\RedisBase.cs 的第 81 行。 在 StackExchange.Redis.RedisDatabase.StringGet(RedisKey key, CommandFlags flags) 中,文件路径为 c:\code\StackExchange.Redis\StackExchange.Redis\StackExchange\Redis\RedisDatabase.cs 的第 1647 行。 在 C3.Code.Controls.Application.Caching.Distributed.DistributedCacheController.Get[T](String cacheKey) 中,文件路径为 C:\Construct.net\Source\C3Alpha2\Code\Controls\Application\Caching\Distributed\DistributedCacheController.cs 的第 115 行。 在 C3.Code.Controls.Application.Caching.Manager.Manager.Get[T](String key, Func`1 getFromExternFunction, Boolean skipLocalCaches) 中,文件路径为 C:\Construct.net\Source\C3Alpha2\Code\Controls\Application\Caching\Manager\Manager.cs 的第 159 行。 在 C3.PageControls.Forums.TopicRender.Page_Load(Object sender, EventArgs e) 中,文件路径为 C:\Construct.net\Source\C3Alpha2\PageControls\Forums\TopicRender.ascx.cs 的第 40 行。 在 System.Web.UI.Control.OnLoad(EventArgs e)。 在 System.Web.UI.Control.LoadRecursive()。 在 System.Web.UI.Control.LoadRecursive()。 在 System.Web.UI.Control.LoadRecursive()。 在 System.Web.UI.Control.LoadRecursive()。 在 System.Web.UI.Control.LoadRecursive()。 在 System.Web.UI.Control.LoadRecursive()。 在 System.Web.UI.Control.LoadRecursive()。 在 System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)。

这些错误是零星的,每天会出现几次。

这是 Azure 网络故障,还是我可以减少的问题?从错误中的数字来看,似乎没有什么异常,而且服务器负载似乎从未超过 Azure 报告的 7%。

Redis 连接

internal static class RedisController
{
    private static readonly object GetConnectionLock = new object();
    public static ConnectionMultiplexer GetConnection()
    {
        if (Global.RedisConnection == null)
        {
            lock (GetConnectionLock)
            {
                if (Global.RedisConnection == null)
                {
                    Global.RedisConnection = ConnectionMultiplexer.Connect(
                        Settings.Deployment.RedisConnectionString);
                }
            }
        }
        return Global.RedisConnection;
    }

@varlogtim无法在其他基础设施上测试此问题,因为它正在生产中使用 - 开发服务器从未遇到过此错误(我在开发中也使用Redis)。 - Tom Gullen
@janniks发布了它。 - Tom Gullen
1
我开始认为这只是StackExchange.Redis客户端的一个错误。我也遇到了同样的问题,但一直没有解决。它在我们的生产服务器上造成了很多问题。 - Mike Christensen
@MikeChristensen,你能分享一下你是如何连接到Redis客户端的吗? - Tom Gullen
@BernieWhite 我们的连接字符串是 xxxxxxx.redis.cache.windows.net:6380,password=xxxxxx=,ssl=True,abortConnect=False,allowAdmin=True我们在Azure上运行两个使用Redis的VM的Web应用程序。我们正在使用StackExchange.Redis 2.0.513非常感谢任何帮助,我们在这里已经快要崩溃了。我们已经从默认值更改了 Application_Start ThreadPool.SetMinThreads(10, 10),但似乎没有任何区别。 - Tom Gullen
显示剩余6条评论
5个回答

11
有3种情况可能会导致超时,很难知道哪种情况正在发生:
  1. 库出现问题;特别是与TLS实现有关以及在v1.*版本的库中如何处理读取循环-我们已经花费了大量时间来为v2.*做出改进(但是:升级到v2并不总是轻松的,特别是如果您正在使用该库作为依赖于特定版本的其他代码的一部分)
  2. 服务器/网络出现问题;这是很可能的情况-查看“slowlog”可以帮助确定是否是服务器端的问题,但我无法看到。
  3. 服务器和网络都正常,库正在尽力工作,但是在客户端和服务器之间传输的某些巨大块会延迟其他操作;我正在进行一些更改以帮助识别此类情况,如果这被证明是一个普遍存在的问题,我们也许会考虑更好地利用并发连接(这不会增加带宽,但可以减少被阻塞操作的延迟)-这只会在v2中进行更改,请注意

感谢你的帮助,马克。我们通过在Application_Start中设置WorkerThreadsCompletionPortThreads为200(默认值我假设为1),并使用ThreadPool.SetMinThreads(200, 200),成功将每日错误数量从约50-100个降至零。猜测这适用于第二类别? - Tom Gullen
@TomGullen 嗯嗯...这可能是“第四类” :) 顺便说一句-我昨天添加了一些新的支持来帮助识别/指出“第三类”-https://github.com/StackExchange/StackExchange.Redis/commit/7de4483509e56ba5798e56f062198b229659ff8c - Marc Gravell
感谢你的帮助 Marc 和一如既往的精彩库!开心地看到我们似乎已经解决了这个问题,至少在我观察的过去两天内都没有再出现。顺便说一下,Azure 支持团队已经分析了服务器/网络性能,并确认在错误被抛出的时候并没有异常行为。 - Tom Gullen

6

延迟连接

作为最佳实践,请确保使用以下模式连接到 StackExchange Redis 客户端:

private static Lazy<ConnectionMultiplexer> lazyConnection = new Lazy<ConnectionMultiplexer>(() => {
    return ConnectionMultiplexer.Connect("cachename.redis.cache.windows.net,ssl=true,abortConnect=false,password=password");
});

public static ConnectionMultiplexer Connection {
    get {
        return lazyConnection.Value;
    }
}

如果上述方法无效,可以参考来源1中提到的其他调试路线,其中涉及区域、带宽和NuGet包版本等。

IO线程

另一个选项是增加最小IO线程数。通常建议将IOCP和工作者线程的最小配置值设置为默认值以上。因为适用于一个应用程序的正确值对于另一个应用程序来说可能会过高或过低,所以没有一种通用的指南来确定这个值。一个好的起点是200或300,然后根据需要进行测试和调整。

如何配置此设置:

  • ASP.NET中,在machine.config文件的<processModel>配置元素下使用minIoThreads配置设置。根据Microsoft的说法,即使在过去可以这样做,也不能通过编辑Web.config(甚至在站点级别)来更改此值,因此您选择的值是所有.NET网站将使用的值。请注意,如果autoConfig设置为false,则不需要添加每个属性,只需放置autoConfig="false"并重写值即可: <processModel autoConfig="false" minIoThreads="250" />

重要提示:在此配置元素中指定的值是每个核心的设置。例如,如果您拥有4核心机器,并且希望运行时minIOThreads设置为200,则应使用<processModel minIoThreads="50"/>

  • 在ASP.NET之外,使用ThreadPool.SetMinThreads() API。
  • .Net Core中,根据环境/注册表配置旋钮,添加环境变量COMPlus_ThreadPool_ForceMinWorkerThreads来覆盖默认的MinThreads设定 - 您也可以使用上述描述的相同的ThreadPool.SetMinThreads()方法。

文献来源:

  1. Microsoft Azure - 调查 StackExchange.Redis 在 Azure Redis Cache 中的超时异常
  2. StackExchange.Redis

我们已经更新到这种方法,但对我们来说没有任何改变,错误的频率仍然相同。 - Tom Gullen
2
为什么在Asp.net(框架)中不使用ThreadPool.SetMinThreads()? - zolty13

2
我的猜测是网络稳定性存在问题,因此出现了超时的情况。
由于没有人提到responseTimeout的增加,我建议尝试一下调整它。默认值为50毫秒,很容易达到。我建议将其尝试调整到200毫秒左右,看看是否有助于处理消息。
引用自配置选项
responseTimeout={int}   ResponseTimeout     SyncTimeout     Time (ms) to decide whether the socket is unhealthy

在GitHub上有多个问题,其中一个可能是#871 The "network stability" / 2.0 / "pipelines" rollup issue

另外,您是否尝试使用ConnectionMultiplexer.ConnectAsync()而不是ConnectionMultiplexer.Connect()

2021年12月14日 - 更新

stackexchange.redis v2.2.4中给出以下内容:

'responseTimeout':警告CS0618 ConfigurationOptions.ResponseTimeout已过时:“此设置不再起作用,不应使用”

更新由MX313发送。


1
FYI:在stackexchange.redis v2.2.4中,对于'responseTimeout',给出了以下警告: 警告CS0618 'ConfigurationOptions.ResponseTimeout'已过时:'此设置不再起作用,不应使用'。 - MX313

1
我一直在为超时错误苦苦挣扎,以下步骤解决了我的问题:
首先,我使用了 `Lazy` 来处理连接:
private static Lazy<ConnectionMultiplexer> lazyConnection =  new Lazy<ConnectionMultiplexer>(() =>
{
   return ConnectionMultiplexer.Connect(new ConfigurationOptions
          {
               EndPoints = { Url },
               AbortOnConnectFail = false,
               Ssl = UseSsl,
               Password = Password,
           });
});

public static ConnectionMultiplexer Connection => lazyConnection.Value;

第二,我将所有的异步方法更新为同步方法。例如,我原来使用的是StringGetAsync,现在我用StringGet替换它。
第三,我改变了最小线程数:
public static async Task Main(string[] args)
{
   ...
   SetupThreadPool();
}

private static void SetupThreadPool()
{
     ThreadPool.GetMaxThreads(out var workerThreads, out var completionPortThreads);
     ThreadPool.SetMinThreads(workerThreads, completionPortThreads);
}

我通过 bombardier 进行了API测试(-d 10s -c 125),并成功达到零错误状态,以下是基准测试结果:

benchmark

如您所见,所有请求都被应用程序成功处理了!
希望这能帮到您。 祝好运。

0

打开网络流量监视器以确认/否认故障。我有一个解决方案,但很粗糙。选项1-尝试在Azure中重新启动托管的Redis实例。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接