你认为的.NET数据库连接池,真的是全部吗?

一般我们的项目中会使用1到2个数据库连接配置,同程艺龙的数据库连接被收拢到配置中心,由DBA统一配置和维护,业务方通过某个字符串配置拿到的是Connection对象

DBA能在对业务方无侵入的情况下,让大规模微服务实例切换数据库连接,之后DBA要求对旧数据库的连接必须立即被清空, 那么问题来了: **dotnet程序怎么清空数据库连接?

如果有同学不知道DBA做这个要求的目的,那我啰嗦一下:
应用程序不再使用旧连接时,理论上你的连接池要被完全清空,因为单纯的释放连接,只会让连接池中的Connection处于Sleep状态,依旧维持了短时间的物理连接,这个短时间其实是不必要的占用,影响了旧连接数据库的吞吐量。

前置知识背景

回答这个问题之前, 我们还是先梳理一下:

  • dotnet进程, 数据库进程;
  • 两者都存在连接池,.NET数据库连接池, 数据库连接池。

.net能直接维护的是 .net数据库连接池, 本次我们通过清空.NET数据库连接池,清理应用进程与具体数据库的物理连接

1. .NET数据库连接池的表现

数据库连接是一个耗时的行为,大多数应用程序只使用1到2种数据库连接,为了最小化打开连接的成本,.net使用了一种称为连接池的优化技术。

.NET 数据库连接池程序维护了数据库物理连接,
通过为每个特定的连接配置保持一组活动的连接对象来管理连接。

每当应用程序尝试Open连接,池程序就会在池中找到可用的连接,如果有则返回给调用者;
应用程序Close连接对象时,池程序将连接对象回收到池中(Sleep), 这个连接可以在下一次Open调用中重用。

池初次建立的时候,创建了最小数量的connection对象(默认是0),为满足后续的业务需求,池会渐增connection对象,直至达到Max Pool Size最大连接数(默认是100)。

后续请求如果面临.NET数据库连接池满的情况,将会进队列等待 ,超时15s抛出异常。

2. .NET数据库连接池是如何建立的?

在一个进程中,相同的连接配置才能被池化,.NET为不同连接配置维护了不同的连接池。

当第一个connection对象被open()的时候,将会创建基于该连接字符串的精准算法的连接池对象。

下面是被定义为同一连接池的精准算法: 
 
进程级别、  
连接字符串相同、  
连接字符串关键key顺序相同。  
(同一连接提供的关键字顺序不同将被分到不同的池)。

connectionString的核心字段属性与池化相关的配置字段:

Connection Lifetime (or Load Balance Timeout)
Enlist
Max Pool Size: 默认值100,连接池中最大连接数
Min Pool Size: 默认值0, 意味着最初没有打开的最小连接。
Pooling: 默认值true, 开启池化机制

在一个应用程序中,有如下代码:

using (SqlConnection connection = new SqlConnection(  
  "Integrated Security=SSPI;Initial Catalog=Northwind"))  
    {  
        connection.Open();
        // Pool A is created.  
    }  
  
using (SqlConnection connection = new SqlConnection(  
  "Integrated Security=SSPI;Initial Catalog=pubs"))  
    {  
        connection.Open();
        // Pool B is created because the connection strings differ.  
    }  
  
using (SqlConnection connection = new SqlConnection(  
  "Integrated Security=SSPI;Initial Catalog=Northwind"))  
    {  
        connection.Open();
        // The connection string matches pool A.  
    }  

上面创建了三个Connection对象,但是只形成了两个数据库连接池

还是以上代码,如果有两个相同的应用程序,理论上就形成了四个数据库连接池。

3. 连接池中的连接什么时候被移除?

数据库连接对象,在被释放或者关闭的时候,会回收进连接池。

We strongly recommend that you always close the connection when you are finished using it so that the connection will be returned to the pool. You can do this using either the Close or Dispose methods of the Connection object, or by opening all connections inside a using statement in C#.

连接池中的连接空闲4-8 分钟,池程序会移除这个连接。

应用程序关闭,连接池直接被清空。


还有一个连接字符串参数Connection LifetimeLoad Balance Timeout控制连接的生命周期。


.NET 如何清空.NET连接池?

有了以上知识背景

我们再来回顾一下DBA的要求,切换原连接配置的时候,清空对原数据库的连接。

.NET这边只能操作.NET数据库连接池。

.NET提供ClearAllPools、ClearPool静态方法用于清空连接池, 目前看sqlserver,mysql客户端库均实现了这两个方法。

  • ClearAllPools: 清空与这个DBProvider相关的所有连接池

  • ClearPool(DBConnection conn) 清空与这个连接对象相关的连接池

ClearAllPools() 过于暴力,直接清空了此时与这个provider相关的连接池, 可能对于其他应用进程的数据库连接造成损流,

要使用细粒度的进程相关的 ClearPool(DBConnection conn) 方法, 本应用进程切换数据库连接之后,只清空与本次连接相关的.NET 数据库连接池。

光说不练不验证,不是我的风格。

天锤压测/queryapi 产生一个包含大量连接对象的连接池;
适当的时候,/clearpoolapi清空.NET连接池。

 using MySql.Data.MySqlClient;       // 引入MySql.Data.dll库文件  

 public class MySqlController : Controller
    {
        // GET: MySql
        [Route("query")]
        public string Index()
        {
            var s = "User ID=teinfra_neo_netreplay;Password=123456;DataBase=teinfra_neo_netreplay;Server=10.100.41.196;Port=3980;Min Pool Size=1;Max Pool Size=28;CharSet=utf8;";
            using (var conn = new MySqlConnection(s))
            {
                var comm = conn.CreateCommand();
                comm.CommandText = "select count(*) from usertest;";
                conn.Open();
                var ret = comm.ExecuteScalar();

                comm.CommandText = "select count(*) from information_schema.PROCESSLIST WHERE HOST like  '10.22.12.245%';";
                var len = comm.ExecuteScalar();
                return $"查询结果:{ret} ,顺便查一下当前连接池的连接对象个数: {len}";
            };
        }

        [Route("clearpool")]
        public string Switch()
        {
            var s = "User ID=teinfra_neo_netreplay;Password=123456;DataBase=teinfra_neo_netreplay;Server=10.100.41.196;Port=3980;Min Pool Size=1;Max Pool Size=28;CharSet=utf8;";
            using (var conn = new MySqlConnection(s))
            {
                conn.Open();
                MySqlConnection.ClearPool(conn);
            };

            using (var conn = new MySqlConnection(s))
            {
                conn.Open();
                var comm = conn.CreateCommand();
                comm.CommandText = "select count(*) from information_schema.PROCESSLIST WHERE HOST like  '10.22.12.245%';";
                var len = comm.ExecuteScalar();
                return $"之前已经清空连接池, 此次查询连接池有 {v1}  个连接对象";
            }

        }
    }

1. 经过压测工具

2. mysql数据库对比

mysql的连接数查询命令, (host是web服务器IP):
select * from information_schema.PROCESSLIST WHERE HOST like '10.22.12.245%';

3. 调用/clearpoolapi,清空.NET连接池, 进而直接影响 mysql数据库的物理连接。

bingo,清空.NET连接池===> 清理mysql数据库物理连接 的理论得到验证。

干货旁白

回顾全文:

从本次实践中理解了.NET数据库连接池的定义方式、行为姿势(进程级别.NET 数据库连接池影响数据库的物理连接) 。

对祖传代码的改造,.NET数据获取组件SDK提高了原数据库的吞吐量。

希望本文设计考量、理论+论证的行文思路对于读者有所帮助, 再次感谢有心读者取关、再关注。

posted @ 2021-08-17 10:28  博客猿马甲哥  阅读(1457)  评论(8编辑  收藏  举报