美文网首页
聊聊druid的handleException

聊聊druid的handleException

作者: go4it | 来源:发表于2023-09-26 09:04 被阅读0次

    本文主要研究一下druid的handleException

    prepareStatement

    com/alibaba/druid/pool/DruidPooledConnection.java

        public PreparedStatement prepareStatement(String sql) throws SQLException {
            checkState();
    
            PreparedStatementHolder stmtHolder = null;
            PreparedStatementKey key = new PreparedStatementKey(sql, getCatalog(), MethodType.M1);
    
            boolean poolPreparedStatements = holder.isPoolPreparedStatements();
    
            if (poolPreparedStatements) {
                stmtHolder = holder.getStatementPool().get(key);
            }
    
            if (stmtHolder == null) {
                try {
                    stmtHolder = new PreparedStatementHolder(key, conn.prepareStatement(sql));
                    holder.getDataSource().incrementPreparedStatementCount();
                } catch (SQLException ex) {
                    handleException(ex, sql);
                }
            }
    
            initStatement(stmtHolder);
    
            DruidPooledPreparedStatement rtnVal = new DruidPooledPreparedStatement(this, stmtHolder);
    
            holder.addTrace(rtnVal);
    
            return rtnVal;
        }
    

    DruidPooledConnection的prepareStatement会catch住SQLException然后执行handleException

    executeQuery

    com/alibaba/druid/pool/DruidPooledPreparedStatement.java

        public ResultSet executeQuery() throws SQLException {
            checkOpen();
    
            incrementExecuteQueryCount();
            transactionRecord(sql);
    
            oracleSetRowPrefetch();
    
            conn.beforeExecute();
            try {
                ResultSet rs = stmt.executeQuery();
    
                if (rs == null) {
                    return null;
                }
    
                DruidPooledResultSet poolableResultSet = new DruidPooledResultSet(this, rs);
                addResultSetTrace(poolableResultSet);
    
                return poolableResultSet;
            } catch (Throwable t) {
                errorCheck(t);
    
                throw checkException(t);
            } finally {
                conn.afterExecute();
            }
        }
    

    executeQuery在catch到Throwable时会执行throw checkException(t)

    checkException

    com/alibaba/druid/pool/DruidPooledStatement.java

        protected SQLException checkException(Throwable error) throws SQLException {
            String sql = null;
            if (this instanceof DruidPooledPreparedStatement) {
                sql = ((DruidPooledPreparedStatement) this).getSql();
            }
    
            handleSocketTimeout(error);
    
            exceptionCount++;
            return conn.handleException(error, sql);
        }
    

    checkException这里会执行conn.handleException(error, sql)

    handleException

        public SQLException handleException(Throwable t, String sql) throws SQLException {
            final DruidConnectionHolder holder = this.holder;
    
            //
            if (holder != null) {
                DruidAbstractDataSource dataSource = holder.getDataSource();
                dataSource.handleConnectionException(this, t, sql);
            }
    
            if (t instanceof SQLException) {
                throw (SQLException) t;
            }
    
            throw new SQLException("Error", t);
        }
    

    handleException这里是委托给了dataSource.handleConnectionException(this, t, sql);

    handleConnectionException

    com/alibaba/druid/pool/DruidDataSource.java

        public void handleConnectionException(DruidPooledConnection pooledConnection,
                                              Throwable t,
                                              String sql) throws SQLException {
            final DruidConnectionHolder holder = pooledConnection.getConnectionHolder();
            if (holder == null) {
                return;
            }
    
            errorCountUpdater.incrementAndGet(this);
            lastError = t;
            lastErrorTimeMillis = System.currentTimeMillis();
    
            if (t instanceof SQLException) {
                SQLException sqlEx = (SQLException) t;
    
                // broadcastConnectionError
                ConnectionEvent event = new ConnectionEvent(pooledConnection, sqlEx);
                for (ConnectionEventListener eventListener : holder.getConnectionEventListeners()) {
                    eventListener.connectionErrorOccurred(event);
                }
    
                // exceptionSorter.isExceptionFatal
                if (exceptionSorter != null && exceptionSorter.isExceptionFatal(sqlEx)) {
                    handleFatalError(pooledConnection, sqlEx, sql);
                }
    
                throw sqlEx;
            } else {
                throw new SQLException("Error", t);
            }
        }
    

    handleConnectionException方法在exceptionSorter.isExceptionFatal(sqlEx)为true时会执行handleFatalError

    isExceptionFatal

    com/alibaba/druid/pool/vendor/MySqlExceptionSorter.java

    public class MySqlExceptionSorter implements ExceptionSorter {
        @Override
        public boolean isExceptionFatal(SQLException e) {
            if (e instanceof SQLRecoverableException) {
                return true;
            }
    
            final String sqlState = e.getSQLState();
            final int errorCode = e.getErrorCode();
    
            if (sqlState != null && sqlState.startsWith("08")) {
                return true;
            }
    
            switch (errorCode) {
                // Communications Errors
                case 1040: // ER_CON_COUNT_ERROR
                case 1042: // ER_BAD_HOST_ERROR
                case 1043: // ER_HANDSHAKE_ERROR
                case 1047: // ER_UNKNOWN_COM_ERROR
                case 1081: // ER_IPSOCK_ERROR
                case 1129: // ER_HOST_IS_BLOCKED
                case 1130: // ER_HOST_NOT_PRIVILEGED
                    // Authentication Errors
                case 1045: // ER_ACCESS_DENIED_ERROR
                    // Resource errors
                case 1004: // ER_CANT_CREATE_FILE
                case 1005: // ER_CANT_CREATE_TABLE
                case 1015: // ER_CANT_LOCK
                case 1021: // ER_DISK_FULL
                case 1041: // ER_OUT_OF_RESOURCES
                    // Out-of-memory errors
                case 1037: // ER_OUTOFMEMORY
                case 1038: // ER_OUT_OF_SORTMEMORY
                    // Access denied
                case 1142: // ER_TABLEACCESS_DENIED_ERROR
                case 1227: // ER_SPECIFIC_ACCESS_DENIED_ERROR
    
                case 1023: // ER_ERROR_ON_CLOSE
    
                case 1290: // ER_OPTION_PREVENTS_STATEMENT
                    return true;
                default:
                    break;
            }
    
            // for oceanbase
            if (errorCode >= -9000 && errorCode <= -8000) {
                return true;
            }
    
            String className = e.getClass().getName();
            if (className.endsWith(".CommunicationsException")) {
                return true;
            }
    
            String message = e.getMessage();
            if (message != null && message.length() > 0) {
                if (message.startsWith("Streaming result set com.mysql.jdbc.RowDataDynamic")
                        && message.endsWith("is still active. No statements may be issued when any streaming result sets are open and in use on a given connection. Ensure that you have called .close() on any active streaming result sets before attempting more queries.")) {
                    return true;
                }
    
                final String errorText = message.toUpperCase();
    
                if ((errorCode == 0 && (errorText.contains("COMMUNICATIONS LINK FAILURE")) //
                        || errorText.contains("COULD NOT CREATE CONNECTION")) //
                        || errorText.contains("NO DATASOURCE") //
                        || errorText.contains("NO ALIVE DATASOURCE")) {
                    return true;
                }
            }
    
            Throwable cause = e.getCause();
            for (int i = 0; i < 5 && cause != null; ++i) {
                if (cause instanceof SocketTimeoutException) {
                    return true;
                }
    
                className = cause.getClass().getName();
                if (className.endsWith(".CommunicationsException")) {
                    return true;
                }
    
                cause = cause.getCause();
            }
    
            return false;
        }
    
        @Override
        public void configFromProperties(Properties properties) {
        }
    
    }
    

    MySqlExceptionSorter针对指定错误码来判断是否fatal

    handleFatalError

    com/alibaba/druid/pool/DruidDataSource.java

        protected final void handleFatalError(DruidPooledConnection conn,
                                              SQLException error,
                                              String sql) throws SQLException {
            final DruidConnectionHolder holder = conn.holder;
    
            if (conn.isTraceEnable()) {
                activeConnectionLock.lock();
                try {
                    if (conn.isTraceEnable()) {
                        activeConnections.remove(conn);
                        conn.setTraceEnable(false);
                    }
                } finally {
                    activeConnectionLock.unlock();
                }
            }
    
            long lastErrorTimeMillis = this.lastErrorTimeMillis;
            if (lastErrorTimeMillis == 0) {
                lastErrorTimeMillis = System.currentTimeMillis();
            }
    
            if (sql != null && sql.length() > 1024) {
                sql = sql.substring(0, 1024);
            }
    
            boolean requireDiscard = false;
            final ReentrantLock lock = conn.lock;
            lock.lock();
            try {
                if ((!conn.isClosed()) || !conn.isDisable()) {
                    conn.disable(error);
                    requireDiscard = true;
                }
    
                lastFatalErrorTimeMillis = lastErrorTimeMillis;
                fatalErrorCount++;
                if (fatalErrorCount - fatalErrorCountLastShrink > onFatalErrorMaxActive) {
                    onFatalError = true;
                }
                lastFatalError = error;
                lastFatalErrorSql = sql;
            } finally {
                lock.unlock();
            }
    
            if (onFatalError && holder != null && holder.getDataSource() != null) {
                ReentrantLock dataSourceLock = holder.getDataSource().lock;
                dataSourceLock.lock();
                try {
                    emptySignal();
                } finally {
                    dataSourceLock.unlock();
                }
            }
    
            if (requireDiscard) {
                if (holder.statementTrace != null) {
                    holder.lock.lock();
                    try {
                        for (Statement stmt : holder.statementTrace) {
                            JdbcUtils.close(stmt);
                        }
                    } finally {
                        holder.lock.unlock();
                    }
                }
    
                this.discardConnection(holder);
            }
    
            // holder.
            LOG.error("{conn-" + holder.getConnectionId() + "} discard", error);
        }
    

    handleFatalError方法这里会执行conn.disable(error),然后标记requireDiscard为true,最后执行discardConnection

    discardConnection

    com/alibaba/druid/pool/DruidDataSource.java

        public void discardConnection(DruidConnectionHolder holder) {
            if (holder == null) {
                return;
            }
    
            Connection conn = holder.getConnection();
            if (conn != null) {
                JdbcUtils.close(conn);
            }
    
            lock.lock();
            try {
                if (holder.discard) {
                    return;
                }
    
                if (holder.active) {
                    activeCount--;
                    holder.active = false;
                }
                discardCount++;
    
                holder.discard = true;
    
                if (activeCount <= minIdle) {
                    emptySignal();
                }
            } finally {
                lock.unlock();
            }
        }
    

    discardConnection这里会通过JdbcUtils.close(conn)关闭连接,然后加锁判断是否小于等于minIdle,若为true则执行emptySignal

    小结

    druid会在prepareStatement或者执行prepareStatement出现异常的时候执行conn.handleException,它委托给dataSource.handleConnectionException,后者会在exceptionSorter.isExceptionFatal(sqlEx)为true时会执行handleFatalError,handleFatalError方法这里会执行conn.disable(error),然后标记requireDiscard为true,最后执行discardConnection来关闭连接。通过这样子来快速清理不可用的连接,避免连接池的连接不可用。

    相关文章

      网友评论

          本文标题:聊聊druid的handleException

          本文链接:https://www.haomeiwen.com/subject/yythbdtx.html