亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

低版本Druid連接池+MySQL驅(qū)動8.0導(dǎo)致線程阻塞、性能受限

 更新時間:2021年07月01日 15:25:13   作者:邋遢的流浪劍客  
應(yīng)用升級MySQL驅(qū)動8.0后,在并發(fā)量較高時,查看監(jiān)控打點,Druid連接池拿到連接并執(zhí)行SQL的時間大部分都超過200ms,本文就解決一下這個問題

現(xiàn)象

應(yīng)用升級MySQL驅(qū)動8.0后,在并發(fā)量較高時,查看監(jiān)控打點,Druid連接池拿到連接并執(zhí)行SQL的時間大部分都超過200ms

對系統(tǒng)進行壓測,發(fā)現(xiàn)出現(xiàn)大量線程阻塞的情況,線程dump信息如下:

"http-nio-5366-exec-48" #210 daemon prio=5 os_prio=0 tid=0x00000000023d0800 nid=0x3be9 waiting for monitor entry [0x00007fa4c1400000]
   java.lang.Thread.State: BLOCKED (on object monitor)
        at org.springframework.boot.web.embedded.tomcat.TomcatEmbeddedWebappClassLoader.loadClass(TomcatEmbeddedWebappClassLoader.java:66)
        - waiting to lock <0x0000000775af0960> (a java.lang.Object)
        at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1186)
        at com.alibaba.druid.util.Utils.loadClass(Utils.java:220)
        at com.alibaba.druid.util.MySqlUtils.getLastPacketReceivedTimeMs(MySqlUtils.java:372)

根因分析

public class MySqlUtils {

    public static long getLastPacketReceivedTimeMs(Connection conn) throws SQLException {
        if (class_connectionImpl == null && !class_connectionImpl_Error) {
            try {
                class_connectionImpl = Utils.loadClass("com.mysql.jdbc.MySQLConnection");
            } catch (Throwable error){
                class_connectionImpl_Error = true;
            }
        }

        if (class_connectionImpl == null) {
            return -1;
        }

        if (method_getIO == null && !method_getIO_error) {
            try {
                method_getIO = class_connectionImpl.getMethod("getIO");
            } catch (Throwable error){
                method_getIO_error = true;
            }
        }

        if (method_getIO == null) {
            return -1;
        }

        if (class_MysqlIO == null && !class_MysqlIO_Error) {
            try {
                class_MysqlIO = Utils.loadClass("com.mysql.jdbc.MysqlIO");
            } catch (Throwable error){
                class_MysqlIO_Error = true;
            }
        }

        if (class_MysqlIO == null) {
            return -1;
        }

        if (method_getLastPacketReceivedTimeMs == null && !method_getLastPacketReceivedTimeMs_error) {
            try {
                Method method = class_MysqlIO.getDeclaredMethod("getLastPacketReceivedTimeMs");
                method.setAccessible(true);
                method_getLastPacketReceivedTimeMs = method;
            } catch (Throwable error){
                method_getLastPacketReceivedTimeMs_error = true;
            }
        }

        if (method_getLastPacketReceivedTimeMs == null) {
            return -1;
        }

        try {
            Object connImpl = conn.unwrap(class_connectionImpl);
            if (connImpl == null) {
                return -1;
            }

            Object mysqlio = method_getIO.invoke(connImpl);
            Long ms = (Long) method_getLastPacketReceivedTimeMs.invoke(mysqlio);
            return ms.longValue();
        } catch (IllegalArgumentException e) {
            throw new SQLException("getLastPacketReceivedTimeMs error", e);
        } catch (IllegalAccessException e) {
            throw new SQLException("getLastPacketReceivedTimeMs error", e);
        } catch (InvocationTargetException e) {
            throw new SQLException("getLastPacketReceivedTimeMs error", e);
        }
    }

MySqlUtils中的getLastPacketReceivedTimeMs()方法會加載com.mysql.jdbc.MySQLConnection這個類,但在MySQL驅(qū)動8.0中類名改為com.mysql.cj.jdbc.ConnectionImpl,所以MySQL驅(qū)動8.0中加載不到com.mysql.jdbc.MySQLConnection

getLastPacketReceivedTimeMs()方法實現(xiàn)中,如果Utils.loadClass("com.mysql.jdbc.MySQLConnection")加載不到類并拋出異常,會修改變量class_connectionImpl_Error,下次調(diào)用不會再進行加載

public class Utils {

