接続プールをHikariCPに置き換えると速すぎます。

背景

通常のコーディングでは、主にオブジェクト作成のコストを考慮して、いくつかのオブジェクトを保存します。

たとえば、スレッド リソース、データベース接続リソース、TCP 接続などのオブジェクトの初期化には通常時間がかかり、頻繁に適用および破棄されると、大量のシステム リソースが消費され、不必要なパフォーマンスの低下が発生します。 。

そしてこれらのオブジェクトは、軽量なリセット作業によりリサイクルして繰り返し使用できるという優れた特徴を持っています。

現時点では、仮想プールを使用してこれらのリソースを保存でき、使用するときにプールからすぐにリソースを取得できます。

Java ではプーリング技術が広く使われており、データベース接続プールやスレッド プールなどが一般的です。この記事では接続プールとスレッド プールに焦点を当てます。

共通プーリングパッケージ Commons Pool 2

オブジェクト プールの一般的な構造を理解するために、まず Java の共通プーリング パッケージである Commons Pool 2 を見てみましょう。

ビジネス ニーズに応じて、この API セットを使用してオブジェクト プール管理を簡単に実装できます。

<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-pool2 -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
    <version>2.11.1</version>
</dependency>

GenericObjectPool はオブジェクト プールのコア クラスであり、オブジェクト プール設定とオブジェクト ファクトリを渡すことで、オブジェクト プールを迅速に作成できます。

public GenericObjectPool(
            final PooledObjectFactory<T> factory,
            final GenericObjectPoolConfig<T> config)

オープンソースで無料の Spring Boot の最も完全なチュートリアルをお勧めします。

https://github.com/javastacks/spring-boot-best-practice

ケース

Redis の共通クライアントである Jedis は Commons Pool を使用して接続プールを管理しており、これがベスト プラクティスと言えます。以下の図は、ファクトリを使用してオブジェクトを作成する Jedis のメイン コード ブロックです。

オブジェクト ファクトリ クラスのメイン メソッドは makeObject で、その戻り値は PooledObject 型で、オブジェクトは new DefaultPooledObject<>(obj) を使用して単純にラップして返すことができます。

redis.clients.jedis.JedisFactory では、ファクトリを使用してオブジェクトを作成します。

@Override
public PooledObject<Jedis> makeObject() throws Exception {
  Jedis jedis = null;
  try {
    jedis = new Jedis(jedisSocketFactory, clientConfig);
    //主要的耗时操作
    jedis.connect();
    //返回包装对象
    return new DefaultPooledObject<>(jedis);
  } catch (JedisException je) {
    if (jedis != null) {
      try {
        jedis.quit();
      } catch (RuntimeException e) {
        logger.warn("Error while QUIT", e);
      }
      try {
        jedis.close();
      } catch (RuntimeException e) {
        logger.warn("Error while close", e);
      }
    }
    throw je;
  }
}

オブジェクト生成プロセスをもう一度紹介します。下の図に示すように、オブジェクトを取得するときは、まずオブジェクト プールからオブジェクトを取得しようとします。オブジェクト プールに空きオブジェクトがない場合は、オブジェクト プールで提供されるメソッドを使用します。新しいものを生成するファクトリークラス。

public T borrowObject(final Duration borrowMaxWaitDuration) throws Exception {
    //此处省略若干行
    while (p == null) {
        create = false;
        //首先尝试从池子中获取。
        p = idleObjects.pollFirst();
        // 池子里获取不到,才调用工厂内生成新实例
        if (p == null) {
            p = create();
            if (p != null) {
                create = true;
            }
        }
        //此处省略若干行
    }
    //此处省略若干行
}

オブジェクトはどこに存在しますか? このストレージの役割は、双方向キューである LinkedBlockingDeque と呼ばれる構造によって引き受けられます。

次に、GenericObjectPoolConfig の主なプロパティを確認します。

