链接数从异常到 300 到 5(RDS MySQL 的一个大坑•后记)

《记 RDS MySQL 的一个大坑》 中,我提到遇到 User juxxxxxxxxxx already has more than 'max_user_connections' active connections…… 这样的错误,最终经过在循环中使用 Thread.Sleep,下降 CRUD 操做的频率,让链接数降低至不到原来的一半,从而解决了这个棘手的问题,有兴趣的朋友能够点击连接回顾一下html

今天又看了一下添加 Thread.Sleep 后,程序运行时的 IOPS 和 链接数:mysql

iops-connections-3

运行结果:链接数:300,运行时间:68 分钟,IOPS:7sql

昨天在博客园中发出上篇文章后,热心的朋友(@沈赟@不知道风往哪儿吹)对此问题提出了宝贵的意见和想法,激发了我对此问题继续深究的决定。数据库

下午通过几个小时的分析和测试,终于找到了该问题的真正缘由和更好的解决方法,在此作个补充。缓存

真正的缘由在于:使用 MySQL 官方提供的 MySql.Data 做为驱动程序链接 MySQL 数据库的时候,默认使用了链接池,才引起了这个问题。错怪了阿里云(上篇中提到怀疑阿里云改了 MySQL 底层作了限制),在此对本身的不严谨表示诚恳的道歉😥😥😥,凡事要本身多思考多研究多求证,不可轻易怀疑权威的力量,切记切记!!!服务器

下面聊一聊该问题出现的真正缘由和更优的解决方法。socket

MySQL 链接池

根据官方介绍:MySQL Connector/NET 中(即 MySql.Data 中)链接池的工做的机制是,当客户端配置 MySqlConnection 时,链接池经过保持一组与服务器的本地链接使其处于活动状态,随后,若是打开一个新的 MySqlConnection 对象,它将从链接池中建立链接,而不是从新建立一个新的本地链接。这样即可以重用数据库链接,避免了频繁建立、释放链接引发的大量性能开销,这有助于缩短响应时间、统一管理、提升运行性能等等。ide

在软件开发中,大多数状况下,数据库链接都有重用的可能,即使永不重用,链接池也有本身的回收机制在适当的时候释放资源,这有点像带有过时时间的缓存数据,也像 .NET 的 GC 回收机制。正由于在大多数状况下,它能够提升运行的性能,也有完善且可配置的回收机制。因此在没有提供任何链接池选项的状况下,MySQL Connector/NET 默认启用链接池,也就是说,建立 MySqlConnection 时使用下面的链接字符串:性能

server=xxx;port=3306;userid=myuserid;password=pwd123;database=db125;charset=utf8;

等同于使用:测试

server=xxx;port=3306;userid=myuserid;password=pwd123;database=db125;charset=utf8;Pooling=true;

而我原来的代码中刚好用的就是前者,也就是说默认启用了链接池。

为何使用链接池反而出问题了

上面说到链接池有那么多的好处,为何我用了链接池反倒出问题了呢?咱们来看一下

链接对池资源的利用状况:

官方文档:
Connector/NET runs a background job every three minutes and removes connections from pool that have been idle (unused) for more than three minutes. The pool cleanup frees resources on both client and server side. This is because on the client side every connection uses a socket, and on the server side every connection uses a socket and a thread.

译文:
Connector/NET 每三分钟运行一次后台做业,从链接池中删除闲置(未使用)超过三分钟的链接。链接池清理会释放客户端和服务器端的资源。这是由于在客户端,每一个链接使用一个套接字,而在服务器端,每一个链接都使用一个套接字和一个线程。

上一篇中有介绍过个人程序的基本状况,这里有必要再补充一下关键的使用场景:

咱们的 MySql 服务实例有不少台,每台实例上有不少个数据库,只有其中一台 MySql 服务实例出现了超出 max_user_connections 的异常,这台实例最大的链接数限制在 600,可是这台实例上的数据库就有 700 多个。

聪明的朋友看到这里,估计已经明白为何使用了链接池会出现问题了。为何呢?就由于上面提到的链接池每三分钟运行一次清理操做呗。循环语句执行的速度是很快的,有的小库瞬间就执行完了,可是在链接池中却保持了一个链接,尚未到每隔三分钟的资源回收时间(这也是我在上篇中添加了 Thread.Sleep 后链接数减小的缘由)。当这台实例的 600 个链接被所有占满时,再链接同一实例上另外一个链接池中没有缓存的数据库时,就报了超出 max_user_connections 的异常。

解决方法

怎么解决呢?最简单的解决方法就是,判断请求的是这台 MySql 服务实例时,不使用链接池,这样就会在调用 MySqlConnection 的 Close 方法时,当即释放客户端和服务端所占用的资源。所以,在数据库链接字符串中加上 Pooling=false,改为下面这样:

server=xxx;port=3306;userid=myuserid;password=pwd123;database=db125;charset=utf8;Pooling=false;

而后发布到服务器上进行测试,查看一下程序运行时的 IOPS 和 链接数:

pooling-false-result

惊不惊喜,意不意外,嚯嚯嚯~~~🥰🥰🥰

运行结果:链接数只有 5 个,运行时间缩短到了 8 分钟,IOPS 为 36,与以前添加 Thread.Sleep 的测试结果相比,天壤之别呀……

最后,用一张图来描述一下两种解决方法的运行效果比较:

activity-diagram

结论

阿里云 RDS MySQL 没有问题,问题出在,在不恰当的场景使用了 MySQL 链接池,链接池虽好,但不可乱用哟,切记切记!

做者 : 技术译民
出品 : 技术译站

相关文章
相关标签/搜索