TiDB を使用したJavaアプリケーション開発のベスト プラクティス

+6
O
r
Y
q

このドキュメントでは、TiDBをより有効に活用するためのJavaアプリケーション開発のベストプラクティスを紹介します。バックエンドのTiDBデータベースとやり取りする一般的なJavaアプリケーションコンポーネントに基づいて、開発中によく発生する問題の解決策も提供します。

Javaアプリケーションで TiDB データベースと対話する一般的なコンポーネントは次のとおりです。

  • ネットワーク プロトコル: クライアントは標準MySQLプロトコルを介して TiDBサーバーと対話します。
  • JDBC APIとJDBCドライバ: Javaアプリケーションは通常、標準のJDBC (Javaデータベース接続) APIを使用してデータベースにアクセスします。TiDBに接続するには、JDBC APIを介してMySQLプロトコルを実装したJDBCドライバを使用できます。MySQLで一般的なJDBCドライバには、 MySQL コネクタ/JMariaDB コネクタ/Jあります。
  • データベース接続プール:接続が要求されるたびに作成するオーバーヘッドを削減するため、アプリケーションは通常、接続プールを使用して接続をキャッシュし、再利用します。JDBC データソース接続プールAPIが定義されています。必要に応じて、様々なオープンソースの接続プール実装から選択できます。
  • データ アクセス フレームワーク: アプリケーションでは通常、 マイバティス休止状態などのデータ アクセス フレームワークを使用して、データベース アクセス操作をさらに簡素化および管理します。
  • アプリケーション実装:アプリケーションロジックは、データベースにどのコマンドをいつ送信するかを制御します。一部のアプリケーションでは、トランザクションの開始とコミットのロジックを管理するために春のトランザクションアスペクトを使用します。

Java application components

上の図から、 Javaアプリケーションが次のことを実行する可能性があることがわかります。

  • TiDB と対話するには、JDBC API を介して MySQL プロトコルを実装します。
  • 接続プールから永続的な接続を取得します。
  • MyBatis などのデータ アクセス フレームワークを使用して、SQL ステートメントを生成および実行します。
  • Spring トランザクションを使用して、トランザクションを自動的に開始または停止します。

このドキュメントの残りの部分では、上記のコンポーネントを使用してJavaアプリケーションを開発する際に発生する問題とその解決策について説明します。

JDBC

Javaアプリケーションは様々なフレームワークでカプセル化されたできます。ほとんどのフレームワークでは、データベースサーバーとやり取りするために最下層でJDBC APIが呼び出されます。JDBCでは、以下の点に重点を置くことをお勧めします。

  • JDBC APIの使用選択
  • API実装者のパラメータ設定

JDBC API

JDBC APIの使用方法については、 JDBC公式チュートリアル参照してください。このセクションでは、いくつかの重要なAPIの使用方法について説明します。

準備APIを使用する

OLTP(オンライントランザクション処理)シナリオでは、プログラムからデータベースに送信されるSQL文は複数の種類に分かれており、パラメータ変更を削除すると使い尽くされる可能性があります。そのため、通常のテキストファイルからの実行ではなく準備された声明使用し、Prepared Statementを再利用して直接実行することをお勧めします。これにより、TiDBでSQL実行プランを繰り返し解析および生成するオーバーヘッドを回避できます。

現在、上位フレームワークのほとんどはSQL実行時にPrepare APIを呼び出します。開発でJDBC APIを直接使用する場合は、Prepare APIを選択する際に注意してください。

また、MySQL Connector/J のデフォルト実装では、クライアント側のステートメントのみが前処理され、クライアント側で?が置換された後、テキストファイルでサーバーに送信されます。そのため、TiDBサーバーでステートメントの前処理を実行する前に、Prepare API の使用に加えて、JDBC 接続パラメータのuseServerPrepStmts = trueも設定する必要があります。詳細なパラメータ設定については、 MySQL JDBCパラメータ参照してください。

バッチAPIを使用する

バッチ挿入の場合は、 addBatch / executeBatch API使用できます。3 addBatch()方法は、複数の SQL 文を最初にクライアントでキャッシュし、次にexecuteBatch方法を呼び出したときにそれらをまとめてデータベースサーバーに送信するために使用されます。

注記:

デフォルトのMySQL Connector/J実装では、 addBatch()でバッチに追加されたSQL文の送信はexecuteBatch()呼び出された時点まで遅延されますが、実際のネットワーク転送では文は1つずつ送信されます。そのため、この方法では通常、通信オーバーヘッドは削減されません。

バッチネットワーク転送を行う場合は、JDBC接続パラメータのrewriteBatchedStatements = true設定する必要があります。詳細なパラメータ設定については、 バッチ関連のパラメータ参照してください。

StreamingResultを使用して実行結果を取得します

多くの場合、JDBCは実行効率を向上させるために、クエリ結果を事前に取得し、デフォルトでクライアントのメモリに保存します。しかし、クエリが返す結果セットが非常に大きい場合、クライアントはデータベースサーバーに一度に返されるレコード数を減らすよう要求し、クライアントのメモリが準備できて次のバッチを要求するまで待機することがよくあります。

通常、JDBC には次の 2 種類の処理方法があります。

  • FetchSize Integer.MIN_VALUEに設定するクライアントがキャッシュしないようにします。クライアントはStreamingResultを介してネットワーク接続から実行結果を読み取ります。

    クライアントがストリーミング読み取り方式を使用する場合、ステートメントを使用してクエリを実行する前に、読み取りを完了するか、 resultset閉じる必要があります。そうでない場合は、エラー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.が返されます。

    クライアントが読み取りを完了するかresultset閉じる前にクエリでこのようなエラーが発生するのを回避するには、URL にclobberStreamingResults=trueパラメータを追加します。これにより、 resultset自動的に閉じられますが、前のストリーミングクエリで読み取られるべき結果セットは失われます。

  • カーソル フェッチを使用するには、まず正の整数としてFetchSizeを設定する設定し、JDBC URL でuseCursorFetch=true設定します。

TiDB は両方の方法をサポートしていますが、実装がより単純で実行効率が優れているため、最初の方法を使用することをお勧めします。

MySQL JDBCパラメータ

JDBCは通常、実装関連の設定をJDBC URLパラメータの形で提供します。このセクションではMySQL Connector/Jのパラメータ設定 (MariaDBをご利用の場合はMariaDBのパラメータ設定 )について説明します。このドキュメントではすべての設定項目を網羅することはできないため、主にパフォーマンスに影響を与える可能性のあるいくつかのパラメータに焦点を当てます。

このセクションでは、 Prepareに関連するパラメータを紹介します。

useServerPrepStmts

デフォルトではuseServerPrepStmtsfalseに設定されています。つまり、Prepare API を使用した場合でも、「準備」操作はクライアント側でのみ実行されます。サーバーの解析オーバーヘッドを回避するため、同じ SQL 文で Prepare API を複数回使用する場合は、この設定をtrueに設定することをお勧めします。

この設定がすでに有効になっていることを確認するには、次の操作を実行します。

  • TiDB 監視ダッシュボードに移動し、クエリ サマリー>インスタンス別の CPSを通じて要求コマンド タイプを表示します。
  • リクエスト内のCOM_QUERY COM_STMT_EXECUTEまたはCOM_STMT_PREPAREに置き換えられた場合、この設定はすでに有効になっていることを意味します。
cachePrepStmts

useServerPrepStmts=trueではサーバーがプリペアドステートメントを実行できますが、デフォルトではクライアントはプリペアドステートメントを毎回実行した後に閉じ、再利用しません。つまり、「準備」操作はテキストファイルの実行ほど効率的ではありません。この問題を解決するには、 useServerPrepStmts=true設定した後、 cachePrepStmts=trueも設定することをお勧めします。これにより、クライアントはプリペアドステートメントをキャッシュできるようになります。

この設定がすでに有効になっていることを確認するには、次の操作を実行します。

  • TiDB 監視ダッシュボードに移動し、クエリ サマリー>インスタンス別の CPSを通じて要求コマンド タイプを表示します。
  • リクエスト内のCOM_STMT_EXECUTEの数がCOM_STMT_PREPARE数よりはるかに多い場合、この設定はすでに有効になっていることを意味します。

また、 useConfigs=maxPerformance設定すると、 cachePrepStmts=true含む複数のパラメータが同時に設定されます。

prepStmtCacheSqlLimit

cachePrepStmts設定した後は、 prepStmtCacheSqlLimit設定にも注意してください(デフォルト値は256です)。この設定は、クライアントにキャッシュされるプリペアドステートメントの最大長を制御します。

この最大長を超えるPrepared Statementはキャッシュされず、再利用できません。この場合、アプリケーションの実際のSQLの長さに応じて、この設定の値を増やすことを検討してください。

次の場合は、この設定が小さすぎないかどうかを確認する必要があります。

  • TiDB 監視ダッシュボードに移動し、クエリ サマリー>インスタンス別の CPSを通じて要求コマンド タイプを表示します。
  • そして、 cachePrepStmts=true設定されていることがわかりますが、 COM_STMT_PREPAREまだCOM_STMT_EXECUTEとほぼ同じで、 COM_STMT_CLOSE存在します。
prepStmtCacheSize

prepStmtCacheSizeキャッシュされるプリペアドステートメントの数を制御します(デフォルト値は25です)。アプリケーションで多くの種類のSQL文を「準備」する必要があり、プリペアドステートメントを再利用したい場合は、この値を増やすことができます。

この設定がすでに有効になっていることを確認するには、次の操作を実行します。

  • TiDB 監視ダッシュボードに移動し、クエリ サマリー>インスタンス別の CPSを通じて要求コマンド タイプを表示します。
  • リクエスト内のCOM_STMT_EXECUTEの数がCOM_STMT_PREPARE数よりはるかに多い場合、この設定はすでに有効になっていることを意味します。

バッチ書き込み処理中は、 rewriteBatchedStatements=true設定することをお勧めします。 addBatch()またはexecuteBatch()使用した後でも、JDBCはデフォルトでSQLを1つずつ送信します。例:

pstmt = prepare("insert into t (a) values(?)"); pstmt.setInt(1, 10); pstmt.addBatch(); pstmt.setInt(1, 11); pstmt.addBatch(); pstmt.setInt(1, 12); pstmt.executeBatch();

Batch方法が使用されていますが、TiDB に送信される SQL ステートメントは個別のINSERTステートメントのままです。

insert into t(a) values(10); insert into t(a) values(11); insert into t(a) values(12);

ただし、 rewriteBatchedStatements=true設定すると、TiDB に送信される SQL ステートメントはINSERTの単一のステートメントになります。

insert into t(a) values(10),(11),(12);

INSERT番目の文の書き換えは、複数の「values」キーワードの後の値を連結して、1 つの SQL 文を作成するというものです。3 INSERT文に他の違いがある場合は、書き換えることはできません。例えば、次のようになります。

insert into t (a) values (10) on duplicate key update a = 10; insert into t (a) values (11) on duplicate key update a = 11; insert into t (a) values (12) on duplicate key update a = 12;

上記のINSERT文を1つの文に書き直すことはできません。しかし、3つの文を次のように書き換えると、

insert into t (a) values (10) on duplicate key update a = values(a); insert into t (a) values (11) on duplicate key update a = values(a); insert into t (a) values (12) on duplicate key update a = values(a);

すると、書き換え要件を満たします。上記のINSERTは、次の1つの文に書き換えられます。

insert into t (a) values (10), (11), (12) on duplicate key update a = values(a);

バッチ更新中に3つ以上の更新が発生した場合、SQL文は書き換えられ、複数のクエリとして送信されます。これにより、クライアントからサーバーへのリクエストのオーバーヘッドは効果的に削減されますが、副作用として生成されるSQL文のサイズが大きくなります。例えば、次のようになります。

update t set a = 10 where id = 1; update t set a = 11 where id = 2; update t set a = 12 where id = 3;

また、 クライアントのバグため、バッチ更新中にrewriteBatchedStatements=trueuseServerPrepStmts=true設定する場合は、このバグを回避するためにallowMultiQueries=trueパラメータも設定することをお勧めします。

パラメータを統合する

監視中に、アプリケーションがTiDBクラスタに対してINSERT操作しか実行していないにもかかわらず、冗長なSELECTステートメントが多数存在することに気付く場合があります。これは通常、JDBCが設定を照会するためにいくつかのSQLステートメント(例えばselect @@session.transaction_read_only )を送信するために発生します。これらのSQLステートメントはTiDBには役に立たないため、余分なオーバーヘッドを回避するためにuseConfigs=maxPerformance設定することをお勧めします。

useConfigs=maxPerformanceには一連の設定が含まれています。MySQL Connector/J 8.0とMySQL Connector/J 5.1の詳細な設定については、それぞれmysql-コネクタ-j 8.0mysql-コネクタ-j 5.1参照してください。

設定後、監視をチェックして、 SELECTステートメントの数が減っていることを確認できます。

注記:

useConfigs=maxPerformance有効にするには、MySQL Connector/J バージョン 8.0.33 以降が必要です。詳細についてはMySQL JDBC バグ参照してください。

TiDBは、タイムアウトを制御するMySQL互換パラメータを2つ提供していますwait_timeoutmax_execution_time 。これらの2つのパラメータは、それぞれJavaアプリケーションとの接続アイドルタイムアウトと、接続中のSQL実行のタイムアウトを制御します。つまり、これらのパラメータはTiDBとJavaアプリケーション間の接続における最長アイドル時間と最長ビジー時間を制御します。両方のパラメータのデフォルト値は0で、デフォルトでは接続が無限にアイドル状態およびビジー状態(1つのSQL文の実行時間が無制限)になります。

しかし、実際の本番環境では、アイドル接続や実行時間が長すぎるSQL文は、データベースやアプリケーションに悪影響を及ぼします。アイドル接続やSQL文の実行時間が長すぎるのを回避するには、アプリケーションの接続文字列でこれらの2つのパラメータを設定できます。例えば、 sessionVariables=wait_timeout=3600 (1時間)とsessionVariables=max_execution_time=300000 (5分)を設定します。

一般的なJDBC接続文字列パラメータ

上記のパラメータ値を組み合わせると、JDBC 接続文字列の構成は次のようになります。

jdbc:mysql://<IP_ADDRESS>:<PORT_NUMBER>/<DATABASE_NAME>?characterEncoding=UTF-8&useSSL=false&useServerPrepStmts=true&cachePrepStmts=true&prepStmtCacheSqlLimit=10000&prepStmtCacheSize=1000&useConfigs=maxPerformance&rewriteBatchedStatements=true

注記:

パブリックネットワーク経由で接続する場合は、 useSSL=trueTiDBクライアントとサーバー間のTLSを有効にする設定する必要があります。

接続プール

TiDB (MySQL) 接続の構築は、TCP 接続の構築に加えて接続認証も必要となるため、比較的コストがかかります(少なくとも OLTP シナリオでは)。そのため、クライアントは通常、TiDB (MySQL) 接続を接続プールに保存し、再利用できるようにします。

TiDB は次のJava接続プールをサポートしています。

実際には、一部の接続プールは特定のアクティブセッションを永続的に使用する場合があります。接続の総数はTiDBコンピューティングノード間で均等に分散されているように見えますが、アクティブ接続の不均一な分散は、実際の負荷の不均衡につながる可能性があります。分散シナリオでは、接続ライフサイクルを効果的に管理し、アクティブ接続が特定のノードに固定されるのを防ぎ、バランスの取れた負荷分散を実現するHikariCPの使用をお勧めします。

一般的な接続プールの構成

以下は、 HikariCPの設定例です。

hikari: maximumPoolSize: 20 poolName: hikariCP connectionTimeout: 30000 maxLifetime: 1200000 keepaliveTime: 120000

各パラメータの説明は以下のとおりです。詳細はHikariCP公式ドキュメントを参照してください。

  • maximumPoolSize : プール内の最大接続数。デフォルト値は10です。コンテナ環境では、 Javaアプリケーションで利用可能なCPUコア数の4~10倍に設定することをお勧めします。この値を高く設定しすぎるとリソースの無駄遣いにつながる可能性があり、低く設定しすぎると接続の取得が遅くなる可能性があります。詳細はプールのサイズについて参照してください。
  • minimumIdle : HikariCP、このパラメータを設定しないことを推奨します。デフォルト値はmaximumPoolSizeで、接続プールのスケーリングが無効になります。これにより、トラフィックの急増時でも接続が確実に利用可能になり、接続作成による遅延を回避できます。
  • connectionTimeout : アプリケーションがプールから接続を取得するまでの最大待機時間(ミリ秒)。デフォルト値は30000ミリ秒(30秒)です。この時間内に利用可能な接続を取得できない場合は、例外SQLExceptionが発生します。
  • maxLifetime : プール内の接続の最大存続時間(ミリ秒単位)。デフォルト値は1800000ミリ秒(30分)です。使用中の接続は影響を受けません。接続が閉じられた後、この設定に従って削除されます。この値が低すぎると、再接続が頻繁に発生する可能性があります。4 graceful-wait-before-shutdown使用する場合は、この値が待機時間よりも小さいことを確認してください。
  • keepaliveTime : プール内の接続に対するキープアライブ操作の間隔(ミリ秒単位)。この設定は、データベースまたはネットワークのアイドルタイムアウトによる切断を防ぐのに役立ちます。デフォルト値は120000ミリ秒(2分)です。プールは、アイドル接続を維持するためにJDBC4 isValid()方式を優先的に使用します。

プローブ構成

接続プールは、次のようにクライアントから TiDB への永続的な接続を維持します。

  • v5.4 より前では、TiDB はデフォルトでクライアント接続をプロアクティブに閉じません (エラーが報告されない限り)。
  • バージョン5.4以降、TiDBはデフォルトで28800秒(つまり8時間)の非アクティブ状態が続くとクライアント接続を自動的に閉じます。このタイムアウト設定は、TiDBとMySQL互換のwait_timeout変数を使用して制御できます。詳細については、 JDBCクエリタイムアウト参照してください。

さらに、クライアントとTiDBの間には、 LVSHAプロキシようなネットワークプロキシが存在する場合があります。これらのプロキシは通常、一定のアイドル時間(プロキシのアイドル設定によって決定されます)が経過すると、接続をプロアクティブにクリーンアップします。接続プールは、プロキシのアイドル設定を監視するだけでなく、キープアライブのために接続を維持またはプローブする必要があります。

Javaアプリケーションで次のエラーが頻繁に表示される場合:

The last packet sent successfully to the server was 3600000 milliseconds ago. The driver has not received any packets from the server. com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failure

n milliseconds ago分のn 0または非常に小さい値である場合、通常は実行されたSQL操作によってTiDBが異常終了したことが原因です。原因を特定するには、TiDBのstderrログを確認することをお勧めします。

n非常に大きな値(上記の例では3600000など)の場合、この接続は長時間アイドル状態のままで、その後中間プロキシによって切断された可能性があります。通常の解決策は、プロキシのアイドル設定の値を増やし、接続プールで以下の処理を実行することです。

  • 毎回接続を使用する前に接続が利用可能かどうかを確認してください
  • 別のスレッドを使用して、接続が利用可能かどうかを定期的に確認します。
  • 接続を維持するために定期的にテストクエリを送信する

接続プールの実装によっては、上記の方法のうち1つ以上をサポートしている場合があります。対応する設定については、接続プールのドキュメントをご確認ください。

データアクセスフレームワーク

アプリケーションでは、データベース アクセスを簡素化するために、何らかのデータ アクセス フレームワークを使用することが多いです。

マイバティス

マイバティス 、広く普及しているJavaデータアクセスフレームワークです。主にSQLクエリの管理と、結果セットとJavaオブジェクトのマッピングに使用されます。MyBatisはTiDBと高い互換性があります。MyBatisはこれまでの歴史から、問題が発生することはほとんどありません。

このドキュメントでは主に以下の構成に焦点を当てています。

マッパーパラメータ

MyBatis Mapper は次の 2 つのパラメータをサポートしています。

  • select 1 from t where id = #{param1} Prepared Statement としてselect 1 from t where id =?に変換され、「準備済み」となり、実際のパラメータは再利用のために使用されます。このパラメータを前述の Prepare 接続パラメータと併用すると、最高のパフォーマンスが得られます。
  • select 1 from t where id = ${param2}テキストファイルとしてselect 1 from t where id = 1に置き換えられ、実行されます。この文が異なるパラメータに置き換えられて実行されると、MyBatis は TiDB に文を「準備」するための異なるリクエストを送信します。これにより、TiDB は多数の Prepared Statement をキャッシュする可能性があり、この方法で SQL 操作を実行するとインジェクションのセキュリティリスクが生じます。

動的SQLバッチ

動的SQL - foreach

複数のINSERT文をinsert ... values(...), (...), ...形式に自動書き換えるために、前述のようにJDBCでrewriteBatchedStatements=true設定することに加え、MyBatisでは動的SQLを使用して半自動でバッチ挿入を生成することもできます。以下のマッパーを例に挙げましょう。

<insert id="insertTestBatch" parameterType="java.util.List" fetchSize="1"> insert into test (id, v1, v2) values <foreach item="item" index="index" collection="list" separator=","> ( #{item.id}, #{item.v1}, #{item.v2} ) </foreach> on duplicate key update v2 = v1 + values(v1) </insert>

このマッパーはinsert on duplicate key update文を生成します。その後に続く(?,?,?)の「値」の数は、渡されたリストの数によって決まります。最終的な効果はrewriteBatchStatements=true使用した場合と似ており、クライアントとTiDB間の通信オーバーヘッドも効果的に削減されます。

前述のように、準備済みステートメントの最大長がprepStmtCacheSqlLimitを超えるとキャッシュされなくなることにも注意する必要があります。

ストリーミング結果

前のセクション 、JDBCで実行結果をストリーム読み取りする方法が導入されました。JDBCの対応する設定に加えて、MyBatisで非常に大きな結果セットを読み取る場合は、以下の点にも注意する必要があります。

  • マッパー設定で単一のSQL文にfetchSize設定できます(前のコードブロックを参照)。これはJDBCでsetFetchSize呼び出すのと同じ効果があります。
  • クエリ インターフェイスをResultHandlerで使用すると、一度に結果セット全体を取得することを回避できます。
  • ストリーム読み取りにはCursorクラスを使用できます。

XML を使用してマッピングを構成する場合は、マッピングの<select>セクションでfetchSize="-2147483648" ( Integer.MIN_VALUE ) を構成することによって読み取り結果をストリーミングできます。

<select id="getAll" resultMap="postResultMap" fetchSize="-2147483648"> select * from post; </select>

コードを使用してマッピングを構成する場合は、 @Options(fetchSize = Integer.MIN_VALUE)アノテーションを追加し、結果のタイプをCursorのままにして、SQL 結果をストリーミングで読み取ることができるようにすることができます。

@Select("select * from post") @Options(fetchSize = Integer.MIN_VALUE) Cursor<Post> queryAllPost();

ExecutorType

openSession間にExecutorType選択できます。MyBatis は 3 種類のエグゼキューターをサポートしています。

  • シンプル: 準備されたステートメントは実行ごとにJDBCに呼び出されます(JDBC構成項目cachePrepStmts有効になっている場合、繰り返しの準備されたステートメントは再利用されます)
  • 再利用: 準備済みステートメントはexecutorにキャッシュされるため、JDBC cachePrepStmtsを使用せずに準備済みステートメントの重複呼び出しを削減できます。
  • バッチ:各更新操作( INSERT / DELETE / UPDATE )はまずバッチに追加され、トランザクションがコミットされるか、 SELECTクエリが実行されるまで実行されます。JDBCレイヤーでrewriteBatchStatements有効になっている場合は、ステートメントの書き換えが試行されます。有効になっていない場合は、ステートメントが1つずつ送信されます。

通常、デフォルト値ExecutorTypeSimpleです。7 openSession呼び出す場合はExecutorType変更する必要があります。バッチ実行の場合、トランザクション内でUPDATEまたはINSERTステートメントは非常に高速に実行されるものの、データの読み取りやトランザクションのコミット時に速度が低下することがあります。これは正常な動作であるため、遅いSQLクエリのトラブルシューティングを行う際にはこの点に注意する必要があります。

春のトランザクション

現実の世界では、アプリケーションは春のトランザクションと AOP の側面を使用してトランザクションを開始および停止する場合があります。

メソッド定義に@Transactionalアノテーションを追加すると、AOPはメソッドが呼び出される前にトランザクションを開始し、メソッドが結果を返す前にトランザクションをコミットします。アプリケーションで同様のニーズがある場合は、コード内の@Transactional使用して、トランザクションの開始と終了のタイミングを判断できます。

埋め込みの特殊なケースに注意してください。このケースが発生した場合、Springは伝搬設定に基づいて異なる動作をします。

その他

このセクションでは、問題のトラブルシューティングに役立つJavaの便利なツールをいくつか紹介します。

トラブルシューティングツール

Javaアプリケーションで問題が発生し、アプリケーションロジックが不明な場合は、JVMの強力なトラブルシューティングツールの使用をお勧めします。以下に、一般的なツールをいくつかご紹介します。

jスタック

jスタックは Go の pprof/goroutine に似ており、プロセスがスタックする問題を簡単にトラブルシューティングできます。

jstack pid実行すると、対象プロセス内のすべてのスレッドのIDとスタック情報を出力できます。デフォルトではJavaスタックのみが出力されます。JVM内のC++スタックも同時に出力したい場合は、 -mオプションを追加してください。

jstack を複数回使用することで、スタックしている問題 (たとえば、Mybatis で Batch ExecutorType を使用しているためにアプリケーションのビューからのクエリが遅いなど) やアプリケーションのデッドロックの問題 (たとえば、アプリケーションが SQL ステートメントを送信する前にロックをプリエンプトしているために SQL ステートメントを送信しないなど) を簡単に見つけることができます。

さらに、 top -p $ PID -HまたはJava のSwiss Knife は、スレッド ID を確認する一般的な方法です。また、「スレッドが大量の CPU リソースを占有し、何を実行しているのかわからない」という問題を特定するには、次の手順を実行してください。

  • スレッド ID を 16 進数に変換するにはprintf "%x\n" pid使用します。
  • 対応するスレッドのスタック情報を見つけるには、jstack 出力に移動します。

jmapとmat

Go の pprof/heap とは異なり、 jmapプロセス全体のメモリスナップショットをダンプし (Go ではディストリビューターのサンプリング)、そのスナップショットを別のツールマットで分析できます。

mat を使用すると、プロセス内のすべてのオブジェクトの関連情報と属性を確認できるほか、スレッドの実行状態を観察することもできます。例えば、mat を使用すると、現在のアプリケーションに存在する MySQL 接続オブジェクトの数や、各接続オブジェクトのアドレスとステータス情報を確認できます。

mat はデフォルトでは到達可能なオブジェクトのみを処理することに注意してください。若いGCの問題をトラブルシューティングしたい場合は、mat の設定を調整して到達不可能なオブジェクトを表示できます。また、若いGCの問題(または多数の短命オブジェクト)のメモリ割り当てを調査するには、 Java Flight Recorder を使用する方が便利です。

トレース

オンラインアプリケーションは通常、コードの変更をサポートしていませんが、 Javaで動的なインストルメンテーションを実行して問題を特定することが求められることがよくあります。そのため、btraceやarthas traceの使用は良い選択肢です。これらのツールは、アプリケーションプロセスを再起動することなく、トレースコードを動的に挿入できます。

フレームグラフ

Javaアプリケーションでフレームグラフを取得するのは面倒です。詳細についてはJava Flame Graphs の紹介: みんなに火を!参照してください。

結論

このドキュメントでは、データベースとやり取りする一般的なJavaコンポーネントに基づいて、TiDBを使用したJavaアプリケーション開発における一般的な問題と解決策について説明します。TiDBはMySQLプロトコルと高い互換性があるため、MySQLベースのJavaアプリケーションのベストプラクティスのほとんどがTiDBにも適用できます。

ヘルプが必要ですか?

不和またはスラック 、あるいはサポートチケットを送信するについてコミュニティに質問してください。

このページは役に立ちましたか?