// GenericObjectPoolConfig本身的属性
private int maxTotal = DEFAULT_MAX_TOTAL;
private int maxIdle = DEFAULT_MAX_IDLE;
private int minIdle = DEFAULT_MIN_IDLE;
// 其父类BaseObjectPoolConfig的属性
private boolean lifo = DEFAULT_LIFO;
private boolean fairness = DEFAULT_FAIRNESS;
private long maxWaitMillis = DEFAULT_MAX_WAIT_MILLIS;
private long minEvictableIdleTimeMillis = DEFAULT_MIN_EVICTABLE_IDLE_TIME_MILLIS;
private long evictorShutdownTimeoutMillis = DEFAULT_EVICTOR_SHUTDOWN_TIMEOUT_MILLIS;
private long softMinEvictableIdleTimeMillis = DEFAULT_SOFT_MIN_EVICTABLE_IDLE_TIME_MILLIS;
private int numTestsPerEvictionRun = DEFAULT_NUM_TESTS_PER_EVICTION_RUN;
private EvictionPolicy<T> evictionPolicy = null;
// Only 2.6.0 applications set this
private String evictionPolicyClassName = DEFAULT_EVICTION_POLICY_CLASS_NAME;
private boolean testOnCreate = DEFAULT_TEST_ON_CREATE;
private boolean testOnBorrow = DEFAULT_TEST_ON_BORROW;
private boolean testOnReturn = DEFAULT_TEST_ON_RETURN;
private boolean testWhileIdle = DEFAULT_TEST_WHILE_IDLE;
private long timeBetweenEvictionRunsMillis = DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS;
private boolean blockWhenExhausted = DEFAULT_BLOCK_WHEN_EXHAUSTED;

多くのパラメーターがありますが、パラメーターの意味を理解するために、まずプール全体のプールされたオブジェクトのライフ サイクルを見てみましょう。

次の図に示すように、プールには 2 つの主な操作があります。1 つはビジネス スレッドで、もう 1 つは検出スレッドです。

オブジェクト プールを初期化するときは、次の 3 つの主要なパラメータを指定する必要があります。

  • maxTotal オブジェクトプールで管理されるオブジェクトの上限
  • maxIdle 最大アイドル数
  • minIdle 最小アイドル数

このうち、maxTotal はビジネス スレッドに関連しており、ビジネス スレッドがオブジェクトを取得する場合、まずアイドル状態のオブジェクトがあるかどうかを確認します。

存在する場合は 1 つを返し、存在しない場合は作成ロジックを入力します。この時点で、プール内の数が最大値に達している場合、作成は失敗し、空のオブジェクトが返されます。

オブジェクトを取得する際に、アプリケーション側のパフォーマンスに比較的大きな影響を与える最大待ち時間(maxWaitMillis)という非常に重要なパラメータがあります。このパラメーターのデフォルトは -1 です。これは、オブジェクトが解放されるまでタイムアウトしないことを意味します。

次の図に示すように、オブジェクトの作成が非常に遅い場合、またはオブジェクトの使用が非常に混雑している場合、ビジネス スレッドはブロックされ続け (blockWhenExhausted のデフォルトは true)、通常のサービスが実行できなくなります。

面接の質問

一般的な面接官は、「タイムアウト パラメータをどれくらいの大きさに設定しますか?」と尋ねます。私は通常、インターフェイスが許容できる最大遅延として最大待機時間を設定します。

最新の Java 面接の質問を最も包括的にまとめたもの: https://www.javastack.cn/mst/

たとえば、通常のサービスの応答時間が 10ms 程度の場合、1 秒を超えると引っかかりを感じるため、このパラメータは 500 ~ 1000ms に設定できます。

タイムアウト後は NoSuchElementException がスローされ、他のビジネス スレッドに影響を与えることなくリクエストはすぐに失敗します。この Fail Fast の考え方はインターネットで広く使用されています。

evcit という単語が含まれるパラメータは、主にオブジェクトの削除を扱います。初期化と破棄にコストがかかることに加えて、プールされたオブジェクトは実行時にシステム リソースも占有します。

たとえば、接続プールは複数の接続を占有し、スレッド プールはスケジューリングのオーバーヘッドを増加させます。突然の交通状況下では、企業は通常の状況を超えてオブジェクト リソースを申請し、それらをプールに入れます。これらのオブジェクトが使用されなくなったら、クリーンアップする必要があります。

minEvictableIdleTimeMillis パラメータで指定された値を超えるオブジェクトは強制的にリサイクルされます。この値はデフォルトで 30 分です。softMinEvictableIdleTimeMillis パラメータも同様ですが、現在のオブジェクト数が minIdle よりも大きい場合にのみ削除されるため、前者のアクションはより暴力的なものもある。

また、testOnCreate、testOnBorrow、testOnReturn、testwhileIdle の 4 つのテスト パラメーターもあり、それぞれ作成、取得、返却、アイドル検出中にプールされたオブジェクトの有効性をチェックするかどうかを指定します。

これらのチェックをオンにすると、リソースの可用性が確保されますが、パフォーマンスが消費されるため、デフォルトは false です。

運用環境では、リソースの可用性と効率を確保するために、testwhileIdle のみを true に設定し、アイドル検出間隔 (timeBetweenEvictionRunsMillis) を 1 分などに調整することをお勧めします。

JMHテスト

接続プールを使用した場合と使用しない場合のパフォーマンスの差はどのくらいですか?

以下は、単純な JMH テストの例 (ウェアハウスを参照) です。これは、単純な設定操作を実行し、redis のキーにランダムな値を設定します。

@Fork(2)
@State(Scope.Benchmark)
@Warmup(iterations = 5, time = 1)
@Measurement(iterations = 5, time = 1)
@BenchmarkMode(Mode.Throughput)
public class JedisPoolVSJedisBenchmark {
   JedisPool pool = new JedisPool("localhost", 6379);

   @Benchmark
   public void testPool() {
       Jedis jedis = pool.getResource();
       jedis.set("a", UUID.randomUUID().toString());
       jedis.close();
   }

   @Benchmark
   public void testJedis() {
       Jedis jedis = new Jedis("localhost", 6379);
       jedis.set("a", UUID.randomUUID().toString());
       jedis.close();
   }
   //此处省略若干行
}

メタチャートを使用してテスト結果をプロットし、次の図のように結果を表示すると、接続プール方式を使用しており、接続プールを使用しない方式に比べてスループットが 5 倍になっていることがわかります。

データベース接続プールHikariCP

HikariCP は日本語の「光る」に由来しており、これはソフトウェアが光の速度と同じくらい速く動作することを意味しており、SpringBoot のデフォルトのデータベース接続プールです。

データベースは私たちの仕事で頻繁に使用するコンポーネントです。データベース用に設計されたクライアント接続プールが多数あります。その設計原則は基本的にこの記事の冒頭で述べたものと同じであり、作成および作成にかかるリソースを効果的に削減できます。データベース接続を破棄しています。

同じ接続プールでも、パフォーマンスは異なります。下の写真は、HikariCP の公式テスト チャートです。その優れたパフォーマンスがわかります。公式の JMH テスト コードは、Github を参照してください。

面接の一般的な質問は次のとおりです。「HikariCP はなぜ速いのですか?」

主な側面は次の 3 つです。

  • ArrayList の代わりに FastList を使用し、デフォルト値を初期化することで範囲外チェックの動作を軽減します。
  • Javassist を使用してバイトコードを最適化および簡素化し、invokevirtual 命令の代わりに invokestatic 命令を使用するなど、動的プロキシのパフォーマンス損失を削減しました。
  • ロックフリーの ConcurrentBag を実装し、同時シナリオでのロックの競合を軽減しました。

以下のブログでは、いくつかの最適化シナリオを詳細に分析します。

データベース接続プールは、最大値 (maximumPoolSize) と最小値 (minimumIdle) の問題にも直面します。ここには、面接で非常に頻繁に聞かれる質問もあります。「通常、接続プールをどのくらいの大きさに設定しますか?」

多くの学生は、接続プールのサイズは大きいほど良いと考えており、この値を 1000 を超える値に設定する学生もいますが、これは誤解です。

経験によれば、データベース接続は 20 ~ 50 だけで十分です。具体的な規模はビジネスの特性に応じて調整する必要がありますが、大きすぎるのは絶対に不適切です。

HikariCP は公式に minimumIdle の値を設定することを推奨していません。デフォルトでは、maximumPoolSize と同じサイズに設定されます。データベース サーバー側の接続リソースが比較的アイドル状態である場合は、接続プールの動的調整機能を削除することもできます。

また、データベースのクエリやトランザクションの種類に応じて、アプリケーション内に複数のデータベース接続プールを設定することができますが、この最適化手法を知っている人は少ないので、ここで簡単に説明します。

通常、ビジネスには 2 つのタイプがあり、1 つは高速な応答時間を必要とし、できるだけ早くデータをユーザーに返すもの、もう 1 つはバックグラウンドでゆっくりと実行できるため、時間がかかり、高い適時性は要求されません。

これら 2 つの種類のビジネスがデータベース接続プールを共有すると、リソースの競合が発生しやすくなり、インターフェイスの応答速度に影響を及ぼします。

マイクロサービスはこの状況を解決できますが、ほとんどのサービスにはこの状況がなく、現時点では接続プールが分割される可能性があります。

図にあるように、同じビジネスにおいて、ビジネスの属性に応じて 2 つの接続プールを分けて対応しています。

また、HikariCP は、JDBC4 プロトコルでは、Connection.isValid() を通じて接続の有効性を検出できるという別の知識ポイントについても言及しました。

この方法では、多くのテスト パラメータを設定する必要はありません。また、HikariCP にはそのようなパラメータはありません。

結果バッファプール

ここまで来ると、プールとキャッシュには多くの類似点があることがわかるでしょう。

