Mysql作为一个常用数据库,在互联网系统应用很多。有些故障是其自身的bug,有些则不是,这里以前段时间遇到的问题举例。

问题

当时遇到的症状是这样的,我们的应用在线上测试环境,JMeter测试过程中,发现每次压力测试开始时访问低前几个http request请求会超时,而之后的请求持续测试中都不会。最后一点是Tomcat的log并没有报什么错误。

压测的内容就是起200线程不停的向这个http页面发送请求,这个页面逻辑也比较简单,会在后端向数据库插入一条数据,连接池采用阿里的Druid(这个坑先留在这),tomcat中运行的是常规web app应用,每个应用的JDBC连接池最大连接数只设了30,就是说就算4个tomcat一起连数据库,最大也没有多少连接。

尝试排查

由于tomcat的log并没有什么错,所以先开始尝试重现错误。 错误重现开始并不容易,因为看起来比较随机,后来经过总结,发现每次都是出现问题都是应用放了一晚上后,测试人员早上过来开始压力测试时出现,开始怀疑跟闲置有关,所以后面的重现都按这个方式来,闲置半小时再开始尝试重现。

找log

没有log,就要看下JVM的stack信息了。重现故障,上该机器上用jstack直接抓问题tomcat 的jvm信息。

jps
列出机器的java进程号
jstack javaid
dump该java进程的stack信息

拿到的stack信息中发现了有用的东西:

"http-bio-8081-exec-4975" daemon prio=10 tid=0x00007f9d4c127000 nid=0x65db runnable [0x00007f9cc4544000]
java.lang.Thread.State: RUNNABLE
at java.net.SocketInputStream.socketRead0(Native Method)
at java.net.SocketInputStream.read(SocketInputStream.java:129)
at com.mysql.jdbc.util.ReadAheadInputStream.fill(ReadAheadInputStream.java:114)
at com.mysql.jdbc.util.ReadAheadInputStream.readFromUnderlyingStreamIfNecessary(ReadAheadInputStream.java:161)
at com.mysql.jdbc.util.ReadAheadInputStream.read(ReadAheadInputStream.java:189)
- locked <0x0000000684d608c8> (a com.mysql.jdbc.util.ReadAheadInputStream)

at com.mysql.jdbc.MysqlIO.readFully(MysqlIO.java:3014)
at com.mysql.jdbc.MysqlIO.reuseAndReadPacket(MysqlIO.java:3467)
at com.mysql.jdbc.MysqlIO.reuseAndReadPacket(MysqlIO.java:3456)
at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3997)
at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:2468)
at com.mysql.jdbc.ConnectionImpl.pingInternal(ConnectionImpl.java:4092)
at com.mysql.jdbc.ConnectionImpl.ping(ConnectionImpl.java:4069)
at sun.reflect.GeneratedMethodAccessor94.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at com.alibaba.druid.pool.vendor.MySqlValidConnectionChecker.isValidConnection(MySqlValidConnectionChecker.java:98)
at com.alibaba.druid.pool.DruidAbstractDataSource.testConnectionInternal(DruidAbstractDataSource.java:1235)
at com.alibaba.druid.pool.DruidDataSource.getConnectionDirect(DruidDataSource.java:928)
at com.alibaba.druid.pool.DruidDataSource.getConnection(DruidDataSource.java:882)
at com.alibaba.druid.pool.DruidDataSource.getConnection(DruidDataSource.java:872)
at com.alibaba.druid.pool.DruidDataSource.getConnection(DruidDataSource.java:97)
at org.springframework.jdbc.datasource.DataSourceTransactionManager.doBegin(DataSourceTransactionManager.java:202)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.getTransaction(AbstractPlatformTransactionManager.java:372)
at org.springframework.transaction.interceptor.TransactionAspectSupport.createTransactionIfNecessary(TransactionAspectSupport.java:417)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:255)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:94)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:207)
at com.sun.proxy.$Proxy27.insert(Unknown Source)

可以看到HTTP请求从前端容器直到数据库读取时为止,卡在了数据库读取的地方,而且并不是JDBC驱动代码里的问题,而且出在socket读取的地方:

com.mysql.jdbc.util.ReadAheadInputStream.read(ReadAheadInputStream.java:189)
- locked <0x0000000684d608c8> (a com.mysql.jdbc.util.ReadAheadInputStream)*

根据这个错误搜索了下,唯一有价值的就是N年前mysql官网上报的一个bug,同样的错误,但处理的办法并不治本。如提到了将JDBC连接字串改为:

useReadAheadInput=false&useUnbufferedInput=false

这只是让socket不去预读网络缓冲区,但实际上这个时候Mysql的连接已经断开了,并不知道是web应用这里断的还是Mysql断的。

查找连接池超时

由于根据log看起来是应用的客户端在socket上读不到东西,肯定是应用与mysql的tcp连接断了,所以开始排查应用连接池设置与mysql的连接超时设置。

应用连接池设置

name="maxWait" value="60000"
获取连接最大等待60秒
name="testWhileIdle" value="true"
测试空闲连接
name="minEvictableIdleTimeMillis" value="300000"
name="timeBetweenEvictionRunsMillis" value="60000"
Destroy线程会检测连接的间隔时间

应用端的连接池设置没有主动断掉的设置。

mysql连接超时设置

show global variables like '%timeout%'

看到mysql维持连接的timeout时间为28800,即8小时,数据库端不会断掉这个连接。

至此,问题的排查进入死胡同,两边都不会主动断开连接,为什么客户端在闲置几分钟后会被断掉?

还有一个疑点是同样的代码,数据库也没什么变动,在另一个纯测试环境完全没有这个问题。

查找网络问题

现在问题的重点怀疑方向就是线上环境网络问题。于是找运维的同事查看了下数据库机器上linux有没有什么异常的配置,结果是没有。

期间也怀疑为什么用了阿里druid的连接池,现在设成每分钟检测连接池里的连接,还是会在拿到连接的时候有失效的连接。

解决

断断续续折腾了2天,抱着死马当活马医去咨询了其他部门的同事,结果那兄弟说是不是闲置后卡在socketRead上?然后问了应用与数据库是不是在不同网段上,马上建议找网络的人查一下防火墙对tcp长连接超时的设置。

这时候基本上就肯定是防火墙设置的问题,排查后发现两个网段间华为交换机的长连接超时设了3分钟,由于java应用的连接池是尽量长时间的维持连接(几个小时,低于数据库的最长8小时设置),而防火墙认为超过3分钟的连接是有问题的,直接断掉了,这时应用与mysql都不知道tcp连接已经被断了。

此次故障还暴露了阿里Druid开源连接池对连接处理逻辑的问题,连接池并没有用单独的线程去检测所有连接有没有断开,查代码后发现其只是在拿到连接时测试连接是否有效,处理逻辑没有老牌c3p0严谨,之后将应用连接池实现更换为c3p0。


文章来自微信平台「麦芽面包」。转载请注明。

一则线上MySql连接异常的排查过程的更多相关文章

  1. 原创 记录一次线上Mysql慢查询问题排查过程

    背景 前段时间收到运维反馈,线上Mysql数据库凌晨时候出现慢查询的报警,并把原始sql发了过来: --去除了业务含义的sql update test_user set a=1 where id=1; ...

  2. Linux(2)---记录一次线上服务 CPU 100%的排查过程

    Linux(2)---记录一次线上服务 CPU 100%的排查过程 当时产生CPU飙升接近100%的原因是因为项目中的websocket时时断开又重连导致CPU飙升接近100% .如何排查的呢 是通过 ...

  3. 记一次排查线上MySQL死锁过程,不能只会curd,还要知道加锁原理

    昨晚我正在床上睡得着着的,突然来了一条短信. 啥,线上MySQL死锁了,我赶紧登录线上系统,查看业务日志. 能清楚看到是这条insert语句发生了死锁. MySQL如果检测到两个事务发生了死锁,会回滚 ...

  4. 一次线上CPU高的问题排查实践

    一次线上CPU高的问题排查实践 前言 近期某一天上班一开电脑,就收到了运维警报,有两台服务CPU负载很高,同时收到一线同事反馈 系统访问速度非常慢,几乎无响应. 一个美好的早晨,最怕什么就来什么.只好 ...

  5. 线上Mysql数据库崩溃事故的原因和处理

    前文提要 承接前文<一次线上Mysql数据库崩溃事故的记录>,在文章中讲到了一次线上数据库崩溃的事件记录,建议两篇文章结合在一起看,不至于摸不着头脑. 由于时间原因,其中只讲了当时的一些经 ...

  6. Spring+SpringMVC+MyBatis+easyUI整合进阶篇(八)线上Mysql数据库崩溃事故的原因和处理

    前文提要 承接前文<一次线上Mysql数据库崩溃事故的记录>,在文章中讲到了一次线上数据库崩溃的事件记录,建议两篇文章结合在一起看,不至于摸不着头脑. 由于时间原因,其中只讲了当时的一些经 ...

  7. 线上CPU飙升100%问题排查

    本文转载自线上CPU飙升100%问题排查 引子 对于互联网公司,线上CPU飙升的问题很常见(例如某个活动开始,流量突然飙升时),按照本文的步骤排查,基本1分钟即可搞定!特此整理排查方法一篇,供大家参考 ...

  8. 探针配置失误,线上容器应用异常死锁后,kubernetes集群未及时响应自愈重启容器?

    探针配置失误,线上容器应用异常死锁后,kubernetes集群未及时响应自愈重启容器? 探针配置失误,线上容器应用异常死锁后,kubernetes集群未及时响应自愈重启容器? 线上多个服务应用陷入了死 ...

  9. 记一次线上服务CPU 100%的处理过程

    告警 正在开会,突然钉钉告警声响个不停,同时市场人员反馈客户在投诉系统登不进了,报504错误.查看钉钉上的告警信息,几台业务服务器节点全部报CPU超过告警阈值,达100%. 赶紧从会上下来,SSH登录 ...

随机推荐

  1. Spark难道比oracle性能还差?百万级数据测试性能

    本人对大数据方面也是刚刚研究,由于工作需要在实时查询与统计的性能方面要深入学习.现测试性能如下:   环境:VirtualBox  host-only ubuntu版本: Linux master 4 ...

  2. 【转】linux yum命令详解

    yum(全称为 Yellow dog Updater, Modified)是一个在Fedora和RedHat以及SUSE中的Shell前端软件包管理器.基於RPM包管理,能够从指定的服务器自动下载RP ...

  3. js中返回上一页失效的解决办法

    最近在做移动端的时候,碰到了一个问题,就是点击返回按键,使用history.go(-1)或history.back(-1)不能够生效.于是便寻找其他的方法,终于找到了一位大神的方法如下: if(win ...

  4. Hibernate-list()与iterate()方法的区别

    对于list方法而言,实际上Hibernate是通过一条Select SQL获取所有的记录.并将其读出,填入到POJO中返回.而iterate 方法,则是首先通过一条Select SQL 获取所有符合 ...

  5. laravel中如何写验证码文件,并防止中文乱码.

    本例为生成中文验证码,专为laravel而生. //控制器: public function getVcode(Request $request){ $width=845; $height=125; ...

  6. Java 第17章 继承

    继承的概念 继承机制是面向对象程序设计不可缺少的关键概念,是实现软件可重用的根基, 是提高软件系统的可扩展性与可维护性的主要途径. 所谓继承是指一个类的定义可以基于另外一个已经存在的类,即子类基于父类 ...

  7. JavaScript-取消事件-e.preventDefault();

    取消事件:(阻止默认行为) 当事件执行过程中,遇到问题,可取消事件.不再触发 如何e.preventDefault(); <!DOCTYPE html> <html> < ...

  8. 对偶理论、拉格朗日对偶问题、LP线性规划对偶性质

    Lagrange 对偶问题 定义其的对偶问题: Lagrange函数 考虑线性规划问题 若取集合约束D={x|x≥0},则该线性规划问题的Lagrange函数为 线性规划的对偶问题为: 对偶定理原问题 ...

  9. DOM 事件

    1.注册事件 // 事件处理函数 function handleMouseOver(event) { // process ...... } p.addEventListener("mous ...

  10. 零值初始化&字符串常数作为函数模板参数

    1.在定义一个局部变量时,并希望该局部变量的初始化一个值,可以显示调用其默认构造函数,使其值为0(bool类型默认值为false). template <typename T> void ...