EFCore連線池的坑 差點晚年不保
長話短說
上個月公司上線了一個物聯網資料科學專案,我主要負責前端接受物聯網事件,並提供 引數下載。
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例項, 可以想象到
① 在高併發請求下,連線數不斷累積,最終某時刻會超過 Azure 的連線限制數量。
② 頻繁建立和銷燬 DbContext 例項,影響App Service自身效能。
EFCore2.0 為DbContext引入新的註冊方式, 能夠透明的註冊一個 DbContext例項池:
services.AddDbContextPool<CarModelContext>(options => options.UseSqlServer(Configuration.GetConnectionString("SQL")));
- 一如既往支援lambda方式註冊連線字串
- 預設的連線池數量為 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 特性, 能有效提高查詢吞吐量
② 嘗試使用SQL Server 內建指令碼自證會話中有效連線數
+ https://stackoverflow.com/questions/48443567/adddbcontext-or-adddbcontextpool
+ https://docs.microsoft.com/en-us/ef/core/what-is-new/ef-core-2.0#dbcontext-pooling
+ https://www.mssqltips.com/sqlservertip/5507/understanding-and-using-sysdmexecsessions-in-sql-server/