これらの共通点は、オブジェクトが比較的高速な領域で処理および保存されることです。私は普段、キャッシュをデータ オブジェクト、プール内のオブジェクトを実行オブジェクトと考えています。キャッシュ内のデータにはヒット率の問題がありますが、プール内のオブジェクトは通常ピアです。

次のシナリオを考えてみましょう。jsp は Web ページの動的な機能を提供し、実行後にクラス ファイルにコンパイルして実行を高速化できます。または、一部のメディア プラットフォームは、nginx の負荷分散によってのみ、人気のある記事を静的な HTML ページに定期的に変換します。大量の同時リクエストを処理できます (動的および静的分離)。

このような場合、これがオブジェクトのキャッシュのための最適化なのか、それともオブジェクトのプールのための最適化なのかを判断するのは困難ですが、本質的には、特定の実行ステップの結果を保存するだけなので、次のステップを最初からやり直す必要はありません。アクセスされた時間。

私は通常、この手法を結果キャッシュ プールと呼んでいます。これは、さまざまな最適化手法を組み合わせたものです。

まとめ

この記事の要点を簡単に要約します。まず、Java で最も一般的なプーリング パッケージである Commons Pool 2 から始め、その実装の詳細をいくつか紹介し、いくつかの重要なパラメーターの適用について説明します。

Jedis は Commons Pool 2 に基づいてカプセル化されています。JMH テストを通じて、オブジェクト プーリング後はパフォーマンスが 5 倍近く向上することがわかりました。

次に、データベース接続プールで非常に高速な HikariCP を紹介しました。これはプーリング技術をベースにしており、コーディング スキルによってさらにパフォーマンスが向上します。HikariCP は、私が注力しているクラス ライブラリの 1 つです。また、あなたも参加することをお勧めします。タスクリスト。

一般に、次のシナリオが発生した場合は、システム パフォーマンスを向上させるためにプーリングの使用を検討できます。

  • オブジェクトの作成または破棄には、より多くのシステム リソースが必要です
  • オブジェクトの作成または破棄には時間がかかり、複雑な操作と長い待ち時間が必要です
  • オブジェクトの作成後は、状態をリセットすることで繰り返し使用できます。

オブジェクトをプールした後は、最適化の最初のステップのみが有効になります。最適なパフォーマンスを達成するには、プールのいくつかの重要なパラメータを調整する必要があります。適切なプール サイズと適切なタイムアウト期間を追加すると、プールの役割がさらに大きくなります。キャッシュ ヒット率と同様に、プールの監視も非常に重要です。

次の図に示すように、データベース接続プール内の接続数が解放されずに長期間高いレベルに留まり、待機中のスレッドの数が急激に増加していることがわかります。これにより、トランザクションを迅速に見つけることができます。データベースの問題。

通常のコーディングでは、同様のシナリオがたくさんあります。たとえば、Http 接続プール、Okhttp および Httpclient はどちらも接続プールの概念を提供しており、接続サイズとタイムアウトにも重点を置いて類推して分析できます。

RPC などの基盤となるミドルウェアも、通常、Dubbo 接続プール、httppclient への Feign スイッチング、その他のテクノロジなど、リソースの取得を高速化するために接続プール テクノロジを使用します。

さまざまなリソース レベルでのプーリング設計が類似していることがわかります。たとえば、スレッド プールはキューを使用して第 2 層でタスクをバッファリングし、さまざまな拒否戦略を提供します (次の記事でスレッド プールについて紹介します)。

スレッド プールのこれらの機能は、リクエストのオーバーフローを軽減し、いくつかのオーバーフロー戦略を作成するために、接続プール テクノロジで参照するために使用することもできます。

実際、私たちも同じことをしています。では、どうすればよいのでしょうか?どのような実践がありますか? この部分はみんなに考えてもらいましょう。

著作権に関する声明: この記事は CSDN ブロガー「Philadelphia Migrant Worker」の元の記事であり、CC 4.0 BY-SA 著作権契約に従っています。転載する場合は、元のソース リンクとこの声明を添付してください。

元のリンク: https://blog.csdn.net/monarch91/article/details/123867269

最近のおすすめ記事:

1. 1,000 を超える Java 面接の質問と回答 (2022 年最新バージョン)

2.素晴らしい!Java コルーチンが登場します。

3. Spring Boot 2.x チュートリアル、包括的すぎる!

4.画面を爆発や爆発で埋め尽くさないで、デコレーターモードを試してください。これがエレガントな方法です。

5.最新リリースの「Java 開発マニュアル (松山編)」をすぐにダウンロードしてください!

気分がいいので、「いいね!」+「転送」を忘れないでください!

おすすめ

転載: blog.csdn.net/youanyyou/article/details/132617954