現象アプリケーションを MySQL ドライバー 8.0 にアップグレードした後、同時実行性が高くなると、監視ポイントがチェックされ、Druid 接続プールが接続を取得して SQL を実行する時間はほとんど 200 ミリ秒を超えます。 システムのストレス テストを行ったところ、多数のスレッドがブロックされていることが判明しました。スレッド ダンプ情報は次のとおりです。 "http-nio-5366-exec-48" #210 デーモン prio=5 os_prio=0 tid=0x00000000023d0800 nid=0x3be9 モニターエントリを待機中 [0x00007fa4c1400000] java.lang.Thread.State: BLOCKED (オブジェクト モニター上) org.springframework.boot.web.embedded.tomcat.TomcatEmbeddedWebappClassLoader.loadClass(TomcatEmbeddedWebappClassLoader.java:66) で - <0x0000000775af0960> (java.lang.Object) のロック待ち org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1186) で com.alibaba.druid.util.Utils.loadClass(Utils.java:220) で com.alibaba.druid.util.MySqlUtils.getLastPacketReceivedTimeMs(MySqlUtils.java:372) で 根本原因分析パブリッククラスMySqlUtils { パブリック静的long getLastPacketReceivedTimeMs(接続conn)はSQLExceptionをスローします{ (class_connectionImpl == null && !class_connectionImpl_Error) の場合 { 試す { class_connectionImpl = Utils.loadClass("com.mysql.jdbc.MySQLConnection"); } キャッチ (スロー可能なエラー) { クラス接続エラー = true; } } (class_connectionImpl == null)の場合{ -1 を返します。 } メソッド取得IOがnullの場合、メソッド取得IOエラーが発生します。 試す { メソッド getIO = class_connectionImpl.getMethod("getIO"); } キャッチ (スロー可能なエラー) { メソッド_getIO_error = true; } } メソッドgetIOがnullの場合 -1 を返します。 } (class_MysqlIO == null && !class_MysqlIO_Error) の場合 { 試す { class_MysqlIO = Utils.loadClass("com.mysql.jdbc.MysqlIO"); } キャッチ (スロー可能なエラー) { クラス_MysqlIO_Error = true; } } (class_MysqlIO == null)の場合{ -1 を返します。 } (method_getLastPacketReceivedTimeMs == null && !method_getLastPacketReceivedTimeMs_error) の場合 { 試す { メソッド method = class_MysqlIO.getDeclaredMethod("getLastPacketReceivedTimeMs"); メソッド.setAccessible(true); method_getLastPacketReceivedTimeMs = メソッド; } キャッチ (スロー可能なエラー) { method_getLastPacketReceivedTimeMs_error = true; } } (method_getLastPacketReceivedTimeMs == null)の場合{ -1 を返します。 } 試す { オブジェクト connImpl = conn.unwrap(class_connectionImpl); connImpl == nullの場合{ -1 を返します。 } オブジェクト mysqlio = method_getIO.invoke(connImpl); Long ms = (Long) method_getLastPacketReceivedTimeMs.invoke(mysqlio); ms.longValue() を返します。 } キャッチ (IllegalArgumentException e) { 新しい SQLException をスローします ("getLastPacketReceivedTimeMs エラー", e); } キャッチ (IllegalAccessException e) { 新しい SQLException("getLastPacketReceivedTimeMs エラー", e) をスローします。 } キャッチ (InvocationTargetException e) { 新しい SQLException("getLastPacketReceivedTimeMs エラー", e) をスローします。 } } MySqlUtils の getLastPacketReceivedTimeMs() メソッドは com.mysql.jdbc.MySQLConnection クラスをロードしますが、クラス名は MySQL ドライバー 8.0 で com.mysql.cj.jdbc.ConnectionImpl に変更されているため、com.mysql.jdbc.MySQLConnection は MySQL ドライバー 8.0 ではロードできません。 getLastPacketReceivedTimeMs() メソッドの実装で、Utils.loadClass("com.mysql.jdbc.MySQLConnection") がクラスのロードに失敗して例外をスローすると、変数 class_connectionImpl_Error が変更され、次回呼び出されたときにクラスはロードされなくなります。 パブリッククラスUtils{ パブリック静的クラス<?> loadClass(String className) { クラス<?> clazz = null; クラス名が null の場合 null を返します。 } 試す { Class.forName(className) を返します。 } キャッチ (ClassNotFoundException e) { // スキップ } クラスローダー ctxClassLoader = Thread.currentThread().getContextClassLoader(); ctxClassLoader != null の場合 { 試す { clazz = ctxClassLoader.loadClass(クラス名); } キャッチ (ClassNotFoundException e) { // スキップ } } 戻りクラッズ; } ただし、ClassNotFoundException は Utils の loadClass() メソッドでもキャッチされるため、loadClass() はクラスをロードできない場合に例外をスローせず、getLastPacketReceivedTimeMs() メソッドが呼び出されるたびに MySQLConnection クラスが 1 回ロードされることになります。 スレッド ダンプ情報には、TomcatEmbeddedWebappClassLoader の loadClass() メソッドを呼び出すときにスレッドがブロックされていることが示されています。 パブリッククラスTomcatEmbeddedWebappClassLoaderはParallelWebappClassLoaderを拡張します{ パブリック Class<?> loadClass(String name, boolean resolve) は ClassNotFoundException をスローします { 同期化 (JreCompat.isGraalAvailable() ? this : getClassLoadingLock(name)) { クラス<?>結果 = findExistingLoadedClass(名前); 結果 = (結果 != null) ? 結果: doLoadClass(name); 結果が null の場合 新しい ClassNotFoundException(名前) をスローします。 } 必要であれば解決を返します(結果、解決)。 } } これは、TomcatEmbeddedWebappClassLoader がクラスをロードするときに同期ロックを追加するためです。その結果、getLastPacketReceivedTimeMs() メソッドが呼び出されるたびに com.mysql.jdbc.MySQLConnection が 1 回ロードされますが、実際にはロードされません。クラスをロードするときに同期ロックが追加されるため、スレッドのブロックとパフォーマンスの低下が発生します。 getLastPacketReceivedTimeMs() メソッドの呼び出し時間パブリック抽象クラス DruidAbstractDataSource は WrapperAdapter を拡張し、DruidAbstractDataSourceMBean、DataSource、DataSourceProxy、Serializable を実装します { 保護されたブール型 testConnectionInternal(DruidConnectionHolder ホルダー、接続接続) { 文字列 sqlFile = JdbcSqlStat.getContextSqlFile(); 文字列 sqlName = JdbcSqlStat.getContextSqlName(); (sqlFile != null)の場合{ JdbcSqlStat.setContextSqlFile(null); } (sqlName != null)の場合{ JdbcSqlStat.setContextSqlName(null); } 試す { 有効な接続チェッカーが null の場合 ブール値有効 = validConnectionChecker.isValidConnection(conn、validationQuery、validationQueryTimeout); 長いcurrentTimeMillis = System.currentTimeMillis(); ホルダーが null ではない場合 ホルダー.lastValidTimeMillis = 現在のTimeMillis; ホルダー.lastExecTimeMillis = currentTimeMillis; } if (valid && isMySql) { // 例外のないブランチ 長い lastPacketReceivedTimeMs = MySqlUtils.getLastPacketReceivedTimeMs(conn); 最後のパケット受信時間ミリ秒 > 0 の場合 { 長い mysqlIdleMillis = currentTimeMillis - lastPacketReceivedTimeMs; (最後のパケット受信時間Ms > 0の場合 // && mysqlIdleMillis >= timeBetweenEvictionRunsMillis) { 接続を破棄します(ホルダー); 文字列 errorMsg = "長時間受信されていない接続を破棄します。" + "、jdbcUrl : " + jdbcUrl + "、jdbcUrl : " + jdbcUrl + "、lastPacketReceivedIdleMillis : " + mysqlIdleMillis; LOG.error(エラーメッセージ); false を返します。 } } } if (有効 && onFatalError) { ロック。ロック(); 試す { (致命的なエラーの場合){ onFatalError = false; } ついに ロックを解除します。 } } 有効な値を返します。 } conn.isClosed() の場合 { false を返します。 } if (null == 検証クエリ) { true を返します。 } ステートメント stmt = null; 結果セット rset = null; 試す { stmt = conn.createStatement(); getValidationQueryTimeout() が 0 以上のとき stmt.setQueryTimeout(検証クエリタイムアウト); } rset = stmt.executeQuery(検証クエリ); if (!rset.next()) { false を返します。 } ついに JdbcUtils.close(rset); JdbcUtils.close(stmt); } (致命的なエラーの場合){ ロック。ロック(); 試す { (致命的なエラーの場合){ onFatalError = false; } ついに ロックを解除します。 } } true を返します。 } キャッチ (Throwable ex) { // スキップ false を返します。 ついに (sqlFile != null)の場合{ JdbcSqlStat.setContextSqlFile(sqlFile); } (sqlName != null)の場合{ JdbcSqlStat.setContextSqlName(sqlName); } } } getLastPacketReceivedTimeMs() メソッドは、DruidAbstractDataSource の testConnectionInternal() メソッドでのみ呼び出されます。 testConnectionInternal() は、接続が有効かどうかを確認するために使用されます。このメソッドは、Druid が接続が有効かどうかを確認するために使用するパラメータに応じて、接続を取得するとき、または接続を返すときに呼び出される場合があります。 接続が有効かどうかを検出するための Druid のパラメータ:
解決このバグは、Druid 1.x バージョン <= 1.1.22 を使用しているときに発生することが確認されています。解決策は、Druid 1.x バージョン >= 1.1.23 または Druid 1.2.x バージョンにアップグレードすることです。 GitHub の問題: https://github.com/alibaba/druid/issues/3808 これで、低バージョンの Druid 接続プール + MySQL ドライバー 8.0 がスレッド ブロックとパフォーマンス制限を引き起こすことに関するこの記事は終了です。MySQL ドライバー 8.0 低バージョンの Druid 接続プールの詳細については、123WORDPRESS.COM の以前の記事を検索するか、次の関連記事を引き続き参照してください。今後とも 123WORDPRESS.COM を応援してください。 以下もご興味があるかもしれません:
|
>>: a タグにはテキストと画像があります。テキストを非表示にして画像のみを表示するにはどうすればよいでしょうか?
いわゆるファビコンは、Favorites Icon の略で、中国語ではウェブサイトアバターと呼ばれて...
この記事では、パズルゲームを実装するためのjsの具体的なコードを参考までに共有します。具体的な内容は...
質問LINUX では、定期的なタスクは通常、cron デーモン プロセス [ps -ef | gre...
Cockpit は、CentOS および RHEL システムで使用できる Web ベースのサーバー管...
アルファベット順DTD: このタグが許可される XHTML 1.0 DTD を示します。 S=厳密、...
無料のパブリック STUN サーバーSIP 端末がプライベート IP アドレスを使用する場合、スタン...
## 1最近、docker デプロイメントを学習しており、当初は nginx を docker 化す...
B/S システム インターフェースを構築する場合、メイン ページ index.html 内に他のペー...
序文:ルートでは、主要部分は同じでも、基礎となる構造が異なることがあります。たとえば、ホームページに...
NULL および NOT NULL 修飾子、DEFAULT 修飾子、AUTO_INCREMENT 修...
問題の説明MySQL の起動時にエラーが報告されます。エラー ログを確認してください。 [エラー] ...
コマンド: mysqlhotcopyこのコマンドは、ファイルをコピーする前にテーブルをロックし、不完...
早速ですが、デモ画像をご紹介します。実装されている機能は、左側に凡例、右側にウォーターフォール チャ...
フレックス コンテナーを作成するには、要素に display: flex プロパティを追加するだけで...
この記事の例では、画像拡大鏡効果を実現するためのVue3の具体的なコードを参考までに共有しています。...