在昨天的博文中,咱们坚持认为数据库链接数过万是阿里云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 以后,效果是立竿见影!