ホームページ >Java >&#&チュートリアル >SpringBootHikariCP接続プールの作成方法
一般的なオブジェクト プールの構造を理解するために、まず Java の共通オブジェクト プーリング パッケージ Commons Pool 2 について学習しましょう。
ビジネス ニーズに応じて、この API セットを使用すると、オブジェクト プーリング管理を簡単に実現できます。
<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)
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)。これにより、通常のサービスが実行できなくなります。 。
面接の質問
面接中によく尋ねられるのは、「タイムアウト パラメーターをどのくらいの時間設定しますか?」という質問です。私のアプローチは、最大待機時間です。インターフェイスが許容できる最大遅延に設定されます。
たとえば、通常のサービスの応答時間が 10 ミリ秒程度で、1 秒に達すると行き詰まりを感じる場合、このパラメータを 500 ~ 1000 ミリ秒に設定しても問題ありません。
タイムアウト後、NoSuchElementException 例外がスローされ、リクエストは他のビジネス スレッドに影響を与えることなくすぐに失敗します。この Fail Fast のアイデアはインターネットで広く使用されています。
evcit という単語が含まれるパラメータは、主にオブジェクトの削除を処理するために使用されます。プールされたオブジェクトの作成および破棄操作には時間がかかるだけでなく、実行時にシステム リソースも占有します。
たとえば、接続プールは複数の接続を占有し、スレッド プールはスケジューリングのオーバーヘッドを増加させます。ビジネスでバースト トラフィックが発生すると、通常の要件を超えるオブジェクト リソースがプールから適用されます。これらのオブジェクトが使用されなくなったら、クリーンアップする必要があります。
minEvictableIdleTimeMillis パラメータで指定された値を超えるオブジェクトは強制的にリサイクルされます。この値のデフォルトは 30 分です。softMinEvictableIdleTimeMillis パラメータも同様ですが、現在のオブジェクト数が minIdle より大きい場合にのみ削除されます。 , したがって、前者の行動はより暴力的です。
また、testOnCreate、testOnBorrow、testOnReturn、testwhileIdle の 4 つのテスト パラメーターもあり、それぞれ作成、取得、返却、アイドル検出中にプールされたオブジェクトの有効性をチェックするかどうかを指定します。
これらの検出を有効にすると、リソースの有効性を確保できますが、パフォーマンスが消費されるため、デフォルトは false です。
運用環境では、リソースの可用性と効率を確保するために、testwhileIdle を true にのみ設定し、アイドル検出間隔 (timeBetweenEvictionRunsMillis) を 1 分などに調整することをお勧めします。
接続プーリングを使用する場合と使用しない場合のパフォーマンスの差はどれくらいですか?
次は、単純な 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);
@Benchmarkpublicvoid testPool() { Jedis jedis = pool.getResource(); jedis.set("a", UUID.randomUUID().toString()); jedis.close(); } @Benchmarkpublicvoid testJedis() { Jedis jedis = new Jedis("localhost",6379); jedis.set("a", UUID.randomUUID().toString()); jedis.close(); }//此处省略若干行}
将测试结果使用 meta-chart 作图,展示结果如下图所示,可以看到使用了连接池的方式,它的吞吐量是未使用连接池方式的 5 倍!
HikariCP 源于日语“光る”,光的意思,寓意软件工作速度和光速一样快,它是 SpringBoot 中默认的数据库连接池。
数据库是我们工作中经常使用到的组件,针对数据库设计的客户端连接池是非常多的,它的设计原理与我们在本文开头提到的基本一致,可以有效地减少数据库连接创建、销毁的资源消耗。
同是连接池,它们的性能也是有差别的,下图是 HikariCP 官方的一张测试图,可以看到它优异的性能,官方的 JMH 测试代码见 Github。
一般面试题是这么问的:HikariCP 为什么快呢?
主要有三个方面:
它使用 FastList 替代 ArrayList,通过初始化的默认值,减少了越界检查的操作
优化并精简了字节码,通过使用 Javassist,减少了动态代理的性能损耗,比如使用 invokestatic 指令代替 invokevirtual 指令
实现了无锁的 ConcurrentBag,减少了并发场景下的锁竞争
HikariCP 对性能的一些优化操作,是非常值得我们借鉴的,在之后的博客中,我们将详细分析几个优化场景。
数据库连接池同样面临一个最大值(maximumPoolSize)和最小值(minimumIdle)的问题。一个在面试中经常被问到的问题是:你通常会将连接池设置为多大?
许多学生误以为,连接池设置得越大越好,有些学生甚至将该值设置为1000以上。
根据经验,数据库连接,只需要 20~50 个就够用了。具体的大小,要根据业务属性进行调整,但大得离谱肯定是不合适的。
HikariCP 官方是不推荐设置 minimumIdle 这个值的,它将被默认设置成和 maximumPoolSize 一样的大小。如果你的数据库Server端的连接资源空闲很多,你可以考虑禁用连接池的动态调整功能。
另外,根据数据库查询和事务类型,一个应用中是可以配置多个数据库连接池的,这个优化技巧很少有人知道,在此简要描述一下。
业务类型通常有两种:一种需要快速的响应时间,把数据尽快返回给用户;另外一种是可以在后台慢慢执行,耗时比较长,对时效性要求不高。
如果这两种业务类型,共用一个数据库连接池,就容易发生资源争抢,进而影响接口响应速度。
虽然微服务能够解决这种情况,但大多数服务是没有这种条件的,这时就可以对连接池进行拆分。
如图,在同一个业务中,根据业务的属性,我们分了两个连接池,就是来处理这种情况的。
HikariCP 还提到了另外一个知识点,在 JDBC4 的协议中,通过 Connection.isValid() 就可以检测连接的有效性。
这样,我们就不用设置一大堆的 test 参数了,HikariCP 也没有提供这样的参数。
到了这里你可能会发现池(Pool)与缓存(Cache)有许多相似之处。
它们有一个共同点,即将经过加工的对象存储在相对高速的区域中。我习惯性将缓存看作是数据对象,而把池中的对象看作是执行对象。缓存中的数据有一个命中率问题,而池中的对象一般都是对等的。
考虑下面一个场景,jsp 提供了网页的动态功能,它可以在执行后,编译成 class 文件,加快执行速度;再或者,一些媒体平台,会将热门文章,定时转化成静态的 html 页面,仅靠 nginx 的负载均衡即可应对高并发请求(动静分离)。
这些时候,你很难说清楚,这是针对缓存的优化,还是针对对象进行了池化,它们在本质上只是保存了某个执行步骤的结果,使得下次访问时不需要从头再来。
我通常把这种技术叫作结果缓存池(Result Cache Pool),属于多种优化手段的综合。
下面我来简单总结一下本文的内容重点:我们从 Java 中最通用的公用池化包 Commons Pool 2 说起,介绍了它的一些实现细节,并对一些重要参数的应用做了讲解。
Jedis 就是在 Commons Pool 2 的基础上封装的,通过 JMH 测试,我们发现对象池化之后,有了接近 5 倍的性能提升。
接下来介绍了数据库连接池中速度很快的 HikariCP ,它在池化技术之上,又通过编码技巧进行了进一步的性能提升,HikariCP 是我重点研究的类库之一,我也建议你加入自己的任务清单中。
总体来说,当你遇到下面的场景,就可以考虑使用池化来增加系统性能:
对象的创建或者销毁,需要耗费较多的系统资源
对象的创建或者销毁,耗时长,需要繁杂的操作和较长时间的等待
对象创建后,通过一些状态重置,可被反复使用
将对象池化之后,只是开启了第一步优化。要想达到最优性能,就不得不调整池的一些关键参数,合理的池大小加上合理的超时时间,就可以让池发挥更大的价值。和缓存的命中率类似,对池的监控也是非常重要的。
如下图,可以看到数据库连接池连接数长时间保持在高位不释放,同时等待的线程数急剧增加,这就能帮我们快速定位到数据库的事务问题。
平常的编码中,有很多类似的场景。比如 Http 连接池,Okhttp 和 Httpclient 就都提供了连接池的概念,你可以类比着去分析一下,关注点也是在连接大小和超时时间上。
在底层的中间件,比如 RPC,也通常使用连接池技术加速资源获取,比如 Dubbo 连接池、 Feign 切换成 httppclient 的实现等技术。
你会发现,在不同资源层面的池化设计也是类似的。在后续的文章中,我们将介绍线程池的功能,例如利用队列对任务进行二层缓冲、提供多种拒绝策略等。
线程池的这些特性,你同样可以借鉴到连接池技术中,用来缓解请求溢出,创建一些溢出策略。
现实情况中,我们也会这么做。那么具体怎么做?有哪些做法?这部分内容就留给大家思考了。
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.3.12.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.ykq</groupId> <artifactId>qy151-springboot-vue</artifactId> <version>0.0.1-SNAPSHOT</version> <name>qy151-springboot-vue</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-generator</artifactId> <version>3.5.2</version> </dependency> <dependency> <groupId>org.freemarker</groupId> <artifactId>freemarker</artifactId> <version>2.3.31</version> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.2</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.2.8</version> </dependency> <dependency> <groupId>com.github.xiaoymin</groupId> <artifactId>swagger-bootstrap-ui</artifactId> <version>1.9.6</version> </dependency> <dependency> <groupId>com.spring4all</groupId> <artifactId>swagger-spring-boot-starter</artifactId> <version>1.9.1.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.2.2</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <excludes> <exclude> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </exclude> </excludes> </configuration> </plugin> </plugins> </build> </project>
以上がSpringBootHikariCP接続プールの作成方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。