druiddatasource_记一次DruidDataSource重复请求排查记录

说明:不能这么配置,仅供参考,后续会重写一篇druid连接池的文章,目前本文的操作会导致一个问题:当数据源关闭时连接失败,数据源重启后就无法连接了,本周开发任务有点大,所以一直没有更新…所以说网上相关的文章也不能照葫芦画瓢,需要辩证的看待,DruidDataSource相关介绍稍微写了一下,地址在

京思祺:DruidDataSoure介绍​zhuanlan.zhihu.com

d6be9ef9a14cdf17c1f275d09473a96c.gif

昨晚上线以后出现了一个非常神奇的"MySQLTransactionRollbackException;Lock wait timeout exceeded; try restarting transaction"错误,经过排查以后发现是没有调整配置的情况下导致DruidDataSource不断请求sqlserver导致的,特此记录一下

daf499d8920a429bc2c35c8a09c8025d.png

还原案发现场

业务逻辑是这样的,当一个业务单据进来的时候,会先进行id+type的唯一索引校验,当不存在的时候,进行单据的插入,随后同步到金蝶数据库,流程如下:

b4e0c6921595e7384b3761b402d2c941.png

这个时候神奇的事情发生了:

a627d0dbddb7af06b18c91b9b2bdbad9.png

DruidDataSource在连接sqlServer失败的情况下,疯狂的开始了重试,此时mysql中的插入操作还占着那一条单据的唯一索引,这个时候定时任务来了一条相同的业务请求,然后就出现了喜大普奔的报错:"MySQLTransactionRollbackException;Lock wait timeout exceeded; try restarting transaction"

9231d59862385b4586a075236354f00c.png

问题排查

在查的时候发现一个非常神奇的run方法:

c96c1594c747db0afced69da5e17133d.png

代码是这样的:

public void run() {
            initedLatch.countDown();

            long lastDiscardCount = 0;
            int errorCount = 0;
            for (;;) {
                // addLast
                try {
                    lock.lockInterruptibly();
                } catch (InterruptedException e2) {
                    break;
                }

                long discardCount = DruidDataSource.this.discardCount;
                boolean discardChanged = discardCount - lastDiscardCount > 0;
                lastDiscardCount = discardCount;

                try {
                    boolean emptyWait = true;

                    if (createError != null && poolingCount == 0 && !discardChanged) {
                        emptyWait = false;
                    }

                    if (emptyWait) {
                        // 必须存在线程等待,才创建连接
                        if (poolingCount >= notEmptyWaitThreadCount //
                                && !(keepAlive && activeCount + poolingCount < minIdle)) {
                            empty.await();
                        }

                        // 防止创建超过maxActive数量的连接
                        if (activeCount + poolingCount >= maxActive) {
                            empty.await();
                            continue;
                        }
                    }

                } catch (InterruptedException e) {
                    lastCreateError = e;
                    lastErrorTimeMillis = System.currentTimeMillis();
                    break;
                } finally {
                    lock.unlock();
                }

                PhysicalConnectionInfo connection = null;

                try {
                    connection = createPhysicalConnection();
                    setFailContinuous(false);
                } catch (SQLException e) {
                    LOG.error("create connection error, 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 error", e);
                    setFailContinuous(true);
                    continue;
                } catch (Error e) {
                    LOG.error("create connection error", e);
                    setFailContinuous(true);
                    break;
                }

                if (connection == null) {
                    continue;
                }

                boolean result = put(connection);
                if (!result) {
                    JdbcUtils.close(connection.getPhysicalConnection());
                    LOG.info("put physical connection to pool failed.");
                }

                errorCount = 0; // reset errorCount
            }
        }

这个时候魔性的事情发生了,可以看到connection一直都是null的

b94a25982efe2fd457fc3c493502ae9e.png

然后就开始了罪恶的一生:

if (connection == null) {
                    continue;
                }

然后这个run方法是:

for (;;){}

91b9fa52ef83590f1a0f0c06d1145973.gif

在catch中可以看到断开判断是

75ea781061b95059df8f16e2b07619d3.png

第一个重试次数是30次,第二个是两个连接的间隔时间,可以看到要重试相当长一波才会进入到断开连接的判断中去,仔细看这个判断中的方法:

if 

可以看到breakAfterAcquireFailure这个值为true的时候回断开这次重试,重试方法中的相关参数如注释:

//重试次数,进入时默认设置为0,每次失败累加

在经历了这一波操作后,如果还活着,就回到了那熟悉的节奏:

if 

修改起来也非常简单,只需要做几个操作即可:

  1. 调整重试次数限制
  2. 调整breakAfterAcquireFailure 的默认值
  3. 设置maxWait最大连接等待时间

这里需要说明一下,不设置最大连接等待时间还是会有问题,测试的时候发现break了之前的请求仍然还在..像是一直在getConnection

其他Druid相关配置答疑可以参考Druid的开源仓库建议配置和答疑,Druid的文档有中文版的,所以看起来也没有什么压力,感觉可以学习一下,毕竟简介上写的是Java语言中最好的数据库连接池

da90b547712df6e0c903133c7de83c24.png
https://github.com/alibaba/druid/wiki/DruidDataSource%E9%85%8D%E7%BD%AE​github.com https://github.com/alibaba/druid/wiki/%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98​github.com

祖传代码害死人啊!!!!

38e183e16cdca6ab3683fbab3fd70876.png
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值