Spring Boot中Oracle数据库的HikariCP最佳实践

HikariCP 是一种流行的 Java 连接池,通常与 Spring Boot 一起使用。这篇博文提供了使用 Spring Boot 为 Oracle 数据库配置 HikariCP 的最佳实践。

您将了解如何配置 HikariCP 以利用 Oracle 数据库的高可用性 (HA) 功能在计划内和计划外停机期间提供连续服务。本博文中的所有建议均已使用最新的长期支持 (LTS) Oracle 数据库版本(即 19c (19.21))进行了测试。

关于UCP
Oracle 数据库提供通用连接池 (UCP),这是一个功能丰富的 Java 连接池(Spring Boot 选择之一),支持开箱即用的所有 Oracle 数据库配置和关键任务功能,例如 Oracle真正应用集群 (RAC)、Data Guard、Oracle 分片、异步 API、运行时负载平衡、XA 以及数据库驻留连接池 (DRCP) 的前端

UCP 每季度发布一次,并得到 Oracle 的全面支持。查看更多详细信息@ https://www.oracle.com/jdbc/

将 HikariCP 与 Oracle 数据库结合使用的基本步骤包括

  • (i) 配置 Oracle JDBC 驱动程序,以及
  • (ii) 配置 HikariCP。

配置 Oracle JDBC 驱动程序
将 Oracle 驱动程序添加为 Spring Initializr 中的依赖项,或手动将其添加到项目的pom.xml 中。

<properties>
    <oracle.jdbc.version>19.21.0.0</<oracle.jdbc.version>  
</properties>
 
<dependencies>

<dependency>
      <groupId>com.oracle.database.jdbc</groupId>
      <artifactId>ojdbc8</artifactId>
      <version>${oracle.jdbc.version}</version>
</dependency>

</dependencies>

使用此页面了解有关 Oracle RDBMS 和 Oracle JDBC 驱动程序的 JDK 版本兼容性的更多信息。

配置 HikariCP

  • 从第 2 版开始,Spring Boot 将 HikariCP 用作默认连接池,并通过以下 Spring Boot 启动程序临时导入:Spring-boot-starter-jdbc 或 Spring-boot-starter-data-jpa。

<parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>3.0.4</version>
  <relativePath/> <!-- lookup parent from repository -->
 </parent>

<dependencies>
...
<dependency>
<! -- Assume Spring Data JDBC -->
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
...
</dependencies>

然后使用以下方法之一配置应用程序属性:

  • (i) 使用 application.properties 文件(Spring Boot 会自动读取这些文件并应用配置),
  • (ii) 创建一个用 @Configuration 注释的配置类,并使用 @Bean 定义带有 HikariCP 设置的 DataSource Bean - 我们将在下文中使用这种方法,
  • (iii) 使用 application.yaml 文件,以及
  • (iv) 使用 Kubernetes secret(一种更安全的方法)。

有关这些方法的更多详情,请参阅这篇与 UCP 相关的博文。选择最适合你的项目和偏好的方法。

第一种方法可能更简单,通常足以满足基本设置的需要,而第二种方法则提供了更大的灵活性,可以在 Java 类中控制配置。

HikariCP 具有默认设置,无需额外调整即可在大多数部署中良好运行。

Spring Boot 使用spring.datasource.hikarinamespace 公开了特定于 Hikari 的属性。除了几个必须设置的基本属性外,其他属性都是可选的。HikariCP 的官方 GitHub 页面解释了各种配置选项。此外,你还可以查看列出所有 Spring Boot application.properties 选项的通用应用程序属性页面。

下面是一个包含使用 Oracle 数据库所需的最小属性集的 application.properties 文件示例。

# Oracle DataSource Configuration
spring.datasource.url=${JDBC_URL}
spring.datasource.username=${DB_USER}
spring.datasource.password=${DB_PASSWORD}
spring.datasource.driver-class-name=oracle.jdbc.OracleDriver

# HikariCP settings
spring.datasource.hikari.maximum-pool-size=4
spring.datasource.hikari.data-source-properties.oracle.jdbc.defaultConnectionValidation=LOCAL

将 oracle.jdbc.defaultConnectionValidation 属性设置为 LOCAL,可使 JDBC 驱动程序在调用 isValid(timeout) 方法时进行轻量级连接验证。我们强烈建议设置这样一个属性。

不过,在设置多个数据源属性时,请注意语法和副作用。
以下设置将删除第一个数据源属性设置,只保留最后一个。

spring.datasource.hikari.data-source-properties=oracle.jdbc.implicitStatementCacheSize=10
spring.datasource.hikari.data-source-properties=oracle.jdbc.defaultConnectionValidation=LOCAL

要成功设置两个数据源属性,需要使用以下语法。

spring.datasource.hikari.data-source-properties.oracle.jdbc.implicitStatementCacheSize=10
spring.datasource.hikari.data-source-properties.oracle.jdbc.defaultConnectionValidation=LOCAL


下面是一个 DataSourceConfig 类的示例,该类定义了数据源与 Oracle 数据库协同工作所需的最小属性集。

package maa.spring;
import org.springframework.context.annotation.Bean;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import com.zaxxer.hikari.HikariDataSource;
import com.zaxxer.hikari.HikariConfig;

import java.util.Properties;
import javax.sql.DataSource;

@Configuration
public class DataSourceConfig {
    @Bean
    public HikariDataSource getDataSource () {
        HikariConfig config = new HikariConfig();
        config.setJdbcUrl(System.getenv("JDBC_URL"));
        config.addDataSourceProperty(
"url",System.getenv("JDBC_URL"));
        config.setUsername(System.getenv(
"DB_USER"));
        config.setPassword(System.getenv(
"DB_PASSWORD"));
        config.setMaximumPoolSize(4);
        config.addDataSourceProperty(
"oracle.jdbc.defaultConnectionValidation", "LOCAL");
        return new HikariDataSource(config);
    }
}

至此,您已成功配置了 Spring Boot 应用程序,以便使用 HikariCP 与 Oracle 数据库协同工作。

接下来的章节将介绍配置 HikariCP 以利用 Oracle 数据库高可用性功能的步骤。

配置基本高可用性 - 第 1 级
利用 Oracle 真正应用集群系统 (RAC),可以从多个服务器节点平等地访问任何 Oracle 数据库服务。如果 RAC 集群的一个节点或一个子集发生故障或离线维护,仍可通过其余活动节点访问数据库。

使用 Oracle 数据库实施应用程序高可用性的构建模块包括:使用数据库服务、为高可用性配置 URL 或连接字符串、启用快速应用程序通知 (FAN)、实施耗尽以及为 Java 应用程序启用持续数据库可用性。
您可以选择适合自己要求的高可用性级别。有关 HA 保护级别的详细说明,请参阅 Oracle "Oracle 的“高可用性概述和最佳实践” "中的 "应用程序高可用性级别 "一章。

通过以下步骤,您可以配置 Oracle RAC 系统、Oracle JDBC 驱动程序和 HikariCP,以维持计划内和计划外的中断。Oracle 的 "Oracle 的“高可用性概述和最佳实践”"文档中涵盖了应用程序持续可用性的详细步骤和说明。

配置高可用性数据库服务
使用 Oracle RAC,Oracle 数据库服务可以部署在集群中的多个节点上。计划中断允许对服务节点/主机的子集执行维护操作。数据库管理员发出生成计划停机事件的命令。由于 HikariCP 无法处理此类事件类型,因此它将由 Oracle JDBC 处理。驱动程序将耗尽,即透明且平滑地关闭所有活动连接,从而允许关闭计划进行维护的节点,而不影响应用程序(剩余的活动节点将吸收工作负载)。

创建专用服务来支持 HA 功能,如下所示。

$ srvctl add service -db mydb -service MYSERVICE -pdb mypdb
-notification TRUE

 -notification TRUE会启用该服务的 FAN。您的应用程序将连接到这样的服务。

为高可用性配置 JDBC 连接字符串
配置 Java 应用程序以使用以下连接字符串。

jdbc:oracle:thin:@(DESCRIPTION = 
  (CONNECT_TIMEOUT= 3)(RETRY_COUNT=4)(RETRY_DELAY=2)
  (TRANSPORT_CONNECT_TIMEOUT=3) (ADDRESS_LIST = (LOAD_BALANCE=on) 
  (ADDRESS = (PROTOCOL = TCP)(HOST=clu_site_scan)(PORT=1521))) 
  (CONNECT_DATA=(SERVICE_NAME = my_service)))

在驱动程序级别启用快速应用程序通知(FAN)
由于 HikariCP 还没有处理 FAN 事件的工具,计划中的维护将由 Oracle JDBC 驱动程序管理。要配置驱动程序以处理 FAN 事件,请在项目的 pom.xml 中添加 simplefan 和 ons jar 文件。


<dependencies>

...
    <dependency>
      <groupId>com.oracle.database.ha</groupId>
      <artifactId>ons</artifactId>
      <version>${oracle.jdbc.version}</version>
    </dependency>
    <dependency>
      <groupId>com.oracle.database.ha</groupId>
      <artifactId>simplefan</artifactId>
      <version>${oracle.jdbc.version}</version>
    </dependency>

...
  </dependencies>

在此阶段,基本的高可用性已配置完成,并具有以下优势:

  • 计划外连接故障的 FAN 事件通知。当故障可能导致应用程序挂起时,如数据库节点发生故障而 TCP/IP 套接字未清理时,这一点尤其有用。
  • 连接字符串会导致连接请求在多个 RAC 节点之间使用单客户端访问名称 (SCAN) 进行负载平衡,并在失败时重试。

如果您的应用程序已设计为可通过重试逻辑处理计划内和计划外故障,那么您就不需要其他任何东西了。

为计划维护进行配置 - 第 2 级
要使应用程序为计划维护做好准备,请在应用程序代码中添加以下连接验证设置。

启用可预测的连接验证
根据设计,HikariCP 会验证空闲时间超过 500 毫秒的连接(最新版本)。如果一个连接不断被检出和检入,它将不会被验证,因此也不会被 Oracle JDBC 驱动程序耗尽。

将以下属性设置为 JVM 系统属性或在 DataSourceConfiguration 类中以编程方式设置(如上图所示),以便在签出时强制连接验证。

-Dcom.zaxxer.hikari.aliveBypassWindowMs=-1
System.property("com.zaxxer.hikari.aliveBypassWindowsMs","-1");

请注意,系统属性具有 JVM 作用域,因此该 JVM 实例中的所有 HikariCP 池都将受到该设置的影响。

借用系统连接验证可能会对性能产生轻微影响;Oracle 建议将轻量级连接验证属性 oracle.jdbc.defaultConnectionValidation 设置为 LOCAL,以减轻影响。您已经在上面设置了这样一个属性。

props.setProperty("oracle.jdbc.defaultConnectionValidation", "LOCAL");

使用 application.properties 或 applications.yaml 文件设置数据源类名时,会出现以下错误信息:"原因:java.lang.IllegalStateException:driverClassName 和 dataSourceClassName"。

我们需要以编程方式进行设置,如下所示。

package maa.spring;
import org.springframework.context.annotation.Bean;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import com.zaxxer.hikari.HikariDataSource;
import com.zaxxer.hikari.HikariConfig;

import java.util.Properties;
import javax.sql.DataSource;

@Configuration
public class DataSourceConfig {
    @Bean
    public HikariDataSource getDataSource () {
        System.setProperty("com.zaxxer.hikari.aliveBypassWindowMs", "-1");
        HikariConfig config = new HikariConfig();
        config.setJdbcUrl(System.getenv(
"JDBC_URL"));
        config.addDataSourceProperty(
"url",System.getenv("JDBC_URL"));
        config.setUsername(System.getenv(
"DB_USER"));
        config.setPassword(System.getenv(
"DB_PASSWORD"));
        config.setMaximumPoolSize(4);
        Properties props = new Properties();
        props.setProperty(
"oracle.jdbc.defaultConnectionValidation", "LOCAL");
        config.addDataSourceProperty(
"connectionProperties", props);
        return new HikariDataSource(config);
    }
}

  • 您可能会注意到,在 Java 类中,我们同时使用了 config.setJdbcUrl(System.getenv("JDBC_URL")) 和
  • config.addDataSourceProperty("url",System.getenv("JDBC_URL"))。
  • 通过 setJdbcUrl,Hikari 会根据指定的 URL 创建驱动程序。
  • 如果指定了数据源类名,Hikari 会创建该类的实例,并调用与数据源匹配的设置器方法。

执行计划维护
以下命令可启动活动会话的耗尽。
参数 -drain_timeout 60 允许活动会话在预定义超时(60 秒)内完成请求。

srvctl stop instance -db mydb -node node1 -stopoption immediate 
–drain_timeout 60 -force -failover

计划中断成功测试
在负载情况下(即数据库活动),观察连接是否从该实例中耗尽,以及是否可以关闭该实例/节点。
在本例中,我们通过 v$session 监控连接耗尽情况。

... 
SQL> 
TIME                               MACHINE   INST_ID   COUNT(*)
----------------------------------- --------- ---------- ----------
2023-12-21 17:06:04 
22-DEC-23 01.06.04.504891 AM +00:00 app1        2         3
2023-12-21 17:06:04 
22-DEC-23 01.06.04.504891 AM +00:00 app2        1         2
2023-12-21 17:06:04 
22-DEC-23 01.06.04.504891 AM +00:00 app2        2         2
2023-12-21 17:06:04 
22-DEC-23 01.06.04.504891 AM +00:00 app1        1         1
...

