Maison  >  Article  >  Java  >  Comment créer un pool de connexions SpringBoot HikariCP

Comment créer un pool de connexions SpringBoot HikariCP

PHPz
PHPzavant
2023-05-25 13:11:48976parcourir

Package de pooling commun Commons Pool 2

Etudions d'abord le package de pooling d'objets communs Commons Pool 2 en Java pour comprendre la structure générale du pool d'objets.

Selon nos besoins métiers, l'utilisation de cet ensemble d'API permet de mettre en œuvre facilement la gestion du pool d'objets.

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

GenericObjectPool est la classe principale du pool d'objets En passant la configuration d'un pool d'objets et d'une fabrique d'objets, vous pouvez créer rapidement un pool d'objets. Le client commun de

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

Case

Redis, Jedis, utilise Commons Pool pour gérer le pool de connexions, ce qui peut être considéré comme une bonne pratique. La figure ci-dessous est le bloc de code principal permettant aux Jedis d'utiliser des usines pour créer des objets.

La méthode principale de la classe de fabrique d'objets est makeObject. Sa valeur de retour est de type PooledObject. L'objet peut être simplement empaqueté et renvoyé en utilisant le nouveau DefaultPooledObject(obj).

redis.clients.jedis.JedisFactory, utilise l'usine pour créer des objets.

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

Introduisons à nouveau le processus de génération d'objets, comme le montre la figure ci-dessous, lors de l'acquisition d'un objet, il essaiera d'abord d'en retirer un du pool d'objets, s'il n'y a pas d'objet libre dans le pool d'objets, utilisez la méthode. fourni par la classe d'usine pour en générer un nouveau.

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

Où existe l'objet ? Cette responsabilité de stockage est supportée par une structure appelée LinkedBlockingDeque, qui est une file d'attente bidirectionnelle.

Ensuite, jetons un coup d'œil aux principales propriétés de 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;

Il existe de nombreux paramètres. Pour comprendre la signification des paramètres, jetons d'abord un coup d'œil au cycle de vie d'un objet poolé dans l'ensemble du pool.

Comme le montre la figure ci-dessous, il existe deux opérations principales du pool : l'une est le thread métier et l'autre est le thread de détection.

Comment créer un pool de connexions SpringBoot HikariCP

Lors de l'initialisation du pool d'objets, trois paramètres principaux doivent être spécifiés :

  • maxTotal La limite supérieure des objets gérés dans le pool d'objets

  • maxIdle Le nombre maximum d'inactivité

  • minIdle Le minimum nombre d'inactivité

maxTotal est lié au thread métier Lorsque le thread métier souhaite obtenir un objet, il détectera d'abord s'il existe un objet inactif.

S'il y en a un, renvoyez-en un ; sinon, entrez dans la logique de création. Si la taille maximale a été atteinte dans le pool, la création de l'objet échouera et un objet vide sera renvoyé.

Lors de l'acquisition d'un objet, il existe un paramètre très important, qui est le temps d'attente maximum (maxWaitMillis). Ce paramètre a un impact relativement important sur les performances de l'application. Ce paramètre a la valeur par défaut -1, ce qui signifie qu'il n'expirera jamais tant qu'un objet n'est pas libre.

Comme le montre la figure ci-dessous, si la création de l'objet est très lente ou si l'utilisation est très chargée, le thread métier continuera à se bloquer (blockWhenExhausted est par défaut true), ce qui entraînera l'échec de l'exécution des services normaux.

Comment créer un pool de connexions SpringBoot HikariCP

Questions d'entretien

On me demande généralement lors des entretiens : "À combien de temps allez-vous définir le paramètre de délai d'attente ?" Mon approche consiste à définir le temps d'attente maximum sur le délai maximum que l'interface peut tolérer.

Par exemple, si le temps de réponse normal d'un service est d'environ 10 ms et qu'il atteint 1 seconde, vous vous sentirez bloqué, alors vous pouvez définir ce paramètre sur 500 ~ 1 000 ms.

Après l'expiration du délai, une exception NoSuchElementException sera levée et la requête échouera rapidement sans affecter les autres threads commerciaux. Cette idée Fail Fast est largement utilisée sur Internet.

Les paramètres avec le mot evcit sont principalement utilisés pour gérer l'expulsion d'objets. Les opérations de création et de destruction d'objets regroupés consomment non seulement du temps, mais occupent également des ressources système pendant l'exécution.

Par exemple, le pool de connexions occupera plusieurs connexions et le pool de threads augmentera la surcharge de planification, etc. Lorsque l'entreprise rencontre un trafic en rafale, elle sollicite les ressources d'objet du pool qui dépassent les exigences normales. Lorsque ces objets ne sont plus utilisés, il faut les nettoyer.

Les objets qui dépassent la valeur spécifiée par le paramètre minEvictableIdleTimeMillis seront recyclés de force. Cette valeur par défaut est de 30 minutes ; le paramètre softMinEvictableIdleTimeMillis est similaire, mais il ne sera supprimé que lorsque le nombre actuel d'objets est supérieur à minIdle, donc le premier. l’action doit être plus violente.

Il existe également 4 paramètres de test : testOnCreate, testOnBorrow, testOnReturn et testWhileIdle, qui précisent respectivement s'il faut vérifier la validité de l'objet poolé lors de la création, de l'acquisition, du retour et de la détection d'inactivité.

L'activation de ces détections peut garantir l'efficacité des ressources, mais cela consommera des performances, donc la valeur par défaut est fausse.

Dans un environnement de production, il est recommandé de définir uniquement testWhileIdle sur true et d'ajuster l'intervalle de détection d'inactivité (timeBetweenEvictionRunsMillis), par exemple 1 minute, pour garantir la disponibilité et l'efficacité des ressources.

Test JMH

Quelle est l'ampleur de l'écart de performances entre l'utilisation du pooling de connexions et la non-utilisation du pooling de connexions ?

Ce qui suit est un exemple de test JMH simple (voir l'entrepôt), qui effectue une opération de définition simple pour définir une valeur aléatoire pour la clé 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 倍!

Comment créer un pool de connexions SpringBoot HikariCP

数据库连接池 HikariCP

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

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

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

Comment créer un pool de connexions SpringBoot HikariCP

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

主要有三个方面:

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

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

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

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

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

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

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

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

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

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

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

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

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

Comment créer un pool de connexions SpringBoot HikariCP

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

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

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

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

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

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

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

Comment créer un pool de connexions SpringBoot 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>

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer