Mysql重启后部分业务应用重连数据库失败

背景

最近频繁收到运营反馈软件故障,经排查应用日志发现应用连接MySQL失败,日志如下:

### Cause: org.springframework.jdbc.CannotGetJdbcConnectionException: Failed to obtain JDBC Connection; nested exception is com.alibaba.druid.pool.GetConnectionTimeoutException: wait millis 1000, active 0, maxActive 400, creating 0, createErrorCount 2
	...
Caused by: com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failure
The last packet sent successfully to the server was 0 milliseconds ago. The driver has not received any packets from the server.
	... 20 common frames omitted
Caused by: java.net.ConnectException: Connection refused (Connection refused)
	at java.net.PlainSocketImpl.socketConnect(Native Method)
	at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:350)
	at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:206)
	at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:188)
	at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392)
	at java.net.Socket.connect(Socket.java:607)
	at com.mysql.jdbc.StandardSocketFactory.connect(StandardSocketFactory.java:211)
	at com.mysql.jdbc.MysqlIO.<init>(MysqlIO.java:299)
	... 22 common frames omitted

很明显的错误提示:druid连接池活跃数为0(跟连接池没关系了),那就是jdbc驱动连不上MySQL(无论使用什么连接池,底层都是基于jdbc建立与数据库的tcp连接通道)。第一反应就是MySQL宕机了,上服务器一看MySQL竟然自动重启了(部署在容器内,这个就没必要纠结性能受损了,适合自己的才是最好的)。那么问题来了:***自动重启后,为什么其他应用可以重连Mysql(jdbc连接参数配置了autoReconnect=true),但是就有些应用无法重连???***进入应用容器执行ping和telnet都是可以连通MySQL的,排除了网络问题,那问题就出现在应用层面了。

解决过程

第一次:当时排查了下MySQL服务器配置参数,当时没有配置MySQL的连接数,默认是比较小的可以使用SQL:show variables like ‘%connect%’,查看相关连接参数max_connections(MySQL最大连接数)、max_user_connections(单个用户的最大连接数)、connect_timeout(连接超时)。因为我的项目是有好几个数据库都在这一个实例上,那就根据应用的连接池配置合计了下连接数修改了MySQL配置max_connections和max_user_connections
第二次:距离上次修改不到一周,又接到运营反馈又出现了之前的错误(看来方案一对本例只是治标不治本)。继续分析:既然jdbc负责和MySQL建立TCP连接,那么问题肯定就是就在建立连接(三次握手四次挥手)这里咯。继续查文档发现我的问题跟文档1.1里面的故障很相似(MySQL建链发生永久性阻塞),分析其解决方案具备可行性。故而设置jdbc连接参数socketTimeout和connectTimeout,以观后效。
第三次: 时间不久再次出现问题,只能想办法复现原问题,然后寻找解决方法。首先正常请求执行sql,然后停止数据库模拟宕机,然后多次请求数据库直至报错与原问题一致,然后重启数据库模拟恢复,再次请求就复现了不能重连的问题。问题复现了那就好办,跟踪源码层层跟进,终于发现druid请求创建数据库连接是在线程里按一定间隔时间循环创建的(com.alibaba.druid.pool.DruidDataSource.CreateConnectionTask.runInternal),而真正创建连接是在com.alibaba.druid.pool.DruidDataSource.CreateConnectionThread.run。如果配置breakAfterAcquireFailure配置为true,那么出现连接失败并达到配置的重试次数后仍然失败就会直接break结束线程方法,相关源码如下:

                try {
                    connection = createPhysicalConnection();
                } catch (SQLException e) {
                    LOG.error("create connection SQLException, url: " + jdbcUrl + ", errorCode " + e.getErrorCode()
                            + ", state " + e.getSQLState(), e);

                    errorCount++;
                    if (errorCount > connectionErrorRetryAttempts && timeBetweenConnectErrorMillis > 0) {
                        // fail over retry attempts
                        setFailContinuous(true);
                        if (failFast) {
                            lock.lock();
                            try {
                                notEmpty.signalAll();
                            } finally {
                                lock.unlock();
                            }
                        }

                        if (breakAfterAcquireFailure) {
                            break;
                        }

                        try {
                            Thread.sleep(timeBetweenConnectErrorMillis);
                        } catch (InterruptedException interruptEx) {
                            break;
                        }
                    }
                } catch (RuntimeException e) {
                    LOG.error("create connection RuntimeException", e);
                    setFailContinuous(true);
                    continue;
                } catch (Error e) {
                    LOG.error("create connection Error", e);
                    setFailContinuous(true);
                    break;
                }

然后仔细核对了应用的数据库连接配置,发现配置了breakAfterAcquireFailure=true,注释该配置后再次验证,问题就此彻底解决。后来查询了相关代码提交记录,以前配置此值是因为数据库被移除掉了就会一直报错连接不到数据库,仔细评估后选择注释此配置恢复自动重连(毕竟错误的业务配置相比应用不可用,简直就是九牛一毛)。后来百度了下breakAfterAcquireFailure发现好多文档(例如https://github.com/alibaba/druid/issues/2130#issuecomment-707739631,3年前都已经解决了),不得不吐槽国内的搜索引擎不堪大用。明明这些文档内也有CannotGetJdbcConnectionException相关词,之前查询文档的时候愣是没搜索到。

1.1 MySQL连接分析参考文档
1.2 Jdbc连接参数参考文档

部分Jdbc连接参数参考文档内容如下:

user
数据库用户名(用于连接数据库)
password
用户密码(用于连接数据库)
useUnicode
是否使用Unicode字符集,如果参数characterEncoding设置为gb2312或gbk,本参数值必须设置为true
characterEncoding
当useUnicode设置为true时,指定字符编码。比如可设置为gb2312或gbk 或UTF8
autoReconnect
当数据库连接异常中断时,是否自动重新连接?
autoReconnectForPools
是否使用针对数据库连接池的重连策略
failOverReadOnly
自动重连成功后,连接是否设置为只读?
maxReconnects
autoReconnect设置为true时,重试连接的次数
initialTimeout
autoReconnect设置为true时,两次重连之间的时间间隔,单位:秒
connectTimeout
和数据库服务器建立socket连接时的超时,单位:毫秒。 0表示永不超时,适用于JDK 1.4及更高版本
socketTimeout
socket操作(读写)超时,单位:毫秒。 0表示永不超时
serverTimezone
时区设置功能有UTC,Asia/Shanghai,Asia/Hongkong
可以选择东8区的Hongkong、Asia/Shanghai或者Asia/Hongkong作为参数
如下所示:
&serverTimezone=UTC
&serverTimezone=Asia/Hongkong
&serverTimezone=Asia/Shanghai

  • 19
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值