SQL> 
TIME                                MACHINE  INST_ID   COUNT(*)
----------------------------------- --------- ---------- ----------
2023-12-21 17:06:05 
22-DEC-23 01.06.05.685695 AM +00:00 app1        1         1
2023-12-21 17:06:05 
22-DEC-23 01.06.05.685695 AM +00:00 app1        2         3
2023-12-21 17:06:05 
22-DEC-23 01.06.05.685695 AM +00:00 app2        2         2
2023-12-21 17:06:05 
22-DEC-23 01.06.05.685695 AM +00:00 app2        1         2
....
SQL> 
TIME                                 MACHINE  INST_ID   COUNT(*)
----------------------------------- --------- ---------- ----------
2023-12-21 17:06:07 
22-DEC-23 01.06.07.486856 AM +00:00 app1        2         4
2023-12-21 17:06:07 
22-DEC-23 01.06.07.486856 AM +00:00 app2        2         4

为计划外停机进行配置 - 第 3 级
对于计划内维护,在关闭数据库实例之前,会使用耗尽功能让活动会话完成工作(耗尽)。但是,在实例、节点或数据库服务故障等计划外事件中,连接到故障节点/实例的所有会话都将立即终止 - 没有时间进行耗尽。快速应用程序通知 (FAN) 机制将检测到这些计划外事件,并通知 JDBC 驱动程序或任何连接池(如 UCP),这些连接池已被设置为接收和处理 FAN 消息

由于 HikarCPi 无法处理 FAN 事件,Oracle JDBC 驱动程序会以透明方式清理无主连接,并在尚存的数据库实例(使用新连接)上重发飞行中请求;因此,故障对使用这些连接的应用程序是透明的。

在数据库服务定义中添加以下参数:

  • -failovertype AUTO - 启用透明应用程序连续性 (TAC)。
  • -failover_restore AUTO - 自动恢复客户端状态
  • -replay_init_time 600 - 指定不执行重放的时间(以秒为单位)。

$ srvctl add service -db mydb -service MYSERVICE -pdb mypdb
-notification TRUE -failover_restore AUTO -failovertype AUTO -replay_init_time 600

2.要在计划外停机期间持续提供服务,请在 DataSourceConfig 类中包含重放数据源 oracle.jdbc.replay.OracleDataSourceImplain。

package maa.spring;

import org.springframework.context.annotation.Bean;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import com.zaxxer.hikari.HikariDataSource;
import com.zaxxer.hikari.HikariConfig;

import java.util.Properties;
import javax.sql.DataSource;

public class DataSourceConfig {
    @Bean
    public HikariDataSource getDataSource () {
        System.setProperty("com.zaxxer.hikari.aliveBypassWindowMs", "-1");
        HikariConfig config = new HikariConfig();
        config.setJdbcUrl(System.getenv(
"JDBC_URL"));
        config.addDataSourceProperty(
"url",System.getenv("JDBC_URL"));
        config.setUsername(System.getenv(
"DB_USER"));
        config.setPassword(System.getenv(
"DB_PASSWORD"));
        config.setDataSourceClassName(
"oracle.jdbc.replay.OracleDataSourceImpl");
        config.setMaximumPoolSize(4);
        Properties props = new Properties();
        props.setProperty(
"oracle.jdbc.defaultConnectionValidation", "LOCAL");
        config.addDataSourceProperty(
"connectionProperties", props);
        return new HikariDataSource(config);
    }
}

意外停机成功测试
在负载情况下(即数据库活动),使用 ALTER SYSTEM KILL SESSION <sid>模拟故障。

要确定 TAC 是否在您的环境中成功启用,可以使用 ACCHK。该数据库功能可显示应用程序在故障转移时的保护级别。如果受保护呼叫次数或受保护时间减少,请查看统计数据以确定受保护呼叫的范围。您可以在本文档  Application Continuity Protection Check.(应用程序连续性保护检查)中查看 ACCHK 实用程序使用详情。

总结
本博文所述步骤基于 Oracle RAC 系统和 Oracle JDBC 驱动程序 v19.21.0.0。后续博客将介绍使用更新的 Oracle 数据库和 Oracle JDBC 驱动程序版本进行计划内和计划外中断所需的步骤。