我有一个 Spring Boot 应用程序,它使用 Spring Data JPA 连接到使用 Spring Cloud Vault 数据库的 Postgres 数据库。并且,应用程序会定期失去与数据库的连接。唯一的解决方案是重新启动应用程序。
这是配置:
spring:
application:
name: application-name-api
cloud.vault:
kv-version: 2
host: vaulthost.net
port: 8200
scheme: https
authentication: TOKEN
token: XXX_VAULT_TOKEN_XXX
database:
role: database_write_role
kv:
enabled: false
database:
enabled: true
backend: database
username-property: spring.datasource.username
password-property: spring.datasource.password
config:
import: "vault://"
datasource:
url: "jdbc:postgresql://db-postgresql:11111/defaultdb?sslmode=require"
testOnBorrow: true
validationQuery: SELECT 1
hikari:
maximum-pool-size: 2
connection-timeout: 3000
idle-timeout: 10000
minimum-idle: 1
jpa:
hibernate:
ddl-auto: none
database-platform: org.hibernate.dialect.PostgreSQLDialect
profiles:
active: dev
servlet:
multipart:
max-file-size: 5MB
max-request-size: 5MB
当应用程序部署到 Linux Box 时,它运行良好,并且能够连接到数据库。但是,一天后连接出现错误,并出现以下异常:
2023-08-26 17:58:42,875 WARN org.hibernate.engine.jdbc.spi.SqlExceptionHelper [http-nio-8600-exec-8] SQL Error: 0, SQLState: 28P01
2023-08-26 17:58:42,876 ERROR org.hibernate.engine.jdbc.spi.SqlExceptionHelper [http-nio-8600-exec-8] HikariPool-1 - Connection is not available, request timed out after 3000ms.
2023-08-26 17:58:42,877 ERROR org.hibernate.engine.jdbc.spi.SqlExceptionHelper [http-nio-8600-exec-8] FATAL: password authentication failed for user "v-token-xxxxxxx-fffffffxxxxxxxxzzzzzz-1692899441"
2023-08-26 17:58:42,878 ERROR org.apache.catalina.core.ContainerBase.[Tomcat].[localhost].[/api].[dispatcherServlet] [http-nio-8600-exec-8] Servlet.service() for servlet [dispatcherServlet] in context with path [/api] threw exception [Request processing failed; nested exception is org.springframework.transaction.CannotCreateTransactionException: Could not open JPA EntityManager for transaction; nested exception is org.hibernate.exception.JDBCConnectionException: Unable to acquire JDBC Connection] with root cause
org.postgresql.util.PSQLException: FATAL: password authentication failed for user "v-token-xxxxxxx-fffffffxxxxxxxxzzzzzz-1692899441"
at org.postgresql.core.v3.ConnectionFactoryImpl.doAuthentication(ConnectionFactoryImpl.java:646)
at org.postgresql.core.v3.ConnectionFactoryImpl.tryConnect(ConnectionFactoryImpl.java:180)
at org.postgresql.core.v3.ConnectionFactoryImpl.openConnectionImpl(ConnectionFactoryImpl.java:235)
at org.postgresql.core.ConnectionFactory.openConnection(ConnectionFactory.java:49)
at org.postgresql.jdbc.PgConnection.<init>(PgConnection.java:223)
at org.postgresql.Driver.makeConnection(Driver.java:402)
at org.postgresql.Driver.connect(Driver.java:261)
at com.zaxxer.hikari.util.DriverDataSource.getConnection(DriverDataSource.java:138)
at com.zaxxer.hikari.pool.PoolBase.newConnection(PoolBase.java:364)
at com.zaxxer.hikari.pool.PoolBase.newPoolEntry(PoolBase.java:206)
at com.zaxxer.hikari.pool.HikariPool.createPoolEntry(HikariPool.java:476)
at com.zaxxer.hikari.pool.HikariPool.access$100(HikariPool.java:71)
at com.zaxxer.hikari.pool.HikariPool$PoolEntryCreator.call(HikariPool.java:726)
at com.zaxxer.hikari.pool.HikariPool$PoolEntryCreator.call(HikariPool.java:712)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
at java.base/java.lang.Thread.run(Thread.java:833)
以下是我的 pom.xml 中的条目:
<?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.7.5</version>
<relativePath/>
</parent>
<groupId>com.toratoratora</groupId>
<artifactId>application-api</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>application-api</name>
<description>Application API</description>
<properties>
<java.version>17</java.version>
<spring-cloud.version>2021.0.5</spring-cloud.version>
<validator.version>1.7</validator.version>
<hibernate-types-52.version>2.14.0</hibernate-types-52.version>
<commons-lang3.version>3.12.0</commons-lang3.version>
<commons-collections4.version>4.4</commons-collections4.version>
<commons-io.version>2.11.0</commons-io.version>
<spring-boot-admin.version>2.7.4</spring-boot-admin.version>
<okhttp.version>4.10.0</okhttp.version>
<jackson-datatype-jsr310.version>2.14.2</jackson-datatype-jsr310.version>
<cactoos.version>0.55.0</cactoos.version>
<commons-text.version>1.10.0</commons-text.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-vault-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-vault-config-databases</artifactId>
</dependency>
<dependency>
<groupId>com.vladmihalcea</groupId>
<artifactId>hibernate-types-52</artifactId>
<version>${hibernate-types-52.version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>${commons-lang3.version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>${commons-collections4.version}</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>${commons-io.version}</version>
</dependency>
<dependency>
<groupId>commons-validator</groupId>
<artifactId>commons-validator</artifactId>
<version>${validator.version}</version>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>${okhttp.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>${jackson-datatype-jsr310.version}</version>
</dependency>
<dependency>
<groupId>org.cactoos</groupId>
<artifactId>cactoos</artifactId>
<version>${cactoos.version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-text</artifactId>
<version>${commons-text.version}</version>
</dependency>
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20230227</version>
</dependency>
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>google-cloud-firestore</artifactId>
</dependency>
<dependency>
<groupId>com.google.firebase</groupId>
<artifactId>firebase-admin</artifactId>
<version>9.0.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-dependencies</artifactId>
<version>${spring-boot-admin.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>libraries-bom</artifactId>
<version>26.0.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<finalName>${project.name}</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.6.7</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.8.2</version>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
<plugin>
<groupId>io.spring.javaformat</groupId>
<artifactId>spring-javaformat-maven-plugin</artifactId>
<version>0.0.31</version>
<executions>
<execution>
<phase>validate</phase>
<inherited>true</inherited>
<goals>
<goal>validate</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.hibernate.orm.tooling</groupId>
<artifactId>hibernate-enhance-maven-plugin</artifactId>
<version>5.6.1.Final</version>
<executions>
<execution>
<configuration>
<failOnError>true</failOnError>
<enableLazyInitialization>true</enableLazyInitialization>
</configuration>
<goals>
<goal>enhance</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
从错误消息中我了解到密码不再有效。所以Spring CloudVault应该先刷新密码。
spring.cloud.vault:
config.lifecycle:
enabled: true
min-renewal: 10s
expiry-threshold: 1m
lease-endpoints: Legacy
然后代码需要在密码过期时刷新数据源。下面是执行此操作的示例代码
@Configuration
public class VaultConfiguration {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
private SecretLeaseContainer leaseContainer;
@Autowired
private HikariDataSource hikariDataSource;
@Value("${spring.cloud.vault.database.role}")
private String databaseRole;
@PostConstruct
private void postConstruct() {
String path = "database/creds/" + databaseRole;
leaseContainer.addLeaseListener ( event -> {
if (!path.equals(event.getSource().getPath())) {
return;
}
logger.info("Lease event {}, lease Id {}:", event, event.getLease().getLeaseId());
if (event instanceof SecretLeaseExpiredEvent && RENEW == event.getSource().getMode()) {
logger.info("Replace RENEW for expired credential with ROTATE");
leaseContainer.requestRotatingSecret(path);
}
if (event instanceof SecretLeaseCreatedEvent && ROTATE == event.getSource().getMode()) {
Map<String, Object> secrets = ((SecretLeaseCreatedEvent) event).getSecrets();
String username = (String) secrets.get("username");
String password = (String) secrets.get("password");
logger.info("XXXXX New username = {}", username);
hikariDataSource.getHikariConfigMXBean().setUsername(username);
hikariDataSource.getHikariConfigMXBean().setPassword(password);
logger.info("Soft evicting db connections...");
hikariDataSource.getHikariPoolMXBean().softEvictConnections();
}
});
}
}
我还建议使用“min-renewal: 10s”或60s
参考资料: