雲計算之路-阿里雲上:數據庫鏈接數過萬的真相,從阿里雲RDS到微軟.NET Core

在昨天的博文中,咱們堅持認爲數據庫鏈接數過萬是阿里雲RDS的問題,但後來阿里雲提供了當時的數據庫鏈接狀況,讓咱們動搖了本身的想法。html

賬戶 鏈接數
A 4077
B 3995
C 741
D 698
E 519

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

隨後,咱們觀察了主備庫切換後的 RDS 中數據庫鏈接狀況。有一個運行在 Linux 上的 ASP.NET Core 站點,用了3臺服務器,卻產生了1528個數據庫鏈接。github

SELECT * FROM sys.sysprocesses 
WHERE loginame='xxx'

重啓其中1臺服務器上的站點,鏈接數立馬從1528降到了391。什麼狀況?數據庫鏈接池發飆了?sql

繼續觀察,當前數據庫中大量的鏈接都是由運行在 Linux 上的 ASP.NET Core 站點產生的,並且會隨着時間的推移保持增加。數據庫

數據庫鏈接泄漏了,這仍是第1次遇到!可咱們在 APS.NET Core 應用中全部的數據庫操做都用的是Entity Framework Core,不存在沒有及時關閉數據庫鏈接的狀況,惟一能夠懷疑的對象是在 System.Data.SqlClient 中實現的 ADO.NET 數據庫鏈接池。ubuntu

數據庫鏈接池究竟出什麼情況了?咱們在數據庫鏈接字符串中沒有另外設置鏈接池,用的是默認設置(Min_Pool_Size = 0; 與 Max_Pool_Size = 100;)。並且更奇怪的是 Max_Pool_Size 的限制沒起做用,否則只會報下面的錯誤,不會鏈接數一直增加。bash

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 建。更要命的是,這些被無視的鏈接卻一直在保持着與數據庫的鏈接。因而,鏈接泄露成了命中註定。服務器

在有了這個惟一想得通的猜想後,咱們今天開始在測試環境中進行驗證。tcp

部署一個 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 以後,效果是立竿見影!

相關文章
相關標籤/搜索