Druid-Verbindungspool mit niedriger Version + MySQL-Treiber 8.0 führt zu Thread-Blockierungen und Leistungseinschränkungen

Druid-Verbindungspool mit niedriger Version + MySQL-Treiber 8.0 führt zu Thread-Blockierungen und Leistungseinschränkungen

Phänomen

Nachdem die Anwendung auf den MySQL-Treiber 8.0 aktualisiert wurde und die Parallelität hoch ist, werden die Überwachungspunkte geprüft. Die Zeit, die der Druid-Verbindungspool zum Herstellen der Verbindung und Ausführen von SQL benötigt, beträgt meist mehr als 200 ms.

Beim Stresstest des Systems wurde festgestellt, dass eine große Anzahl von Threads blockiert war. Die Thread-Dump-Informationen lauten wie folgt:

"http-nio-5366-exec-48" #210 Daemon prio=5 os_prio=0 tid=0x00000000023d0800 nid=0x3be9 wartet auf Monitoreintrag [0x00007fa4c1400000]
   java.lang.Thread.State: BLOCKIERT (auf dem Objektmonitor)
        bei org.springframework.boot.web.embedded.tomcat.TomcatEmbeddedWebappClassLoader.loadClass(TomcatEmbeddedWebappClassLoader.java:66)
        - Wartet auf die Sperre <0x0000000775af0960> (ein java.lang.Object)
        bei org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1186)
        bei com.alibaba.druid.util.Utils.loadClass(Utils.java:220)
        bei com.alibaba.druid.util.MySqlUtils.getLastPacketReceivedTimeMs(MySqlUtils.java:372)

Ursachenanalyse

öffentliche Klasse MySqlUtils {

    öffentliche statische lange getLastPacketReceivedTimeMs (Verbindung conn) wirft SQLException {
        wenn (class_connectionImpl == null und !class_connectionImpl_Error) {
            versuchen {
                class_connectionImpl = Utils.loadClass("com.mysql.jdbc.MySQLConnection");
            } catch (Auslösbarer Fehler) {
                class_connectionImpl_Error = wahr;
            }
        }

        wenn (class_connectionImpl == null) {
            Rückgabe -1;
        }

        wenn (method_getIO == null und !method_getIO_error) {
            versuchen {
                method_getIO = class_connectionImpl.getMethod("getIO");
            } catch (Auslösbarer Fehler) {
                method_getIO_error = wahr;
            }
        }

        wenn (method_getIO == null) {
            Rückgabe -1;
        }

        wenn (class_MysqlIO == null && !class_MysqlIO_Error) {
            versuchen {
                Klasse_MysqlIO = Utils.loadClass("com.mysql.jdbc.MysqlIO");
            } catch (Auslösbarer Fehler) {
                Klasse_MysqlIO_Error = wahr;
            }
        }

        wenn (class_MysqlIO == null) {
            Rückgabe -1;
        }

        if (method_getLastPacketReceivedTimeMs == null && !method_getLastPacketReceivedTimeMs_error) {
            versuchen {
                Methode Methode = class_MysqlIO.getDeclaredMethod("getLastPacketReceivedTimeMs");
                Methode.SetAccessible(true);
                method_getLastPacketReceivedTimeMs = Methode;
            } catch (Auslösbarer Fehler) {
                method_getLastPacketReceivedTimeMs_error = true;
            }
        }

        wenn (method_getLastPacketReceivedTimeMs == null) {
            Rückgabe -1;
        }

        versuchen {
            Objekt connImpl = conn.unwrap(class_connectionImpl);
            wenn (connImpl == null) {
                Rückgabe -1;
            }

            Objekt mysqlio = method_getIO.invoke(connImpl);
            Lange ms = (Lang) method_getLastPacketReceivedTimeMs.invoke(mysqlio);
            return ms.langerWert();
        } Fang (IllegalArgumentException e) {
            neue SQLException werfen("getLastPacketReceivedTimeMs-Fehler", e);
        } Fang (IllegalAccessException e) {
            neue SQLException werfen("getLastPacketReceivedTimeMs-Fehler", e);
        } Fang (InvocationTargetException e) {
            neue SQLException werfen("getLastPacketReceivedTimeMs-Fehler", e);
        }
    }

Die Methode getLastPacketReceivedTimeMs() in MySqlUtils lädt die Klasse com.mysql.jdbc.MySQLConnection, aber der Klassenname wird im MySQL-Treiber 8.0 in com.mysql.cj.jdbc.ConnectionImpl geändert, sodass com.mysql.jdbc.MySQLConnection im MySQL-Treiber 8.0 nicht geladen werden kann.

Wenn bei der Implementierung der Methode getLastPacketReceivedTimeMs() das Laden der Klasse durch Utils.loadClass("com.mysql.jdbc.MySQLConnection") fehlschlägt und eine Ausnahme auslöst, wird die Variable class_connectionImpl_Error geändert und die Klasse wird beim nächsten Aufruf nicht geladen.

öffentliche Klasse Utils {

    öffentliche statische Klasse<?> loadClass(String Klassenname) {
        Klasse<?> clazz = null;

        wenn (Klassenname == null) {
            gibt null zurück;
        }

        versuchen {
            gibt Class.forName(Klassenname) zurück;
        } Fang (ClassNotFoundException e) {
            // überspringen
        }

        ctxClassLoader = Thread.currentThread().getContextClassLoader();
        if (ctxClassLoader != null) {
            versuchen {
                clazz = ctxClassLoader.loadClass(Klassenname);
            } Fang (ClassNotFoundException e) {
                // überspringen
            }
        }

        Rückgabeklazz;
    }

Allerdings wird die ClassNotFoundException auch in der loadClass()-Methode von Utils abgefangen. Dies bedeutet, dass loadClass() keine Ausnahme auslöst, wenn die Klasse nicht geladen werden kann. Dies führt dazu, dass die MySQLConnection-Klasse bei jedem Aufruf der getLastPacketReceivedTimeMs()-Methode einmal geladen wird.

Aus den Thread-Dump-Informationen geht hervor, dass der Thread beim Aufruf der loadClass()-Methode von TomcatEmbeddedWebappClassLoader blockiert wird.

öffentliche Klasse TomcatEmbeddedWebappClassLoader erweitert ParallelWebappClassLoader {

 öffentliche Klasse<?> loadClass(Stringname, Boolesche Auflösung) wirft ClassNotFoundException {
  synchronisiert (JreCompat.isGraalAvailable() ? dies : getClassLoadingLock(name)) {
   Klasse<?> Ergebnis = findExistingLoadedClass(Name);
   Ergebnis = (Ergebnis != null)? Ergebnis: doLoadClass(Name);
   wenn (Ergebnis == null) {
    wirf eine neue ClassNotFoundException(Name);
   }
   returniere „solveIfNecessary“ (Ergebnis, Lösung);
  }
 }

Dies liegt daran, dass TomcatEmbeddedWebappClassLoader beim Laden einer Klasse eine synchronisierte Sperre hinzufügt, was dazu führt, dass com.mysql.jdbc.MySQLConnection bei jedem Aufruf der Methode getLastPacketReceivedTimeMs() einmal geladen wird, aber nie geladen wird. Beim Laden einer Klasse wird eine synchronisierte Sperre hinzugefügt, sodass es zu Threadblockierungen und Leistungseinbußen kommt.

Aufrufzeit der Methode getLastPacketReceivedTimeMs()

öffentliche abstrakte Klasse DruidAbstractDataSource erweitert WrapperAdapter implementiert DruidAbstractDataSourceMBean, DataSource, DataSourceProxy, Serializable {

    geschützter Boolescher Wert testConnectionInternal(DruidConnectionHolder-Inhaber, Verbindungsverbindung) {
        Zeichenfolge sqlFile = JdbcSqlStat.getContextSqlFile();
        Zeichenfolge sqlName = JdbcSqlStat.getContextSqlName();

        if (sqlFile != null) {
            JdbcSqlStat.setContextSqlFile(null);
        }
        if (sqlName != null) {
            JdbcSqlStat.setContextSqlName(null);
        }
        versuchen {
            if (validConnectionChecker != null) {
                Boolescher Wert gültig = validConnectionChecker.isValidConnection(conn, validationQuery, validationQueryTimeout);
                lange currentTimeMillis = System.currentTimeMillis();
                wenn (Inhaber != null) {
                    Inhaber.letzteValidTimeMillis = aktuelleTimeMillis;
                    Inhaber.lastExecTimeMillis = aktuelleTimeMillis;
                }

                if (valid && isMySql) { // nicht ausgenommener Zweig
                    lange lastPacketReceivedTimeMs = MySqlUtils.getLastPacketReceivedTimeMs(conn);
                    wenn (lastPacketReceivedTimeMs > 0) {
                        lange mysqlIdleMillis = aktuelleZeitMillis - letztePaketempfangszeitMs;
                        wenn (lastPacketReceivedTimeMs > 0 //
                                && mysqlIdleMillis >= timeBetweenEvictionRunsMillis) {
                            Verbindung verwerfen(Inhaber);
                            String errorMsg = "Lange Zeit keine empfangene Verbindung verwerfen."
                                    + ", jdbcUrl: " + jdbcUrl
                                    + ", jdbcUrl: " + jdbcUrl
                                    + ", lastPacketReceivedIdleMillis: " + mysqlIdleMillis;
                            LOG.error(Fehlermeldung);
                            gibt false zurück;
                        }
                    }
                }

                if (gültig && bei schwerwiegendem Fehler) {
                    sperre.sperre();
                    versuchen {
                        if (bei schwerwiegendem Fehler) {
                            bei schwerwiegendem Fehler = falsch;
                        }
                    Endlich
                        sperren.entsperren();
                    }
                }

                Rückgabe gültig;
            }

            wenn (conn.isClosed()) {
                gibt false zurück;
            }

            if (null == Validierungsabfrage) {
                gibt true zurück;
            }

            Anweisung stmt = null;
            Ergebnismenge rset = null;
            versuchen {
                stmt = conn.createStatement();
                wenn (getValidationQueryTimeout() > 0) {
                    stmt.setQueryTimeout(validationQueryTimeout);
                }
                rset = stmt.executeQuery(Validierungsabfrage);
                wenn (!rset.next()) {
                    gibt false zurück;
                }
            Endlich
                JdbcUtils.close(rset);
                JdbcUtils.close(stmt);
            }

            if (bei schwerwiegendem Fehler) {
                sperre.sperre();
                versuchen {
                    if (bei schwerwiegendem Fehler) {
                        bei schwerwiegendem Fehler = falsch;
                    }
                Endlich
                    sperren.entsperren();
                }
            }

            gibt true zurück;
        } fangen (Wurfbeispiel) {
            // überspringen
            gibt false zurück;
        Endlich
            if (sqlFile != null) {
                JdbcSqlStat.setContextSqlFile(sqlFile);
            }
            if (sqlName != null) {
                JdbcSqlStat.setContextSqlName(sqlName);
            }
        }
    }

Die Methode getLastPacketReceivedTimeMs() wird nur in der Methode testConnectionInternal() von DruidAbstractDataSource aufgerufen.

testConnectionInternal() wird verwendet, um zu prüfen, ob die Verbindung gültig ist. Diese Methode kann aufgerufen werden, wenn eine Verbindung hergestellt oder zurückgegeben wird, abhängig von den Parametern, die Druid verwendet, um zu prüfen, ob die Verbindung gültig ist.

Parameter für Druid, um festzustellen, ob die Verbindung gültig ist:

  • testOnBorrow: Führen Sie bei jedem Verbindungsaufbau eine Validierungsabfrage aus, um zu prüfen, ob die Verbindung gültig ist (was sich auf die Leistung auswirkt).
  • testOnReturn: Führen Sie bei jeder Rückgabe einer Verbindung eine Validierungsabfrage aus, um zu prüfen, ob die Verbindung gültig ist (was sich auf die Leistung auswirkt).
  • testWhileIdle: Wird beim Beantragen einer Verbindung überprüft. Wenn die Leerlaufzeit größer als timeBetweenEvictionRunsMillis ist, führen Sie validationQuery aus, um zu überprüfen, ob die Verbindung gültig ist.
  • Die Anwendung setzt testOnBorrow=true. Bei jedem Verbindungsaufbau wird die synchronisierte Sperre aktiviert, was die Leistung erheblich reduziert.

Lösung

Es wurde bestätigt, dass dieser Fehler bei Verwendung von Druid 1.x Version <= 1.1.22 auftritt. Die Lösung besteht darin, auf Druid 1.x Version >= 1.1.23 oder Druid 1.2.x Version zu aktualisieren.

GitHub-Problem: https://github.com/alibaba/druid/issues/3808

Dies ist das Ende dieses Artikels über den Druid-Verbindungspool mit niedriger Version + MySQL-Treiber 8.0, der Thread-Blockierungen und eingeschränkte Leistung verursacht. Weitere Informationen zum Druid-Verbindungspool mit niedriger Version des MySQL-Treibers 8.0 finden Sie in den vorherigen Artikeln von 123WORDPRESS.COM oder in den folgenden verwandten Artikeln. Ich hoffe, dass jeder 123WORDPRESS.COM in Zukunft unterstützen wird!

Das könnte Sie auch interessieren:
  • Beheben Sie das Kompatibilitätsproblem zwischen dem MySQL 8.0-Treiber und der Alibaba Druid-Version
  • Hinweise zum passenden MySql 8.0 und entsprechenden Treiberpaketen
  • Eine kurze Analyse des Problems der Mysql 8.0-Version, das getTables dazu veranlasst, alle Datenbanktabellen zurückzugeben
  • Detaillierte Erläuterung der Probleme, die bei der Verwendung des Mysql8.0-Versionstreibers im Mybatis-Reverse-Engineering auftreten

<<:  IDEA verwendet das Docker-Plugin (Tutorial für Anfänger)

>>:  Vue implementiert eine einfache Notizblockfunktion

Artikel empfehlen

Implementierung der Vue-Paketgrößenoptimierung (von 1,72 M auf 94 K)

1. Hintergrund Ich habe vor Kurzem eine Website n...

Verwenden von CSS3 zum Erzielen von Übergangs- und Animationseffekten

Warum sollten wir CSS-Animationen anstelle von JS...

So installieren Sie Elasticsearch und Kibana in Docker

Elasticsearch erfreut sich derzeit großer Beliebt...

So importieren Sie Excel-Dateien in eine MySQL-Datenbank

In diesem Artikel erfahren Sie, wie Sie Excel-Dat...

Detaillierte Erläuterung der JavaScript-Implementierung der Hash-Tabelle

Inhaltsverzeichnis 1. Hash-Tabellenprinzip 2. Das...

CSS realisiert die Layoutmethode „Fest links“ und „Adaptiv rechts“

1. Schwebendes Layout 1. Lassen Sie zuerst das Di...

Verkürzen Sie die Seiten-Rendering-Zeit, damit die Seite schneller läuft

Wie kann die Seiten-Rendering-Zeit im Browser so ...

Grundlegendes zu MySQL-Sperren basierend auf Update-SQL-Anweisungen

Vorwort Die MySQL-Datenbanksperre ist ein wichtig...

Auswahl der MySQL-Tabellentyp-Speicher-Engine

Inhaltsverzeichnis 1. Zeigen Sie die Speicher-Eng...

Beispiel für die Mosaikierung eines Bildes mit js

Dieser Artikel stellt hauptsächlich ein Beispiel ...

Code zum Anzeigen des Inhalts eines TXT-Buchs auf einer Webseite

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML ...