Heim  >  Artikel  >  Java  >  So erstellen Sie einen SpringBoot HikariCP-Verbindungspool

So erstellen Sie einen SpringBoot HikariCP-Verbindungspool

PHPz
PHPznach vorne
2023-05-25 13:11:48943Durchsuche

Common-Pooling-Paket Commons Pool 2

Schauen wir uns zunächst das Common-Object-Pooling-Paket Commons Pool 2 in Java an, um die allgemeine Objektpoolstruktur zu verstehen.

Entsprechend unseren Geschäftsanforderungen kann mit diesem API-Satz problemlos eine Objektpoolverwaltung erreicht werden.

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
    <version>2.11.1</version>
</dependency>

GenericObjectPool ist die Kernklasse des Objektpools. Durch Übergabe der Konfiguration eines Objektpools und einer Objektfabrik können Sie schnell einen Objektpool erstellen.

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

Case

Der gemeinsame Kunde von Redis, Jedis, verwendet Commons Pool, um den Verbindungspool zu verwalten, was als Best Practice bezeichnet werden kann. Die folgende Abbildung zeigt den Hauptcodeblock für Jedis zur Verwendung von Fabriken zum Erstellen von Objekten.

Die Hauptmethode der Objektfabrikklasse ist makeObject. Ihr Rückgabewert ist vom Typ PooledObject. Das Objekt kann einfach mit new DefaultPooledObject(obj) gepackt und zurückgegeben werden.

redis.clients.jedis.JedisFactory verwendet die Factory zum Erstellen von Objekten.

@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;
  }
}

Lassen Sie uns den Objektgenerierungsprozess wie unten gezeigt vorstellen. Wenn das Objekt erfasst wird, wird zunächst versucht, eines aus dem Objektpool zu entnehmen. Die von der Klasse bereitgestellte Factory wird verwendet, um eine neue zu generieren.

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;
            }
        }
        //此处省略若干行
    }
    //此处省略若干行
}

Wo existiert das Objekt? Diese Speicherverantwortung wird von einer Struktur namens LinkedBlockingDeque übernommen, bei der es sich um eine bidirektionale Warteschlange handelt.

Schauen wir uns als Nächstes die Haupteigenschaften von GenericObjectPoolConfig an:

// 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;

Um die Bedeutung der Parameter zu verstehen, werfen wir zunächst einen Blick auf die Eigenschaften eines gepoolten Objekts im gesamten Lebenszyklus.

Wie in der folgenden Abbildung dargestellt, gibt es zwei Hauptoperationen des Pools: eine ist der Geschäftsthread und die andere ist der Erkennungsthread.

So erstellen Sie einen SpringBoot HikariCP-Verbindungspool

Bei der Initialisierung des Objektpools müssen drei Hauptparameter angegeben werden:

  • #🎜🎜 # maxTotal Die Obergrenze der im Objektpool verwalteten Objekte Mindestleerlaufzahl# 🎜🎜#

  • maxTotal bezieht sich auf den Geschäftsthread. Wenn der Geschäftsthread ein Objekt abrufen möchte, erkennt er zunächst, ob ein Leerlaufobjekt vorhanden ist.
  • Wenn es eine gibt, geben Sie eine zurück; andernfalls geben Sie die Erstellungslogik ein. Wenn die maximale Größe im Pool erreicht ist, schlägt die Objekterstellung fehl und es wird ein leeres Objekt zurückgegeben.

  • Beim Erfassen des Objekts gibt es einen sehr wichtigen Parameter, nämlich die maximale Wartezeit (maxWaitMillis). Dieser Parameter hat einen relativ großen Einfluss auf die Leistung der Anwendung. Dieser Parameter ist standardmäßig auf -1 eingestellt, was bedeutet, dass es nie zu einer Zeitüberschreitung kommt, bis ein Objekt frei ist.
  • Wie unten gezeigt, blockiert der Geschäftsthread weiterhin, wenn die Objekterstellung sehr langsam ist oder die Verwendung sehr ausgelastet ist (blockWhenExhausted ist standardmäßig true), was dazu führt, dass normale Dienste nicht ausgeführt werden können.

Interviewfrage

Während des Interviews werden Sie normalerweise gefragt: „Auf wie lange stellen Sie den Timeout-Parameter ein?“ " ? "Mein Ansatz besteht darin, die maximale Wartezeit auf die maximale Verzögerung festzulegen, die die Schnittstelle tolerieren kann.

Zum Beispiel beträgt die Reaktionszeit eines normalen Dienstes etwa 10 ms. Wenn sie 1 Sekunde erreicht, fühlen Sie sich festgefahren. Dieser Parameter kann auf 500 bis 1000 ms eingestellt werden.

Nach dem Timeout wird eine NoSuchElementException-Ausnahme ausgelöst und die Anfrage schlägt schnell fehl, ohne dass sich dies auf andere Geschäftsthreads auswirkt. Diese Fail-Fast-Idee wird im Internet häufig verwendet.

So erstellen Sie einen SpringBoot HikariCP-VerbindungspoolParameter mit dem Wort evcit werden hauptsächlich zur Objektentfernung verwendet. Die Erstellungs- und Zerstörungsvorgänge gepoolter Objekte nehmen nicht nur Zeit in Anspruch, sondern belegen zur Laufzeit auch Systemressourcen.

Zum Beispiel belegt der Verbindungspool mehrere Verbindungen und der Thread-Pool erhöht den Planungsaufwand. Wenn das Unternehmen auf einen Burst-Datenverkehr stößt, beantragt es Objektressourcen aus dem Pool, die über die normalen Anforderungen hinausgehen. Wenn diese Objekte nicht mehr verwendet werden, müssen wir sie bereinigen.

Objekte, die den durch den Parameter minEvictableIdleTimeMillis angegebenen Wert überschreiten, werden zwangsweise recycelt. Der Parameter softMinEvictableIdleTimeMillis ist ähnlich, wird jedoch nur entfernt, wenn die aktuelle Anzahl an Objekten größer ist als minIdle , daher sind die Aktionen des ersteren gewalttätiger.

Es gibt außerdem 4 Testparameter: testOnCreate, testOnBorrow, testOnReturn, testWhileIdle, die jeweils angeben, ob die Gültigkeit des gepoolten Objekts während der Erstellung, Erfassung, Rückgabe und Leerlauferkennung überprüft werden soll.

Das Aktivieren dieser Erkennungen kann die Wirksamkeit von Ressourcen sicherstellen, verbraucht jedoch Leistung, daher ist die Standardeinstellung falsch.

In einer Produktionsumgebung wird empfohlen, testWhileIdle nur auf true zu setzen und das Leerlauferkennungsintervall (timeBetweenEvictionRunsMillis) anzupassen, z. B. 1 Minute, um die Ressourcenverfügbarkeit und -effizienz sicherzustellen.

JMH-Test

Wie groß ist der Leistungsunterschied zwischen der Verwendung von Verbindungspooling und der Nichtverwendung von Verbindungspooling?

Das Folgende ist ein einfaches JMH-Testbeispiel (siehe Lager), das eine einfache Set-Operation ausführt und einen zufälligen Wert für den Redis-Schlüssel festlegt.

@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 倍!

So erstellen Sie einen SpringBoot HikariCP-Verbindungspool

数据库连接池 HikariCP

HikariCP 源于日语“光る”,光的意思,寓意软件工作速度和光速一样快,它是 SpringBoot 中默认的数据库连接池。

数据库是我们工作中经常使用到的组件,针对数据库设计的客户端连接池是非常多的,它的设计原理与我们在本文开头提到的基本一致,可以有效地减少数据库连接创建、销毁的资源消耗。

同是连接池,它们的性能也是有差别的,下图是 HikariCP 官方的一张测试图,可以看到它优异的性能,官方的 JMH 测试代码见 Github。

So erstellen Sie einen SpringBoot HikariCP-Verbindungspool

一般面试题是这么问的:HikariCP 为什么快呢?

主要有三个方面:

  • 它使用 FastList 替代 ArrayList,通过初始化的默认值,减少了越界检查的操作

  • 优化并精简了字节码,通过使用 Javassist,减少了动态代理的性能损耗,比如使用 invokestatic 指令代替 invokevirtual 指令

  • 实现了无锁的 ConcurrentBag,减少了并发场景下的锁竞争

HikariCP 对性能的一些优化操作,是非常值得我们借鉴的,在之后的博客中,我们将详细分析几个优化场景。

数据库连接池同样面临一个最大值(maximumPoolSize)和最小值(minimumIdle)的问题。一个在面试中经常被问到的问题是:你通常会将连接池设置为多大?

许多学生误以为,连接池设置得越大越好,有些学生甚至将该值设置为1000以上。

根据经验,数据库连接,只需要 20~50 个就够用了。具体的大小,要根据业务属性进行调整,但大得离谱肯定是不合适的。

HikariCP 官方是不推荐设置 minimumIdle 这个值的,它将被默认设置成和 maximumPoolSize 一样的大小。如果你的数据库Server端的连接资源空闲很多,你可以考虑禁用连接池的动态调整功能。

另外,根据数据库查询和事务类型,一个应用中是可以配置多个数据库连接池的,这个优化技巧很少有人知道,在此简要描述一下。

业务类型通常有两种:一种需要快速的响应时间,把数据尽快返回给用户;另外一种是可以在后台慢慢执行,耗时比较长,对时效性要求不高。

如果这两种业务类型,共用一个数据库连接池,就容易发生资源争抢,进而影响接口响应速度。

虽然微服务能够解决这种情况,但大多数服务是没有这种条件的,这时就可以对连接池进行拆分。

如图,在同一个业务中,根据业务的属性,我们分了两个连接池,就是来处理这种情况的。

So erstellen Sie einen SpringBoot HikariCP-Verbindungspool

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 是我重点研究的类库之一,我也建议你加入自己的任务清单中。

总体来说,当你遇到下面的场景,就可以考虑使用池化来增加系统性能:

  • 对象的创建或者销毁,需要耗费较多的系统资源

  • 对象的创建或者销毁,耗时长,需要繁杂的操作和较长时间的等待

  • 对象创建后,通过一些状态重置,可被反复使用

将对象池化之后,只是开启了第一步优化。要想达到最优性能,就不得不调整池的一些关键参数,合理的池大小加上合理的超时时间,就可以让池发挥更大的价值。和缓存的命中率类似,对池的监控也是非常重要的。

如下图,可以看到数据库连接池连接数长时间保持在高位不释放,同时等待的线程数急剧增加,这就能帮我们快速定位到数据库的事务问题。

So erstellen Sie einen SpringBoot HikariCP-Verbindungspool

平常的编码中,有很多类似的场景。比如 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>

Das obige ist der detaillierte Inhalt vonSo erstellen Sie einen SpringBoot HikariCP-Verbindungspool. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:yisu.com. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen