ABP事务型工作单元压力测试失败问题求助
ABP事务工作单元并发压力测试异常问题
压力测试场景
并发调用以下API接口500次:
[UnitOfWork(IsDisabled = true)] [AbpAllowAnonymous] public async Task StressTest(string input) { using (var uow = UnitOfWorkManager.Begin ( new UnitOfWorkOptions { Scope = System.Transactions.TransactionScopeOption.RequiresNew, IsTransactional = true, IsolationLevel = System.Transactions.IsolationLevel.ReadCommitted, AsyncFlowOption = System.Transactions.TransactionScopeAsyncFlowOption.Enabled }) ) { var data = await _userRepository.GetAll() .Where(x => x.EmailAddress == input) .Select(x => new { x.FirstName, x.CreationTime, x.IsDeleted }) .AsNoTracking() .ToListAsync(); await uow.CompleteAsync(); } }
依赖包版本与数据库
- aspnetboilerplate v3.6.1
- aspnetcore v2.2.0
- entityframeworkcore v2.1.0
- Azure SQL
问题概述
- 整个应用无响应,所有涉及SQL的HTTP请求挂起直至超时
- 使用非事务型工作单元(UoW)时测试正常通过
- 该查询在非压力测试场景下仅需50ms即可完成
- 问题在生产与本地环境均能复现
- 所有监控资源均未达到上限
- 审计功能已关闭
压力测试期间监控的资源
- CPU利用率
- 内存利用率
- 完成线程(Completion Threads)
- 工作线程(Worker Threads)
- SQL会话
- SQL工作进程
- SQL CPU利用率
- SQL锁
- 出站端口
已尝试的解决方案
- 使用ABP默认初始化的工作单元
- 针对自定义控制器而非应用服务进行压力测试
- 修改事务隔离级别(IsolationLevel)
- 增加连接线程池的最小与最大线程数
- 增加工作线程与完成线程的最小数量
- 不调用工作单元的完成方法
- 开启/关闭数据库连接池
预期ABP应能处理500次并发更新类HTTP请求,推测ABP内部事务管理存在严重问题,但缺乏有效调试手段。
补充说明1
不依赖ABP工作单元,手动开启事务时可顺利完成压力测试,排除Azure SQL与EF Core自身问题,确定问题源于ABP:
[UnitOfWork(IsDisabled = true)] [AbpAllowAnonymous] public async Task StressTest(string input) { using (var tx = await _ctx.Database.BeginTransactionAsync(IsolationLevel.ReadCommitted)) { var data = await _ctx.Users .Where(x => x.EmailAddress == input) .Select(x => new { x.FirstName, x.CreationTime, x.IsDeleted }) .AsNoTracking() .ToListAsync(); tx.Commit(); } }
补充说明2
监控数据库活跃事务时发现明显差异:手动开启事务时,事务会快速创建并关闭,无堆积;而ABP管理事务时,事务会在数据库中持续堆积直至达到连接池上限(例如最大连接数设为200时,会堆积200个事务)。
用于监控活跃事务的SQL查询:
SELECT [s_tst].[session_id], [database_name] = DB_NAME (s_tdt.database_id), [s_tdt].[database_transaction_begin_time], [sql_text] = [s_est].[text] FROM sys.dm_tran_database_transactions [s_tdt] INNER JOIN sys.dm_tran_session_transactions [s_tst] ON [s_tst].transaction_id = [s_tdt].[transaction_id] INNER JOIN sys.dm_exec_connections [s_ec] ON [s_ec].[session_id] = [s_tst].[session_id] CROSS APPLY sys.dm_exec_sql_text ([s_ec].most_recent_sql_handle) AS [s_est];
内容的提问来源于stack exchange,提问作者Dani Mazahreh
相关产品推荐
相关产品推荐

