1. ホーム
  2. ジャワ

ERROR [com.alibaba.druid.pool.DruidDataSource] - 接続を放棄してください。

2022-02-24 12:28:21
<パス

概要

この記事では、Druidの接続漏れの問題を解決する方法を説明します、プロセスが少し複雑なので、私は詳細に手順とアイデアを記録します、それがあなたの助けとインスピレーションがあれば、コメントを残してください

#プロジェクト構成
MyBatis+TDDL

質問

https://github.com/FS1360472174/javaweb/issues/58

ERROR [com.alibaba.druid.pool.DruidDataSource] - 接続を放棄、所有者スレッド: qtp1267032364
-14 で接続されました。1515409987672 で接続、スタックトレースを開く
at java.lang.Thread.getStackTrace(Thread.java:1556)
com.alibaba.druid.pool.DruidDataSource.getConnectionDirect(DruidDataSource.java:1068)にて。
at com.alibaba.druid.pool.DruidDataSource.getConnection(DruidDataSource.java:994)
at com.alibaba.druid.pool.DruidDataSource.getConnection(DruidDataSource.java:984)
at com.taobao.tddl.atom.jdbc.TDataSourceWrapper.getConnectionByTargetDataSource(TDataSourceWrapper.java:322)
at com.taobao.tddl.atom.jdbc.TDataSourceWrapper.getConnection0(TDataSourceWrapper.java:284)
at com.taobao.tddl.atom.jdbc.TDataSourceWrapper.getConnection(TDataSourceWrapper.java:255)
at com.taobao.tddl.atom.jdbc.TDataSourceWrapper.getConnection(TDataSourceWrapper.java:222)
at com.taobao.tddl.atom.AbstractTAtomDataSource.getConnection(AbstractTAtomDataSource.java:27)
at com.taobao.tddl.group.jdbc.DataSourceWrapper.getConnection(DataSourceWrapper.java:120)
at com.taobao.tddl.group.jdbc.TGroupConnection.createNewConnection(TGroupConnection.java:191)
at com.taobao.tddl.group.jdbc.TGroupConnection$1.tryOnDataSource(TGroupConnection.java:453)
at com.taobao.tddl.group.jdbc.TGroupConnection$1.tryOnDataSource(TGroupConnection.java:443)
at com.taobao.tddl.group.dbselector.AbstractDBSelector.tryOnDataSourceHolderWithIndex(AbstractDBSelector.java:19)

at com.taobao.tddl.group.dbselector.AbstractDBSelector.tryExecute(AbstractDBSelector.java:315)
at com.taobao.tddl.group.jdbc.TGroupConnection.prepareCall(TGroupConnection.java:492)
at com.tao.tddl.group.jdbc.TGroupConnection.prepareCall(TGroupConnection.java:520)
at com.tao.tddl.group.jdbc.TGroupConnection.prepareCall(TGroupConnection.java:74)
at com.taobao.tddl.matrix.jdbc.TConnection.prepareCall(TConnection.java:515)
at com.taobao.tddl.matrix.jdbc.TConnection.prepareCall(TConnection.java:483)
14980,1-8 99%
at com.taobao.tddl.group.jdbc.TGroupConnection.prepareCall(TGroupConnection.java:74)
at com.taobao.tddl.matrix.jdbc.TConnection.prepareCall(TConnection.java:515)
at com.taobao.tddl.matrix.jdbc.TConnection.prepareCall(TConnection.java:483)

オンラインで他の問題をトラブルシューティングしているとき、ログにこのERRORが表示され、非常に頻繁に表示されます。
abandon connectionによると、これはデータベース接続プールの問題であり、非推奨の接続で処理されていることがわかります。

解決手順

  • まずドルイド公式サイトのFAQを検索
    https://github.com/alibaba/druid/wiki/常见问题
    接続漏れの監視処理をしていたのはドルイドであることが判明

    https://github.com/alibaba/druid/wiki/连接泄漏监测
    https://github.com/alibaba/druid/issues/872
    私の方ではペアリング監視を行わず、ログから直接、対応するスレッドスタック情報を取得して、トラブルシューティングを簡単に行うことができます。

    public DruidPooledConnection getConnectionDirect(long maxWaitMillis) throws SQLException {
            int notFullTimeoutRetryCnt = 0;
    
            DruidPooledConnection poolableConnection;
            while(true) {
                while(true) {
                    try {
               
                    Connection realConnection = poolableConnection.getConnection();
                    this.discardConnection(realConnection);
                } else {
                    Connection realConnection = poolableConnection.getConnection();
                    if(realConnection.isClosed()) {
                        this.discardConnection((Connection)null);
                    } else {
                        if(!this.isTestWhileIdle()) {
                            break;
                        }
    
                        long currentTimeMillis = System.currentTimeMillis();
                        long lastActiveTimeMillis = poolableConnection.getConnectionHolder().getLastActiveTimeMillis();
                        long idleMillis = currentTimeMillis - lastActiveTimeMillis;
                        long timeBetweenEvictionRunsMillis = this.getTimeBetweenEvictionRunsMillis();
                        if(timeBetweenEvictionRunsMillis <= 0L) {
                            timeBetweenEvictionRunsMillis = 60000L;
                        }
    
                        if(idleMillis < timeBetweenEvictionRunsMillis) {
                            break;
                        }
    
                        this.discardConnection(realConnection);
                    }
                }
            }
    
            if(this.isRemoveAbandoned()) {
                StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
                poolableConnection.setConnectStackTrace(stackTrace);
                poolableConnection.setConnectedTimeNano();
                poolableConnection.setTraceEnable(true);
                Map var21 = this.activeConnections;
                synchronized(this.activeConnections) {
                    this.activeConnections.put(poolableConnection, PRESENT);
                }
            }
    
            if(!this.isDefaultAutoCommit()) {
                poolableConnection.setAutoCommit(false);
            }
    
            return poolableConnection;
        }
    
    
    
    
  • この接続リークはOOMを引き起こしません。なぜなら、druidは先に進んで、これらの閉じていない接続を積極的に破棄するからです。

  • これで、閉じられていないデータベース接続があることがわかりましたが、コードはデータベース接続プールを管理しておらず、Springに管理を任せており、すべてのデータベース操作に問題があるわけではなく、特定のデータベース操作に問題があることがわかります

  • デバッグログをオンにする

2018-01-23 21:08:35,760 DEBUG [org.springframework.data.redis.core.RedisConnectionUtils] - Opening RedisConnection
2018-01-23 21:08:35,761 DEBUG [org.springframework.data.redis.core.RedisConnectionUtils] - Closing Redis Connection
2018-01-23 21:08:35,762 DEBUG [org.mybatis.spring.SqlSessionUtils] - Creating a new SqlSession
2018-01-23 21:08:35,762 DEBUG [org.mybatis.spring.SqlSessionUtils] - SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@ 5ba28a19] was not registered for synchronization because synchronization is not active
2018-01-23 21:08:35,765 DEBUG [org.springframework.jdbc.datasource.DataSourceUtils] - Fetching JDBC Connection from DataSource
2018-01-23 21:08:35,765 DEBUG [org.mybatis.spring.transaction.SpringManagedTransaction] - JDBC Connection [com.taobao.tddl.matrix.jdbc. TConnection@24f6de0] will not be managed by Spring
2018-01-23 21:08:35,766 DEBUG [com.taobao.tddl.group.jdbc.TGroupConnection] - [TDDL] dataSourceIndex=GroupIndex [index=0, failRetry=false] , tddl version: 5.1.7
2018-01-23 21:08:35,810 DEBUG [org.mybatis.spring.SqlSessionUtils] - Closing non transactional SqlSession [org.apache.ibatis.session. defaults.DefaultSqlSession@5ba28a19]
2018-01-23 21:08:35,810 DEBUG [org.springframework.jdbc.datasource.DataSourceUtils] - Returning JDBC Connection to DataSource



このように、Aliのオープンソースソフトウェアは、まだ産業用にはほど遠いことがわかります。Springでは、接続の解放と復帰が明確にわかりますが、Druidではそうではありません。

  • これは特定のSQLに関連しているため、この特定のSQLの操作を見てください。

     @Options(statementType = StatementType.CALLABLE)
        @Insert("<script> " +  
                "</script>")
     int saveDemo(Demo demo);
    
    
    

    これは以前誰かが残したものですが、この書き方は現在プロジェクトチームではあまり一般的ではなく、コードとSQL文の分離を実現できず、あまり推奨されていません。
    ここのStatementTypeは、一般的にCallableStatementは、データベースのストアドプロシージャの操作のために、明らかにこのステートメントはそうではありませんストアドプロシージャを呼び出すよりも奇妙に見える、呼び出し可能です。前任者がなぜこのように書いたのかわからないので、まずこのStatementType.CALLABLEパラメータを消しました。

  • 何かが間違っていたことが判明しました。呼び出しはエラーを報告しました。おかしなエラーで、auto generate id を使用せず、それを呼び出したのです。

    これはMySQL 5.7ドライバのバグと思われるが、前任者はこの問題を回避するためにCallableStatementを使用して回避していた

    You need to specify Statement.RETURN_GENERATED_KEYS to Statement.executeUpdate() or Connection. prepareStatement().
    at com.taobao.tddl.repo.mysql.handler.PutMyHandlerCommon.handle(PutMyHandlerCommon.java:52)
    at com.taobao.tddl.executor.AbstractGroupExecutor.executeInner(AbstractGroupExecutor.java:59)
    at com.taobao.tddl.executor.AbstractGroupExecutor.execByExecPlanNode(AbstractGroupExecutor.java:40)
    at com.taobao.tddl.executor.TopologyExecutor.execByExecPlanNode(TopologyExecutor.java:59)
    at com.taobao.tddl.executor.MatrixExecutor.execByExecPlanNode(MatrixExecutor.java:282)
    
    
    
  • そこで、まず検証を xml に変更します。xml はデフォルトで PreparedStatement ですが、それでもエラーが発生するかどうかを確認します。

がなくなっていることがわかりました。 ということで、原因はCallableStatementによる

#explore-druid 接続管理
問題は解決したものの、druidがどのように接続を管理するのかがまだ分からないので、もう少し深く掘り下げてみる必要があります。

  • マイバティス
    まず、プロジェクトではMybatisを使用しており、Mybatis + jdbcの操作の流れは以下のようになります。
    データベース操作は、特定のSQLが実行されたときのみ接続を取得する

  • Spring-jdbcの紹介
    トランザクション管理用

    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <tx:annotation-driven transaction-manager="transactionManager"/>


この時点で、コネクションプールの管理はSpringManagedTransactionに引き渡されます。

org.mybatis.spring.transaction.SpringManagedTransaction の略です。

    private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
        Connection connection = this.getConnection(statementLog);
        Statement stmt = handler.prepare(connection, this.transaction.getTimeout());
        handler.parameterize(stmt);
        return stmt;
    }



  • TDDL

テーブルを分割し、コネクションプール管理をdruid自体に依存するため、TDDLを導入した
コネクションを取得する

実行時のTPreparedStatment。
リパッケージされたSQL、PreparedStatment
AutoCommitTransaction は接続を管理します。
getConnection,今回はConnection
取得はTGroupConnectionのcreateNewConnectionです。

接続を閉じる
SqlSessionUtils.closeSqlSession
TConnection.close()を実行します。
TConnectionWrapper.close()
DruidPooledConnection.close()を実行します。
DruidPooledConnection.syncClose()を実行します。
DruidPooledConnection.recycle() // このメソッドは、実際にドルイド接続を閉じます。

public DruidPooledConnection getConnectionDirect(long maxWaitMillis) throws SQLException {
        int notFullTimeoutRetryCnt = 0;

        DruidPooledConnection poolableConnection;
        ...
        if(this.isRemoveAbandoned()) {
            StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
            poolableConnection.setConnectStackTrace(stackTrace);
            poolableConnection.setConnectedTimeNano();
            poolableConnection.setTraceEnable(true);
            Map var21 = this.activeConnections;
            synchronized(this.activeConnections) {
                // put an active connection
                this.activeConnections.put(poolableConnection, PRESENT);
            }
        }

        if(!this.isDefaultAutoCommit()) {
            poolableConnection.setAutoCommit(false);
        }

        return poolableConnection;
    }



放棄を取り除く

  public int removeAbandoned() {
        int removeCount = 0;
        long currrentNanos = System.nanoTime();
        List<DruidPooledConnection> abandonedList = new ArrayList();
        // The class variable activeConnections stores the connections that are not closed
        Map var5 = this.activeConnections;
        synchronized(this.activeConnections) {
            // Get the value of this side
            Iterator iter = this.activeConnections.keySet().iterator();

            while(iter.hasNext()) {
                DruidPooledConnection pooledConnection = (DruidPooledConnection)iter.next();
                if(!pooledConnection.isRunning()) {
                    long timeMillis = (currrentNanos - pooledConnection.getConnectedTimeNano()) / 1000000L;
                    if(timeMillis >= this.removeAbandonedTimeoutMillis) {
                        iter.remove();
                        pooledConnection.setTraceEnable(false);
                        abandonedList.add(pooledConnection);
                    }
                }
            }
        }

        if(abandonedList.size() > 0) {
            Iterator var14 = abandonedList.iterator();
            while(true) {
                DruidPooledConnection pooledConnection;
                do {
                    while(true) {
                        if(!var14.hasNext()) {
                            return removeCount;
                        }

                        pooledConnection = (DruidPooledConnection)var14.next();
                        synchronized(pooledConnection) {
                            if(!pooledConnection.isDisable()) {
                                break;
                            }
                        }
                    }

                    JdbcUtils.close(pooledConnection);
                    pooledConnection.abandond();
                    ++this.removeAbandonedCount;
                    ++removeCount;
                } while(!this.isLogAbandoned());

                StringBuilder buf = new StringBuilder();
                buf.append("abandon connection, owner thread: ");
                LOG.error(buf.toString());
            }
        } else {
            return removeCount;
        }
    }




修正されたSQL操作は閉じられていますが、CallableStatementは閉じられていないため、閉じられていない接続が存在することがわかります。

Javaインターネット技術交換グループ392669336に参加することを歓迎し、Javaの問題のすべての種類を交換するために大修道院長

修道院長】をフォローすると、記事の更新をいち早く受け取り、修道院長と共に技術修行の道を歩み始めることができます

#ref
http://blog.csdn.net/luanlouis/article/details/40422941

http://blog.csdn.net/luanlouis/article/details/37671851

https://www.jianshu.com/p/ec40a82cae28

http://blog.csdn.net/u013476542/article/details/53256610