長話短說
上個月公司上線了一個物聯網數據科學項目,我主要負責前端接受物聯網事件,並提供 參數下載。
webapp 部署在Azure雲上,參數使用Azure SQL Server存儲。 最近從灰度測試轉向全量部署之后,日志時常收到:
SQL Session超限報錯。
19/12/18 20:41:18 [Error].[Microsoft.EntityFrameworkCore.Query].[][0HLS3MS83SC3K:00000004].[http://localhost/api/v1/soc-prediction-model/all].[].[GetModeParameters]
An exception occurred while iterating over the results of a query for context type 'Gridsum.SaicEnergyTracker.CarModelContext'. Microsoft.Data.SqlClient.SqlException (0x80131904): Resource ID : 2. The session limit for the database is 300 and has been reached. See 'http://go.microsoft.com/fwlink/?LinkId=267637' for assistance. Changed database context to 'saic-carmodel'. Changed language setting to us_english. at Microsoft.Data.ProviderBase.DbConnectionPool.CheckPoolBlockingPeriod(Exception e) at Microsoft.Data.ProviderBase.DbConnectionPool.CreateObject(DbConnection owningObject, DbConnectionOptions userOptions, DbConnectionInternal oldConnection) at Microsoft.Data.ProviderBase.DbConnectionPool.UserCreateRequest(DbConnection owningObject, DbConnectionOptions userOptions, DbConnectionInternal oldConnection) at Microsoft.Data.ProviderBase.DbConnectionPool.TryGetConnection(DbConnection owningObject, UInt32 waitForMultipleObjectsTimeout, Boolean allowCreate, Boolean onlyOneCheckConnection, DbConnectionOptions userOptions, DbConnectionInternal& connection) at Microsoft.Data.ProviderBase.DbConnectionPool.WaitForPendingOpen() --- End of stack trace from previous location where exception was thrown --- at Microsoft.EntityFrameworkCore.Storage.RelationalConnection.OpenDbConnectionAsync(Boolean errorsExpected, CancellationToken cancellationToken) at Microsoft.EntityFrameworkCore.Storage.RelationalConnection.OpenDbConnectionAsync(Boolean errorsExpected, CancellationToken cancellationToken) at Microsoft.EntityFrameworkCore.Storage.RelationalConnection.OpenAsync(CancellationToken cancellationToken, Boolean errorsExpected) at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReaderAsync(RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken) at Microsoft.EntityFrameworkCore.Query.RelationalShapedQueryCompilingExpressionVisitor.AsyncQueryingEnumerable`1.AsyncEnumerator.MoveNextAsync()
排查
我在Azure上使用的是 SQL Server Basic Edition(好歹也是付費版),全量發布至今,日均SQL訪問次數約為10000,查詢了Azure SQL的使用限制文檔:
一句話: 付費級別和計算資源大小決定了 Azure SQL最大會話數和請求數。要緩解,要么升級硬件資源,要么優化查詢利用率。
查看使用EFCore訪問SQL Server的過程, 也是官方默認用法:
- 在依賴注入框架 注冊一個自定義的 DbContext類型
- 在 Controller 構造函數中獲取 DbContext實例
這意味着每次請求都會創建一個DbContext實例, 可以想象到
主: 頻繁創建和銷毀DbContext 實例,影響App Service自身性能。
次: 在急缺CPU資源下,連接會話來不及釋放,會話數不斷累積,最終某時刻會超過Azure的會話限制數。
EFCore2.0 為DbContext引入新的注冊方式, 能夠透明的注冊一個 DbContext實例池:
services.AddDbContextPool<CarModelContext>(options => options.UseSqlServer(Configuration.GetConnectionString("SQL")));
- 一如既往支持lambda方式注冊連接字符串
- 默認的DbContext Pool實例數量為 128
- 每次使用完DbContext不會釋放對象,而是重置並回收到DBContextPool
Web程序中通過重用池中DbContext實例可增加高並發場景下的吞吐量, 這在概念上類似於ADO.NET Provider原生的連接池操作方式,具有節省DbContext實例化成本的優點, 這也是EFCore2.0 其中一個性能亮點。
這么重要的使用方式竟然不在 Microsoft doc 默認Demo中推薦使用,真是一個坑。
修改代碼重新部署之后,歷經幾天測試,暫時未出現最開始的SqlException異常。
驗證
回過頭隨機驗證SQL Server 有連接的會話數:
SELECT DEC.session_id, DEC.protocol_type, DEC.auth_scheme,
DES.login_name, DES.login_time
FROM sys.dm_exec_sessions AS DES
JOIN sys.dm_exec_connections AS DEC
ON DEC.session_id = DES.session_id;
總結
① 提示EFCore2.0 新推出的DbContextPool 特性, 提高App Service 性能。
② 嘗試使用SQL Server 內置腳本驗證當前有效連接的 會話數
--------------------------------------20191222 更新------------------------
查閱資料, DbContext 確實是EFCore連接會話的概念,EFCore2.0推出的DbContextPool 通過減少DBContext頻繁實例化、銷毀的成本來提高程序性能,
概念上類似 Ado.Net原生的連接池的概念(Ado.NET連接池是更重更底層的資源, 效果會更好)。
至於SQL查詢吞吐量,DbContextPool 並沒有很直接的證據能證明對SQL吞吐量有改善,目前的證據顯示DbContextPool 因為app 性能提升,前端處理請求的吞吐量有20% 提升。
鑒於沒有實際證據證明對SQL查詢吞吐量有改善,本文不再將 數據庫底層的改善直接歸功於使用DbContextPool, 因此本文刪除了上面的關聯關系,但是DbContextPool 還是應該作為默認實踐。
+ https://github.com/aspnet/EntityFrameworkCore/issues/9426
+ https://github.com/aspnet/EntityFrameworkCore/issues/10125
+ https://stackoverflow.com/questions/48443567/adddbcontext-or-adddbcontextpool
+ https://www.mssqltips.com/sqlservertip/5507/understanding-and-using-sysdmexecsessions-in-sql-server/