    public static Class<?> loadClass(String className) {
        Class<?> clazz = null;

        if (className == null) {
            return null;
        }

        try {
            return Class.forName(className);
        } catch (ClassNotFoundException e) {
            // skip
        }

        ClassLoader ctxClassLoader = Thread.currentThread().getContextClassLoader();
        if (ctxClassLoader != null) {
            try {
                clazz = ctxClassLoader.loadClass(className);
            } catch (ClassNotFoundException e) {
                // skip
            }
        }

        return clazz;
    }

但是,在Utils的loadClass()方法中同樣catch了ClassNotFoundException,這就導(dǎo)致loadClass()在加載不到類的時候,并不會拋出異常,從而會導(dǎo)致每調(diào)用一次getLastPacketReceivedTimeMs()方法,就會加載一次MySQLConnection這個類

線程dump信息中可以看到是在調(diào)用TomcatEmbeddedWebappClassLoader的loadClass()方法時,導(dǎo)致線程阻塞的

public class TomcatEmbeddedWebappClassLoader extends ParallelWebappClassLoader {

 public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
  synchronized (JreCompat.isGraalAvailable() ? this : getClassLoadingLock(name)) {
   Class<?> result = findExistingLoadedClass(name);
   result = (result != null) ? result : doLoadClass(name);
   if (result == null) {
    throw new ClassNotFoundException(name);
   }
   return resolveIfNecessary(result, resolve);
  }
 }

這是因為TomcatEmbeddedWebappClassLoader在加載類的時候,會加synchronized鎖,這就導(dǎo)致每調(diào)用一次getLastPacketReceivedTimeMs()方法,就會加載一次com.mysql.jdbc.MySQLConnection,而又始終加載不到,在加載類的時候會加synchronized鎖,所以會出現(xiàn)線程阻塞,性能下降的現(xiàn)象

getLastPacketReceivedTimeMs()方法調(diào)用時機

public abstract class DruidAbstractDataSource extends WrapperAdapter implements DruidAbstractDataSourceMBean, DataSource, DataSourceProxy, Serializable {

    protected boolean testConnectionInternal(DruidConnectionHolder holder, Connection conn) {
        String sqlFile = JdbcSqlStat.getContextSqlFile();
        String sqlName = JdbcSqlStat.getContextSqlName();

        if (sqlFile != null) {
            JdbcSqlStat.setContextSqlFile(null);
        }
        if (sqlName != null) {
            JdbcSqlStat.setContextSqlName(null);
        }
        try {
            if (validConnectionChecker != null) {
                boolean valid = validConnectionChecker.isValidConnection(conn, validationQuery, validationQueryTimeout);
                long currentTimeMillis = System.currentTimeMillis();
                if (holder != null) {
                    holder.lastValidTimeMillis = currentTimeMillis;
                    holder.lastExecTimeMillis = currentTimeMillis;
                }

                if (valid && isMySql) { // unexcepted branch
                    long lastPacketReceivedTimeMs = MySqlUtils.getLastPacketReceivedTimeMs(conn);
                    if (lastPacketReceivedTimeMs > 0) {
                        long mysqlIdleMillis = currentTimeMillis - lastPacketReceivedTimeMs;
                        if (lastPacketReceivedTimeMs > 0 //
                                && mysqlIdleMillis >= timeBetweenEvictionRunsMillis) {
                            discardConnection(holder);
                            String errorMsg = "discard long time none received connection. "
                                    + ", jdbcUrl : " + jdbcUrl
                                    + ", jdbcUrl : " + jdbcUrl
                                    + ", lastPacketReceivedIdleMillis : " + mysqlIdleMillis;
                            LOG.error(errorMsg);
                            return false;
                        }
                    }
                }

                if (valid && onFatalError) {
                    lock.lock();
                    try {
                        if (onFatalError) {
                            onFatalError = false;
                        }
                    } finally {
                        lock.unlock();
                    }
                }

                return valid;
            }

            if (conn.isClosed()) {
                return false;
            }

            if (null == validationQuery) {
                return true;
            }

            Statement stmt = null;
            ResultSet rset = null;
            try {
                stmt = conn.createStatement();
                if (getValidationQueryTimeout() > 0) {
                    stmt.setQueryTimeout(validationQueryTimeout);
                }
                rset = stmt.executeQuery(validationQuery);
                if (!rset.next()) {
                    return false;
                }
            } finally {
                JdbcUtils.close(rset);
                JdbcUtils.close(stmt);
            }

            if (onFatalError) {
                lock.lock();
                try {
                    if (onFatalError) {
                        onFatalError = false;
                    }
                } finally {
                    lock.unlock();
                }
            }

            return true;
        } catch (Throwable ex) {
            // skip
            return false;
        } finally {
            if (sqlFile != null) {
                JdbcSqlStat.setContextSqlFile(sqlFile);
            }
            if (sqlName != null) {
                JdbcSqlStat.setContextSqlName(sqlName);
            }
        }
    }

只有DruidAbstractDataSource的testConnectionInternal()方法中會調(diào)用getLastPacketReceivedTimeMs()方法

testConnectionInternal()是用來檢測連接是否有效的,在獲取連接和歸還連接時都有可能會調(diào)用該方法,這取決于Druid檢測連接是否有效的參數(shù)

Druid檢測連接是否有效的參數(shù):

  • testOnBorrow:每次獲取連接時執(zhí)行validationQuery檢測連接是否有效(會影響性能)
  • testOnReturn:每次歸還連接時執(zhí)行validationQuery檢測連接是否有效(會影響性能)
  • testWhileIdle:申請連接的時候檢測,如果空閑時間大于timeBetweenEvictionRunsMillis,執(zhí)行validationQuery檢測連接是否有效
  • 應(yīng)用中設(shè)置了testOnBorrow=true,每次獲取連接時,都會去搶占synchronized鎖,所以性能下降的很明顯

解決方案

經(jīng)驗證,使用Druid 1.x版本<=1.1.22會出現(xiàn)該bug,解決方案就是升級至Druid 1.x版本>=1.1.23或者Druid 1.2.x版本

GitHub issue:https://github.com/alibaba/druid/issues/3808

到此這篇關(guān)于低版本Druid連接池+MySQL驅(qū)動8.0導(dǎo)致線程阻塞、性能受限的文章就介紹到這了,更多相關(guān)MySQL驅(qū)動8.0低版本Druid連接池內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • mysql數(shù)據(jù)庫重置表主鍵id的實現(xiàn)

    mysql數(shù)據(jù)庫重置表主鍵id的實現(xiàn)

    在我們的開發(fā)過程中,難免在做測試的時候會生成一些雜亂無章的SQL主鍵數(shù)據(jù),本文主要介紹了mysql數(shù)據(jù)庫重置表主鍵id的實現(xiàn),具有一定的參考價值,感興趣的可以了解一下
    2025-03-03
  • MySQL使用正則表達式來更好地控制數(shù)據(jù)過濾

    MySQL使用正則表達式來更好地控制數(shù)據(jù)過濾

    MySQL中的正則表達式是一種強大的數(shù)據(jù)過濾工具,它允許用戶以靈活的方式匹配和搜索文本數(shù)據(jù),這篇文章主要給大家介紹了關(guān)于MySQL使用正則表達式來更好地控制數(shù)據(jù)過濾的相關(guān)資料,需要的朋友可以參考下
    2024-08-08
  • mysql enum字段類型的謹(jǐn)慎使用

    mysql enum字段類型的謹(jǐn)慎使用

    本文主要介紹了mysql enum字段類型使用,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-07-07
  • MySQL 8.0.19支持輸入3次錯誤密碼鎖定賬戶功能(例子)

    MySQL 8.0.19支持輸入3次錯誤密碼鎖定賬戶功能(例子)

    這篇文章主要介紹了MySQL 8.0.19支持輸入3次錯誤密碼鎖定賬戶功能,本文通過實例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-01-01
  • Mysql環(huán)境變量配置方式

    Mysql環(huán)境變量配置方式

    這篇文章主要介紹了Mysql環(huán)境變量配置方式,具有很好的參考價值,希望對大家有所幫助。
    2022-12-12
  • 利用ssh tunnel鏈接mysql服務(wù)器的方法

    利用ssh tunnel鏈接mysql服務(wù)器的方法

    這篇文章主要給大家介紹了利用ssh tunnel鏈接mysql服務(wù)器的方法,文中介紹的很詳細(xì),需要的朋友可以參考借鑒,下面來一起看看吧。
    2017-02-02
  • MySQL數(shù)據(jù)庫安全之防止撰改的方法

    MySQL數(shù)據(jù)庫安全之防止撰改的方法

    這篇文章主要介紹了MySQL數(shù)據(jù)庫防止撰改的方法,需要的朋友可以參考下
    2014-07-07
  • MySQL:explain結(jié)果中Extra:Impossible?WHERE?noticed?after?reading?const?tables問題

    MySQL:explain結(jié)果中Extra:Impossible?WHERE?noticed?after?rea

    這篇文章主要介紹了MySQL:explain結(jié)果中Extra:Impossible?WHERE?noticed?after?reading?const?tables問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2023-12-12
  • Keepalived+HAProxy實現(xiàn)MySQL高可用負(fù)載均衡的配置

    Keepalived+HAProxy實現(xiàn)MySQL高可用負(fù)載均衡的配置

    這篇文章主要介紹了keepalived+haproxy實現(xiàn)MySQL高可用負(fù)載均衡的配置方法,通過這兩個軟件可以有效地使MySQL脫離故障及進行健康檢測,需要的朋友可以參考下
    2016-02-02
  • Mysql常見的慢查詢優(yōu)化方式總結(jié)

    Mysql常見的慢查詢優(yōu)化方式總結(jié)

    優(yōu)化是一項復(fù)雜的任務(wù),因為它最終需要對整個系統(tǒng)的理解,下面這篇文章主要給大家總結(jié)介紹了關(guān)于Mysql常見的慢查詢優(yōu)化方式,文中介紹的非常詳細(xì),需要的朋友可以參考下
    2023-05-05

最新評論