Azure Function创建到PostgreSQL的连接过多

时间:2019-07-10 19:10:41

标签: postgresql azure azure-functions npgsql azure-durable-functions

我有一个Azure耐用功能,可与也托管在Azure中的PostgreSQL数据库进行交互。

PostgreSQL数据库的连接限制为50,此外,我的连接字符串将连接池的大小限制为40,为超级用户/管理员连接留有空间。

尽管如此,在某些负载下我还是得到了错误

  

53300:剩余的连接插槽保留用于非复制超级用户连接

This documentation from Microsoft似乎很重要,但似乎我不能成为一个静态客户端,而且正如所提到的,

  

由于仍然无法使用连接,因此应该优化与数据库的连接。

我有这种方法

private IDbConnection GetConnection()
{
    return new NpgsqlConnection(Environment.GetEnvironmentVariable("PostgresConnectionString"));
}

当我想与PostgreSQL交互时,我确实喜欢这样

using (var connection = GetConnection())
{
    connection.Open();
    return await connection.QuerySingleAsync<int>(settings.Query().Insert, settings);
}

因此,我正在创建(和处置)许多NpgsqlConnection对象,但是根据this,这应该没问题,因为连接池是在后台处理的。但是有关Azure Functions的某些内容可能会使这种想法无效。

我注意到我最终有很多空闲连接(来自pgAdmin): pgAdmin connection graph 基于此,我尝试摆弄Connection Idle LifetimeTimeoutPooling之类的Npgsql connection parameters,但是连接过多的问题似乎仍然持续到一个或另一个程度。另外,我尝试限制并发编排器和活动函数的数量(请参见this doc),但这似乎在一定程度上抵消了Azure函数具有可伸缩性的目的。它确实有帮助-我得到的太多连接错误更少了。据推测,如果我继续用较低的数字进行测试,我什至可以消除它,但是,再次看来,这很不合理,并且可能还有另一种解决方案。

如何在不增加连接数的情况下将PostgreSQL与Azure函数一起使用?

3 个答案:

答案 0 :(得分:0)

这是Dependency Injection真正有用的地方。您可以创建一个singleton客户,它将完美地完成工作。如果您想了解更多有关服务寿命的信息,可以在docs

中阅读
  1. 首先添加此nuget Microsoft.Azure.Functions.Extensions.DependencyInjection

  2. 现在添加如下所示的新类并解析您的客户端。

[assembly: FunctionsStartup(typeof(Kovai.Serverless360.Functions.Startup))]

namespace MyFunction
{
    class Startup : FunctionsStartup
    {
        public override void Configure(IFunctionsHostBuilder builder)
        {
            ResolveDependencies(builder);
        }
    }
    public void ResolveDependencies(IFunctionsHostBuilder builder)
    {
        var conStr = Environment.GetEnvironmentVariable("PostgresConnectionString");
        builder.Services.AddSingleton((s) =>
        {
            return new NpgsqlConnection(conStr);
        }
    }
}

现在您可以轻松地从任何功能中使用它

public FunctionA
    {
        private readonly NpgsqlConnection _connection;
        public FunctionA(NpgsqlConnection conn)
        {
            _connection = conn;
        }

        public async Task<HttpResponseMessage> Run()
        {
            //do something with your _connection
        }
    }

答案 1 :(得分:0)

我没有一个好的解决方案,但是我想我能解释为什么会发生这种情况。

Azure Function App为什么要最大化连接?

即使您为池大小指定了40个限制,但它仅在功能应用程序的一个实例上受尊重。请注意,功能应用可以根据负载进行扩展。它可以在同一个功能应用程序实例中同时处理多个请求,此外还可以创建该应用程序的新实例。在同一实例中的并发请求将支持池大小设置。但是对于多个实例,每个实例最终使用40个池。

即使持久性函数中的并发调节器也不能解决此问题,因为它们仅在单个实例中进行调节,而不是跨实例。

如何在不使连接最大化的情况下将PostgreSQL与Azure函数一起使用?

不幸的是,功能应用程序未提供执行此操作的本机方法。请注意,连接池的大小不是由函数运行时管理的,而是由npgsql的库代码管理的。在不同实例上运行的该库代码无法互相交谈。

请注意,这是使用共享资源的经典问题。在这种情况下,您有50个这些资源。支持更多消费者的最有效方法是减少每个消费者使用资源的时间。 大幅减少Connection Idle Lifetime可能是最有效的方法。增加Timeout确实有助于减少错误(这是一个不错的选择),但不会增加吞吐量。它只是减轻负载。减少Maximum Pool size也很好。

从共享资源的锁定角度来考虑。您可能希望在最短的时间内获取锁。打开连接后,它将锁定50个连接中的一个。通常,SQL库会进行池化,并保持连接打开以节省每个新连接所涉及的初始设置时间。但是,如果这限制了并发性,那么最好尽快终止空闲连接。在单个应用程序实例中,当达到最大池大小时,库会自动执行此操作。但是在多个实例中,它无法终止另一个实例的连接。

要注意的一件事是减少Maximum Pool Size并不一定会限制应用程序的并发性。在大多数情况下,它只是减少了 idle 连接的数量-付出的代价是-在以后需要建立新连接时支付初始建立时间。

更新

WEBSITE_MAX_DYNAMIC_APPLICATION_SCALE_OUT可能会有用。您可以将其设置为5,将池大小设置为8或类似的值。如果减少Maximum Pool SizeConnection Idle Lifetime没有帮助,我会走这种方式。

答案 2 :(得分:0)

这是一个使用静态HttpClient的示例,您应该考虑使用此方法,这样就无需显式管理连接,而只需让客户端即可进行连接:

public static class PeriodicHealthCheckFunction
{
    private static HttpClient _httpClient = new HttpClient();

    [FunctionName("PeriodicHealthCheckFunction")]
    public static async Task Run(
        [TimerTrigger("0 */5 * * * *")]TimerInfo healthCheckTimer,
        ILogger log)
    {
        string status = await _httpClient.GetStringAsync("https://localhost:5001/healthcheck");

        log.LogInformation($"Health check performed at: {DateTime.UtcNow} | Status: {status}");
    }
}