一次Mysql链接池卡死致使服务无响应问题分析(.Net Mysql.Data 6.9.9)

问题:

进程启动后,线程数迅速上升至最小线程数后,缓慢上升(线程池限制)到数千,而后因为线程过多,CPU飙升到90%sql

对外表现为Api无响应或链接超时。数据库

 

背景

有些数据存在于另外一个机房,经过内网专线链接。一个服务程序有4个数据库,其中3个在本地机房,1个在外地。    api

 

各类排查,没有解决。缓存

 

最终的处理方法

 

Dump进程

  1. 使用进程管理器,建立进程Dump文件。
  2. 使用VisualStudio打开该Dump文件并进行托管调试
  3. 查看并行堆栈,发现大部分线程均处于MySql.Data.MySqlClient.MySqlPoolManager.GetPool这个函数的调用中。并在此处进入了本机代码。处于其余调用堆栈的线程屈指可数。

 

 

 

代码分析

  1. 因为Mysql.Data.dll没有对应的pdb文件(Oracle没有提供),因此在Visual Studio中不能进入其中的代码,所以直接反编译,找到该函数,代码以下:

 

 

函数中,第一句的GetKey函数以下,其中有一个lock。其中代码仅仅是赋值,或是在集成认证的状况下才执行。因此卡住的可能性不大。服务器

 

 

第二句是个赋值,且MysqlPoolManager.pools是个字段(field),理论上不会卡住。网络

第二个lock中,若是指定key对应的缓存已存在,则lock会很快返回。若是不存在,则执行new MysqlPool(setttings);函数代码以下:
异步

 

其主要功能有socket

  1. 建立一个事件,用于获取链接时的异步等待
  2. 根据settings持久化设置
  3. 初始化池驱动列表、队列
  4. 按照配置的minSize建立指定数量的链接。
  5. 建立一个过程缓存,代码以下

     

     

5个步骤中,最可能耗时较久的是步骤d。其余步骤理论上不会有问题。函数

步骤d中的代码,虽然就一个函数,可是代码不少。spa

通过不停的查看代码,发现其主要功能是根据链接字符串中的设置,建立一个指定类型的链接。其底层建立代码以下:

 

 

能够看到,任何建立Stream失败的状况都会抛出异常,最终致使链接池建立失败。

其中第一句,GetStream的底层代码以下:

 

 

开始链接(BeginConnect)后,即开始了等待。等待的超时默认值以下:

2147483s,即596h。若是有链接到数据库服务器的网络有问题或其余缘由致使链接不成功,而也未触发其余致使失败的状况,则会一直等下去。若是推断正确,那么全部线程中,必定有线程的调用堆栈在以下位置:

 

 Dump文件中的全部线程堆栈排序,有且仅有一个线程处于该调用堆栈处。高亮行正是上述堆栈的函数名CreateSocketStream上面一行就是WaiteOne。以后进入本机代码。

 

那么根本缘由也就清楚了:一个链接的建立卡住了数据库链接建立,间接卡住了链接池的锁,又间接卡住了其余链接池的使用和建立。致使全部数据库链接不可用。因此,全部进入的请求通过运行,所有堆在GetPool这里。

 

解决方法:

  1. 保证网络正常(跨机房专线稳定性不可控,有人摇晃光纤玩 o(_)o 或者其余缘由致使流量堵塞)
  2. 容易卡的数据库链接分离出去到单独的进程。这样因为不共享锁,因此不会卡住其余线程池的使用和建立。
  3. 须要跨机房的业务,在数据所在机房单独提供api,内网失效时能够走外网。
  4. 容易卡住的线程池链接字符串中设置minPoolSize=0。这样建立链接池时,不预建立链接而影响其余链接池。可是,对于突发流量增加的状况,响应可能不够及时。
  5. 设置一个合理的ConnectionTimeout。能够有效避免链接建立时卡住,致使api无响应和其余反作用。

 

其余在源代码中发现的须要注意的地方

  1. 链接池中空闲链接的空闲时间是180s
  2. 清理周期第一次是188s,以后保持180s
  3. 若是链接池中的空闲链接数大于设置的minPoolSize,则清理空闲链接直到minPoolSize
  4. ConnectionTimeout 用于几个地方
    1. ) 链接socket时的等待超时
    2. ) 链接以后,链接上的读写超时。
    3. ) 从已空且总数达上限的链接池中,等待可用链接时的等待超时

以上全部信息基于.Net版本Mysql.Data 6.9.9版本反编译分析。

相关文章
相关标签/搜索