文章出處

在昨天的博文中,我們堅持認為數據庫連接數過萬是阿里云RDS的問題,但后來阿里云提供了當時的數據庫連接情況,讓我們動搖了自己的想法。

帳戶 連接數
A 4077
B 3995
C 741
D 698
E 519

上面這5個帳戶產生了10030個數據庫連接,當看前4個帳戶(產生了9511個連接)的名稱時,我們打了一個寒顫 —— 這些都是運行 Linux 上的 ASP.NET Core 站點。。。這不是巧合,其中必有蹊蹺。

隨后,我們觀察了主備庫切換后的 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 的限制沒起作用,不然只會報下面的錯誤,不會連接數一直增長。

Timeout expired. The timeout period elapsed prior to obtaining a connection from the pool. This may have occurred because all pooled connections were in use and max pool size was reached.

我們想來想去,唯一能想得通的解釋是 .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分鐘左右進行訪問測試,問題重現了!比如其中的一次測試,同一個頁面第1次訪問,產生了5個連接;過10分鐘左右再訪問,會新建3個連接變成8個連接;再過10分鐘左右訪問,連接增長到11個。這種連接不能被重用的情況通過 tcp 抓包也可以看出來。如果在很短的時間內訪問,連接數保持不變(連接被重用)。

這個問題不僅在阿里云 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 文件解決了這個問題。操作步驟如下:

1)在 https://github.com/dotnet/corefx/releases 下載 .NET Core 1.1 得到 corefx-1.1.0.zip 文件并解壓。

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:04

7)登錄 Linux 服務器重啟 ASP.NET Core 站點

8)第一次訪問,在數據庫中看到了這些新建的連接,然后停止訪問。。。等了5-6分鐘,這些連接全部消失,和在 Windows 上的表現一致,連接泄露的問題搞定!

連接泄露引起的數據庫連接數過萬的問題,僅僅是因為少寫了1行 Dispose 代碼。

附:我們 build 出來的修復這個問題的 System.Data.SqlClient.dll

【23:15 更新】

更新 System.Data.SqlClient.dll 之后,效果是立竿見影!


文章列表


不含病毒。www.avast.com
arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()