1. 程式人生 > >EFCore連線池的坑 差點晚年不保

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/