雲計算之路-阿裡雲上:資料庫連接數過萬的真相,從阿裡雲RDS到微軟.NET Core
在昨天的博文中,我們堅持認為資料庫連接數過萬是阿裡雲RDS的問題,但後來阿裡雲提供了當時的資料庫連接情況,讓我們動搖了自己的想法。
帳戶連接數A4077B3995C741D698E519上面這5個帳戶產生了10030個資料庫連接,
隨後,我們觀察了主備庫切換後的 RDS 中資料庫連接情況。有一個運行在 Linux 上的 ASP.NET Core 網站,用了3台伺服器,卻產生了1528個資料庫連接。
SELECT * FROM sys.sysprocesses WHERE loginame='xxx'重啟其中1台伺服器上的網站,連接數立馬從1528降到了391。什麼情況?資料庫連接池發飆了?
繼續觀察,當前資料庫中大量的連接都是由運行在 Linux 上的 ASP.NET Core 網站產生的,
資料庫連接洩漏了,這還是第1次遇到!可我們在 APS.NET Core 應用中所有的資料庫操作都用的是Entity Framework Core,不存在沒有及時關閉資料庫連接的情況,唯一可以懷疑的物件是在 System.Data.SqlClient 中實現的 ADO.NET 資料庫連接池。
資料庫連接池究竟出什麼狀況了?我們在資料庫連接字串中沒有另外設置連接池,用的是默認設置(Min_Pool_Size = 0; 與 Max_Pool_Size = 100;)。而且更奇怪的是 Max_Pool_Size 的限制沒起作用,
我們想來想去,唯一能想得通的解釋是 .NET Core 的資料庫連接池發生了這樣的狀況 —— 連接池中已經創建的連接無法被重用,不僅如此,而且它們直接被 SqlClient 給無視了,都沒有被計算在 Pool Size 中,所以根本觸發不了 Max_Pool_Size 的限制,造成連接無限制,任由 SqlClient 建。更要命的是,這些被無視的連接卻一直在保持著與資料庫的連接。於是,連接洩露成了命中註定。
在有了這個唯一想得通的猜測後,我們今天開始在測試環境中進行驗證。
部署一個 ASP.NET Core 網站,創建一個專用資料庫連接帳戶,然後用下面的 SQL 語句查看資料庫連接是否被重用,同時在測試伺服器用 tcpdump 進行抓包,並且分別用阿裡雲 RDS 與我們自己搭建的 SQL Server 伺服器進行測試。
SELECT * from sys.sysprocesses where loginame='測試專用帳戶'如果連接池正常工作,第1次訪問,新建所需的資料庫連接;第2次訪問同樣的頁面,應該重用已有的資料庫連接,
開始測試時,不管連接阿裡雲 RDS 還是我們自己的 SQL Server,連接池都工作正常,連接能被重用。
後來分析了一下,雖然生產環境中連接數一直在增長,但增長速度不是很快,可能問題的發生需要一定的時間間隔,或許連接閒置超過一定時間之後才不會被重用。
於是,我們間隔了10分鐘左右進行訪問測試,問題重現了!比如其中的一次測試,
這個問題不僅在阿裡雲 RDS (SQL Server 2008 R2)可以重現,而且在我們自己搭建的 SQL Server 2014 也能重現,問題的真相隨之水落石出。
資料庫連接數過萬問題不是阿裡雲 RDS 的問題,而是 .NET Core 中 System.Data.SqlClient的連接池在 Linux 上的實現問題,我們錯怪了阿裡雲,輕信了微軟。這是我們使用阿裡雲以來對阿裡雲最大的一次誤會,這是我們 .NET Core 遷移過程中遇到的最大的一個坑。
為什麼最近才出現這個問題?是因為我們最近將更多網站遷移到了 ASP.NET Core ,而且將之前一些跑在 Windows 上的 ASP.NET Core 網站切換到了 Linux 。
如何解決這個問題?我們會察看一下 System.Data.SqlClient 的實現代碼,看能否找到實現層面的線索。阿裡雲會進一步驗證這個問題,如果確認是微軟實現上的問題,會與微軟溝通解決。
【16:55 更新】
我們在 Windows 上進行對比測試發現,在 Windows 上連接池中閒置的資料庫連接過段時間會被自動關閉,與上面 Linux 同樣的測試場景,間隔10分鐘後查看,資料庫連接全消失了。
【18:18 更新】
感謝 @feiyun0112在評論中提供的線索,2016年11月7日就有人發現了這個問題,並且在 github 上提交了issue。
【18:41 更新】
我們在應用中使用的 System.Data.SqlClient.dll 版本是 4.3.0,是在2016年11月5日生成的,正好在這個 issue之前。
【20:56 更新-成功解決】
通過手動替換 System.Data.SqlClient.dll 文件解決了這個問題。操作步驟如下:
2)在 corefx-1.1.0 檔中運行 init-tools.cmd 命令安裝 build 工具
3)用 VS2017 打開 corefx-1.1.0\src\System.Data.SqlClient 中的 System.Data.SqlClient.sln 解決方案
4)打開 SNITcpHandle.cs ,去掉 private readonly NetworkStream _tcpStream; 中的 readonly ,在 Dispose 方法中添加如下代碼:
if (_tcpStream != null) { _tcpStream.Dispose; _tcpStream = null; }5)用 VS2017 以 Release 方式 build System.Data.SqlClient 項目。
6)將 corefx-1.1.0\bin\Unix.AnyCPU.Release\System.Data.SqlClient 資料夾中生成的 System.Data.SqlClient.dll 檔,在 git bash 中通過 scp 命令上傳到 Linux 伺服器上的 nuget 資料夾。
MINGW64 /c/Dev/GitHub/corefx-1.1.0/bin/Unix.AnyCPU.Release/System.Data.SqlClient $ scp System.Data.SqlClient.dll root@ubuntu-server:~/.nuget/packages/system.data.sqlclient/4.3.0/runtimes/unix/lib/netstandard1.3 System.Data.SqlClient.dll 100% 708KB 176.9KB/s 00:047)登錄 Linux 伺服器重啟 ASP.NET Core 網站
8)第一次訪問,在資料庫中看到了這些新建的連接,然後停止訪問。。。等了5-6分鐘,這些連接全部消失,和在 Windows 上的表現一致,連接洩露的問題搞定!
連接洩露引起的資料庫連接數過萬的問題,僅僅是因為少寫了1行 Dispose 代碼。
附:我們 build 出來的修復這個問題的 System.Data.SqlClient.dll
【23:15 更新】
更新 System.Data.SqlClient.dll 之後,效果是立竿見影!
為什麼最近才出現這個問題?是因為我們最近將更多網站遷移到了 ASP.NET Core ,而且將之前一些跑在 Windows 上的 ASP.NET Core 網站切換到了 Linux 。
如何解決這個問題?我們會察看一下 System.Data.SqlClient 的實現代碼,看能否找到實現層面的線索。阿裡雲會進一步驗證這個問題,如果確認是微軟實現上的問題,會與微軟溝通解決。
【16:55 更新】
我們在 Windows 上進行對比測試發現,在 Windows 上連接池中閒置的資料庫連接過段時間會被自動關閉,與上面 Linux 同樣的測試場景,間隔10分鐘後查看,資料庫連接全消失了。
【18:18 更新】
感謝 @feiyun0112在評論中提供的線索,2016年11月7日就有人發現了這個問題,並且在 github 上提交了issue。
【18:41 更新】
我們在應用中使用的 System.Data.SqlClient.dll 版本是 4.3.0,是在2016年11月5日生成的,正好在這個 issue之前。
【20:56 更新-成功解決】
通過手動替換 System.Data.SqlClient.dll 文件解決了這個問題。操作步驟如下:
2)在 corefx-1.1.0 檔中運行 init-tools.cmd 命令安裝 build 工具
3)用 VS2017 打開 corefx-1.1.0\src\System.Data.SqlClient 中的 System.Data.SqlClient.sln 解決方案
4)打開 SNITcpHandle.cs ,去掉 private readonly NetworkStream _tcpStream; 中的 readonly ,在 Dispose 方法中添加如下代碼:
if (_tcpStream != null) { _tcpStream.Dispose; _tcpStream = null; }5)用 VS2017 以 Release 方式 build System.Data.SqlClient 項目。
6)將 corefx-1.1.0\bin\Unix.AnyCPU.Release\System.Data.SqlClient 資料夾中生成的 System.Data.SqlClient.dll 檔,在 git bash 中通過 scp 命令上傳到 Linux 伺服器上的 nuget 資料夾。
MINGW64 /c/Dev/GitHub/corefx-1.1.0/bin/Unix.AnyCPU.Release/System.Data.SqlClient $ scp System.Data.SqlClient.dll root@ubuntu-server:~/.nuget/packages/system.data.sqlclient/4.3.0/runtimes/unix/lib/netstandard1.3 System.Data.SqlClient.dll 100% 708KB 176.9KB/s 00:047)登錄 Linux 伺服器重啟 ASP.NET Core 網站
8)第一次訪問,在資料庫中看到了這些新建的連接,然後停止訪問。。。等了5-6分鐘,這些連接全部消失,和在 Windows 上的表現一致,連接洩露的問題搞定!
連接洩露引起的資料庫連接數過萬的問題,僅僅是因為少寫了1行 Dispose 代碼。
附:我們 build 出來的修復這個問題的 System.Data.SqlClient.dll
【23:15 更新】
更新 System.Data.SqlClient.dll 之後,效果是立竿見影!