上個月公司上線了一個物聯網數據科學項目,我主要負責前端接受物聯網事件,並提供 參數下載。前端
webapp 部署在Azure雲上,參數使用Azure SQL Server存儲。 最近從灰度測試轉向全量部署以後,日誌時常收到:web
SQL Session會話超限的報錯。sql
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的使用限制文檔:api
一句話: 付費級別和計算資源大小決定了 Azure SQL最大會話數和請求數。要緩解,要麼升級硬件資源,要麼優化查詢利用率。session
查看使用EFCore訪問 SQL Server的過程, 也是官方默認用法:併發
這意味着每次請求都會建立一個 DbContext實例, 能夠想象到app
① 在高併發請求下,鏈接數不斷累積,最終某時刻會超過 Azure 的鏈接限制數量。框架
② 頻繁建立和銷燬 DbContext 實例,影響App Service自身性能。webapp
EFCore2.0 爲DbContext引入新的註冊方式, 可以透明的註冊一個 DbContext實例池:ide
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/