我有一个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):
基于此,我尝试摆弄Connection Idle Lifetime
,Timeout
和Pooling
之类的Npgsql connection parameters,但是连接过多的问题似乎仍然持续到一个或另一个程度。另外,我尝试限制并发编排器和活动函数的数量(请参见this doc),但这似乎在一定程度上抵消了Azure函数具有可伸缩性的目的。它确实有帮助-我得到的太多连接错误更少了。据推测,如果我继续用较低的数字进行测试,我什至可以消除它,但是,再次看来,这很不合理,并且可能还有另一种解决方案。
如何在不增加连接数的情况下将PostgreSQL与Azure函数一起使用?
答案 0 :(得分:0)
这是Dependency Injection
真正有用的地方。您可以创建一个singleton
客户,它将完美地完成工作。如果您想了解更多有关服务寿命的信息,可以在docs
首先添加此nuget Microsoft.Azure.Functions.Extensions.DependencyInjection
现在添加如下所示的新类并解析您的客户端。
[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 Size
和Connection 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}");
}
}