应用端连接MySQL数据库报Communications link failure
事情的起因:
某项目的开发同学突然Q我们组的某同学,要求我们调整MySQL的连接等待超时参数wait_timeout。要求我们从28800s调整到31536000s(也就是一年)
应用端测试环境的tomcat报错日志如下图:
恩。报错很明显。这个问题百度后的解决方案大部分都是要求数据库端更改连接等待超时时间。那么这种解决方法是否可行呢?
遗憾的是,这是不可行的。
主要原因还是性能考量。Wait_timeout参数的含义是指MySQL将断开指定时间内没有任何操作的连接(Connection)。这个值会直接影响到MySQL数据库的并发性能,因为Connection是由参数max_connections设置的。
在高并发场景中,由于我们的连接数已经封顶,过长的wait_timeout值会导致连接长时间不释放(如像开发同学提出修改为一年)。如果开发人员没有在代码中显式关闭连接,或者使用连接池时没有定义连接回收方式,那么连接数将会随时间递增,最后达到参数max_connections的设定值后,报经典的1040错误,too many connections...
所以为了避免出现这种情况,DBAs的立场是非常明确的,绝对不允许轻易修改生产环境数据库的参数。这个原因我后面给出。那么现在我们继续focus到眼前的问题。
既然不允许更改数据库参数,但是问题还在,那么如何定位问题的真正原因呢?这里我将根据我在处理这个问题时的思路,引导大家掌握基本的排障方法。
问题定位:
连接出现问题,那么肯定要涉及到两个主要的部分:数据库和webserver的应用连接池。由于出于对自己工作的过度自信(笑),所以针对webserver端我问了开发同学几个问题:
1. 是否使用了连接池?如果不是,是否有在代码里显式关闭连接?
2. 连接池是否配置了连接自动回收机制(如tomcat的话可能要涉及removeAbandoned和removeAbandonedTimeout等参数)?
开发同学给出的答案是:
1. 确实使用了连接池(dbcp原生的)。
2. 经过和运维相关同学协调,发现tomcat里确实缺少连接回收的参数。
解决方案:
OK,那么我基本可以定位问题了,下面就是给出解决方案:
1. Tomcat增加removeAbandoned=true、removeAbandonedTimeout=60、testOnBorrow=true和validationQuery=select now()。目的是让webserver的连接池自己在连接前先判断数据库侧是否已经将连接释放掉,如果释放掉则会回收并重建连接。注:removeAbandoned和removeAbandonedTimeout两个参数我在给出时略有犹豫,因为这两个参数可能会导致连接在removeAbandonedTimeout所设置的时间内没有处理完时,也会被连接池强制回收,从而导致请求没有返回数据就断开了。所以一般生产环境中不会设置这两个参数,不过在得知是测试环境后,我决定先尝试一下看是否能定位问题就是连接池。
2. 如果上述方法不能解决,那么可以尝试更换c3p0这样的第三方连接池。
3. 如果更换c3p0还不能解决,那么请检查综测环境里的这台机器是否开启了防火墙,或者是否selinux的策略有问题。这样做主要是我发现错误日志中的连接断开时间非常有规律。如下图:
因此,可能是某种机制导致人为断开连接。所以在前两种方法都不能解决的情况下,可以尝试使用tcpdump等工具抓包,检查当前系统网络环境。
经过一番痛苦的尝试,终于在更换了c3p0的第三方连接池后,一切都正常了。
后记:
这个小故事,我总结了下面几点请大家借鉴:
1. 所有你看到的故障只是它在某一时间段的某一表象。那么怎么根据这些表象迅速定位问题呢?除了工作经验的积累外,还要掌握把分散的知识点联系起来的能力。这在处理故障时会大有用处。
2. 开发人员不要轻易要求DBA修改数据库。包括参数、数据等等等等。前文提到不允许轻易修改生产数据库的参数,这也是我在每次分享时都要强调的。数据是一个公司的核心,数据库是存放这一核心的工具。数据库最大的特点是稳定,也最需要稳定。不管前端应用开发的多么花里胡哨,实现了多么复杂的逻辑,如果数据库没有稳定支撑,而是在不断变动,那么应用只会出现展现数据不正确(错误数据)或者数据不一致(脏数据)的情况。所以烦请开发同学们对每次提给数据库组的请求一定要慎之又慎。
能在数据库之前就有解决方案的,就绝对不要放到数据库上做。
3. 尽可能的多了解自己工作之外的世界。做技术要有刨根问底的精神,多了解些和本职工作无关的,甚至是其他人的本质工作,你会发现你的职业道路会越走越宽~
PS:
MySQL的Connection Pool和Thread Pool之间的关系
很多人会混淆这两个概念。在one-thread-per-connection的传统配置里,连接和线程就是1对1的关系。但是在thread pool的概念提出后,这种情况就不再是这样的了。这里我用不是DBA就能看懂的语言简单的解释一下:
连接池实现在Client端。由于Client端频繁的创建和释放连接会增加请求的平均响应时间,因此Client端往往会预先创建一些连接,通过这些连接来完成针对数据库的所有请求。在Client端请求繁忙时,还可以通过请求排队机制,缓解数据库并发压力。
线程池实现在Server端(数据库端)。线程池和连接池有点类似,MySQL也会在线程池中预先分配相应的线程资源。除了能完成像连接池一样的功能,如线程复用、请求队列等,还在逻辑上将one-thread-per-connection中的1对1的关系转变为多对1。MySQL的线程池会分为多个group,每个group中会fork出一个或多个worker线程。对于Client端连接池发起的每个连接(socket连接),并不会独占线程池的一个worker线程,而是一个worker线程会处理多个连接。如下图:
不知道这样解释,你明白了吗?