调整 Azure 函数内的 Redis 连接以防止超时

Tune Redis connection inside an Azure Function to prevent timeouts

TL;DR

如何修改 Azure Functions 中 redis 的最小线程数?

问题

我有一个 Azure 函数,它使用 redis(通过 StackExchange.Redis 包)来缓存一些值,或者检索现有值(如果已经存在)。我目前遇到超时问题,看起来是因为繁忙的 IOCP 线程超过了最小 IOCP 线程值。

2016-09-08T11:52:44.492 Exception while executing function: Functions.blobtoeventhub. mscorlib: Exception has been thrown by the target of an invocation. StackExchange.Redis: Timeout performing SETNX 586:tag:NULL, inst: 1, mgr: Inactive, err: never, queue: 4, qu: 0, qs: 4, qc: 0, wr: 0, wq: 0, in: 260, ar: 0, clientName: RD00155D3AE265, IOCP: (Busy=8,Free=992,Min=2,Max=1000), WORKER: (Busy=7,Free=32760,Min=2,Max=32767), Local-CPU: unavailable (Please take a look at this article for some common client-side issues that can cause timeouts: https://github.com/StackExchange/StackExchange.Redis/tree/master/Docs/Timeouts.md).

根据docs on timeouts,解决方案涉及调整 MinThread 计数:

How to configure this setting:

In ASP.NET, use the "minIoThreads" configuration setting under the configuration element in machine.config. If you are running inside of Azure WebSites, this setting is not exposed through the configuration options. You should be able to set this programmatically (see below) from your Application_Start method in global.asax.cs. Important Note: the value specified in this configuration element is a per-core setting. For example, if you have a 4 core machine and want your minIOThreads setting to be 200 at runtime, you would use . Outside of ASP.NET, use the ThreadPool.SetMinThreads(…) API.

在 Azure Functions 中,global.asax.cs 文件不可用,ThreadPool.SetMinThreads 的使用与我可以解析的相关信息很少! webjobs 上有一个类似的问题尚未得到解答。

我的细节

Redis 代码在主函数的单独文件中。

using StackExchange.Redis;
using System.Text;

private static Lazy<ConnectionMultiplexer> lazyConnection = new Lazy<ConnectionMultiplexer>(() =>
        {
            string redisCacheName = System.Environment.GetEnvironmentVariable("rediscachename", EnvironmentVariableTarget.Process).ToString();;
            string redisCachePassword = System.Environment.GetEnvironmentVariable("rediscachepassword", EnvironmentVariableTarget.Process).ToString();;
            return ConnectionMultiplexer.Connect(redisCacheName + ",abortConnect=false,ssl=true,password=" + redisCachePassword);
        });
        
public static ConnectionMultiplexer Connection
{
    get
    {
        return lazyConnection.Value;
    }
}

static string depersonalise_value(string input, string field, int account_id)
{
    IDatabase cache = Connection.GetDatabase();
    string depersvalue = $"{account_id}:{field}:{input}";
    string value = $"{account_id}{Guid.NewGuid()}";
    bool created = cache.StringSet(depersvalue, value, when: When.NotExists);
    string retur = created? value : cache.StringGet(depersvalue).ToString();
    return (retur);
}

我们目前没有让您像这样执行应用级初始化的好方法。这是由我们的回购 here.

中的一个问题跟踪的

目前,您唯一真正的解决方法是将此初始化代码放入您在函数开始时调用的共享助手中。共享的 init 方法应该有逻辑,这样它只执行一次。

最终,我们需要追求@mathewc 的答案,并在连接多路复用器代码中添加一行以将最小线程数设置为 500

readonly static Lazy<ConnectionMultiplexer> lazyConnection =

    new Lazy<ConnectionMultiplexer>(() =>

    {
      ThreadPool.SetMinThreads(500, 500);

此外,还需要进一步调整并通过 SO code review 增强代码。这里最重要的是超时的大幅增加。

using StackExchange.Redis;
using System.Text;
using System.Threading;

readonly static Lazy<ConnectionMultiplexer> lazyConnection =
    new Lazy<ConnectionMultiplexer>(() =>
    {
        ThreadPool.SetMinThreads(500, 500);
        string redisCacheName = System.Environment.GetEnvironmentVariable("rediscache_name", EnvironmentVariableTarget.Process).ToString();
        string redisCachePassword = System.Environment.GetEnvironmentVariable("rediscache_password", EnvironmentVariableTarget.Process).ToString();
        return ConnectionMultiplexer.Connect(new ConfigurationOptions
        {
            AbortOnConnectFail = false,
            Ssl = true,
            ConnectRetry = 3,
            ConnectTimeout = 5000,
            SyncTimeout = 5000,
            DefaultDatabase = 0,
            EndPoints = { { redisCacheName, 0 } },
            Password = redisCachePassword
        });
    });


public static ConnectionMultiplexer Connection => lazyConnection.Value;


static string depersonalise_value(string input, string field, int account_id)
{
    IDatabase cache = Connection.GetDatabase();
    string depersvalue = $"{account_id}:{field}:{input}";
    string existingguid = (string)cache.StringGet(depersvalue);
    if (String.IsNullOrEmpty(existingguid)){ 
        string value = $"{account_id}{Guid.NewGuid()}";
        cache.StringSet(depersvalue, value);
        return value;
    }
    return existingguid;
}