天天看点

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

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

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

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

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

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

还原案发现场

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

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

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

druiddatasource_记一次DruidDataSource重复请求排查记录
DruidDataSource在连接sqlServer失败的情况下,疯狂的开始了重试

,此时mysql中的插入操作还占着那一条单据的唯一索引,这个时候定时任务来了一条相同的业务请求,然后就出现了喜大普奔的报错:"MySQLTransactionRollbackException;Lock wait timeout exceeded; try restarting transaction"

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

问题排查

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

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

代码是这样的:

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的

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

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

if (connection == null) {
                    continue;
                }
           

然后这个run方法是:

for (;;){}
           
druiddatasource_记一次DruidDataSource重复请求排查记录

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

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

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

if 
           

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

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

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

if 
           

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

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

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

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

Java语言中最好的数据库连接池
druiddatasource_记一次DruidDataSource重复请求排查记录

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

祖传代码害死人啊!!!!
druiddatasource_记一次DruidDataSource重复请求排查记录

继续阅读