使用 Azure SQL 和 Azure AppServices 时忽略最大池大小

Coc*_*oco 6 connection-pooling asp.net-web-api2 azure-app-service-plans azure-sql-database azure-web-app-service

我正在开发一个 ASP.NET Web API 项目(完整的 .NET Framework 4.6.1)并使用 Azure SQL 数据库,该 API 部署在 Azure AppService 上。关于服务层,我们在 Azure SQL 数据库 (50 DTU) 的情况下使用 S2,在部署 API 端点(1 个核心和 1.75 GB RAM)的 AppService 的情况下使用 B1。目前我们正在使用 2 个实例(2 个带有负载均衡器的虚拟机)

我们的 QA 团队正在尝试找出平台的性能能力。他们使用 JMeter 配置了性能测试,其中包括在 60 秒的时间间隔内启动 4000 个请求。

第一次执行性能测试后,HTTP 500 错误的比例非常高,查看日志后,我们发现了很多这样的异常:

System.InvalidOperationException: Timeout expired.  The timeout period elapsed prior to obtaining a connection from the pool.  This may have occurred because all pooled connections were in use and max pool size was reached.
   at System.Data.Common.ADP.ExceptionWithStackTrace(Exception e)
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Data.Entity.SqlServer.DefaultSqlExecutionStrategy.<>c__DisplayClass4.<<ExecuteAsync>b__3>d__6.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Data.Entity.SqlServer.DefaultSqlExecutionStrategy.<ExecuteAsyncImplementation>d__9`1.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Data.Entity.Core.EntityClient.EntityConnection.<OpenAsync>d__8.MoveNext()
Run Code Online (Sandbox Code Playgroud)

我首先想到的是连接泄漏问题,我们正在检查代码,并使用 sp_who2 命令监视 SQL Server 上的连接,但连接正在按预期进行处理。

我们使用的注入容器在每次必须处理新请求时创建实体框架上下文(查询是异步的),当请求结束时会自动释放实体框架上下文(作用域依赖项)。

我们得出的结论是,我们需要增加连接池的大小,以缓解大流量负载场景下的超时问题。

在互联网上快速搜索后,我发现 Max Pool Size 值的默认值为 100:

https://www.connectionstrings.com/all-sql-server-connection-string-keywords/

我决定将值增加到 400:

Server=tcp:XXXX.database.windows.net,1433;Initial Catalog=XXXX;Persist Security Info=False;User ID=XXXX;Password=XXXXXXXXXXXX;MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Max Pool Size=400;
Run Code Online (Sandbox Code Playgroud)

重复性能测试后,我们惊讶地发现我们没有注意到任何改进,因为我们收到的 HTTP 500 错误比例相同。我们得出的结论是最大池大小被忽略了。

接下来我们要做的就是在性能测试期间监视 SQL Server,以了解每个主机进程打开了多少个会话,此时我们使用以下 SQL 语句来实现此目的:

SELECT        
COUNT(*) AS sessions, 
host_name, 
host_process_id, 
program_name, 
DB_NAME(database_id) AS database_name
FROM            
sys.dm_exec_sessions AS s
WHERE        
(is_user_process = 1) AND 
(program_name = '.Net SqlClient Data Provider')
GROUP BY host_name, host_process_id, program_name, database_id
ORDER BY sessions DESC
Run Code Online (Sandbox Code Playgroud)

在监视每个主机进程(部署 API 端点的虚拟机)打开的会话后,我们发现每个虚拟机仅创建了 128 个数据库会话。

此时我想到了几个可以解释这种奇怪行为的选项:

  • 记住连接池是一个属于客户端的概念,我首先想到的是 IIS 应用程序池中的某种参数负责这种行为。
  • 另一种选择是每个主机进程和数据库登录只能打开 128 个会话。我在互联网上没有找到任何指向此的内容..但在其他数据库(如 Oracle)中可以配置此约束以限制每次登录打开的会话数量。
  • 最后一个选项..在一些博客和stackoverflow线程中我读到我们收到的异常(从池中获取连接之前超时时间已过。这可能是因为所有池连接都在使用中并且最大池大小已达到)可能会产生误导,并且存在其他问题导致异常的可能性..

快速解决方案是禁用连接字符串中的池,但这是我要做的最后一件事..

另一种解决方案是扩展 AppService 以添加更多 VM 实例,但这在金钱方面是昂贵的。

任何人都知道 Azure AppServices 中是否存在某种限制,这解释了为什么启用连接池时仅打开 128 个会话?

Dan*_*man 2

连接池超时通常是一种症状而不是原因。如果您在 S2 上达到 120 个最大并发请求的限制,则向数据库发起更多连接/查询不会提高吞吐量。您向其投入的其他工作将排队,包括新的连接请求。我不确定这是否会导致在实际达到最大大小之前出现连接池超时错误。

JMeter 测试的构建方式可能会影响结果。出于容量测试的目的,请确保 QA 正在执行相当缓慢的提升,直到达到错误 SLA。您还可以尝试迁移到 S3 或更高版本,看看是否可以缓解连接问题。