Spring 重试连接直到数据源可用
Spring retry connection until datasource is available
我有一个 docker-compose 设置来启动我的 SpringBoot 应用程序和一个 MySQL 数据库。如果数据库先启动,那么我的应用程序就可以成功连接。但是如果我的应用程序先启动,还没有数据库存在,所以应用程序抛出以下异常并退出:
app_1 | 2018-05-27 14:15:03.415 INFO 1 --- [ main]
com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting...
app_1 | 2018-05-27 14:15:06.770 ERROR 1 --- [ main]
com.zaxxer.hikari.pool.HikariPool : HikariPool-1 - Exception during pool initialization
app_1 | com.mysql.jdbc.exceptions.jdbc4.CommunicationsException:
Communications link failure
我可以编辑我的 docker-compose 文件以确保数据库在应用程序启动之前始终处于启动状态,但我希望应用程序能够自行处理这种情况,而不是立即处理无法到达数据库地址时退出。
有一些方法可以在 application.properties 文件中配置数据源以使应用程序重新连接到数据库,如回答 here and here 所述。但这不适用于与数据源的启动连接。
如何让我的 SpringBoot 应用程序在启动时以给定的时间间隔重试与数据库的连接,直到它成功连接到数据库?
将 HikariCP 的 initializationFailTimeout
属性 设置为 0(零)或负数。记录 here:
⌚initializationFailTimeout
This property controls whether the pool will "fail fast" if the pool cannot be seeded with an initial connection successfully. Any positive number is taken to be the number of milliseconds to attempt to acquire an initial connection; the application thread will be blocked during this period. If a connection cannot be acquired before this timeout occurs, an exception will be thrown. This timeout is applied after the connectionTimeout
period. If the value is zero (0), HikariCP will attempt to obtain and validate a connection. If a connection is obtained, but fails validation, an exception will be thrown and the pool not started. However, if a connection cannot be obtained, the pool will start, but later efforts to obtain a connection may fail. A value less than zero will bypass any initial connection attempt, and the pool will start immediately while trying to obtain connections in the background. Consequently, later efforts to obtain a connection may fail. Default: 1
有另一种方法可以做到这一点,它不依赖于特定的连接池库或特定的数据库。请注意,您将需要使用 spring-retry
通过这种方法实现所需的行为
首先,您需要将 spring-retry 添加到您的依赖项中:
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
<version>${spring-retry.version}</version>
</dependency>
然后您可以在 DataSource
上创建一个装饰器,它将扩展 AbstractDataSource
,如下所示:
@Slf4j
@RequiredArgsConstructor
public class RetryableDataSource extends AbstractDataSource {
private final DataSource dataSource;
@Override
@Retryable(maxAttempts = 5, backoff = @Backoff(multiplier = 1.3, maxDelay = 10000))
public Connection getConnection() throws SQLException {
log.info("getting connection ...");
return dataSource.getConnection();
}
@Override
@Retryable(maxAttempts = 5, backoff = @Backoff(multiplier = 2.3, maxDelay = 10000))
public Connection getConnection(String username, String password) throws SQLException {
log.info("getting connection by username and password ...");
return dataSource.getConnection(username, password);
}
}
然后您需要通过创建自定义 BeanPostProcessor
:
将此自定义 DataSource 装饰器注入 Spring 上下文
@Slf4j
@Order(value = Ordered.HIGHEST_PRECEDENCE)
@Component
public class RetryableDatabasePostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if(bean instanceof DataSource) {
log.info("-----> configuring a retryable datasource for beanName = {}", beanName);
return new RetryableDataSource((DataSource) bean);
}
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}
最后但同样重要的是,您需要通过向 spring main class 添加 @EnableRetry
注释来启用 Spring 重试,示例:
@EnableRetry
@SpringBootApplication
public class RetryableDbConnectionApplication {
public static void main(String[] args) {
SpringApplication.run(RetryableDbConnectionApplication.class, args);
}
}
我有一个 docker-compose 设置来启动我的 SpringBoot 应用程序和一个 MySQL 数据库。如果数据库先启动,那么我的应用程序就可以成功连接。但是如果我的应用程序先启动,还没有数据库存在,所以应用程序抛出以下异常并退出:
app_1 | 2018-05-27 14:15:03.415 INFO 1 --- [ main]
com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting...
app_1 | 2018-05-27 14:15:06.770 ERROR 1 --- [ main]
com.zaxxer.hikari.pool.HikariPool : HikariPool-1 - Exception during pool initialization
app_1 | com.mysql.jdbc.exceptions.jdbc4.CommunicationsException:
Communications link failure
我可以编辑我的 docker-compose 文件以确保数据库在应用程序启动之前始终处于启动状态,但我希望应用程序能够自行处理这种情况,而不是立即处理无法到达数据库地址时退出。
有一些方法可以在 application.properties 文件中配置数据源以使应用程序重新连接到数据库,如回答 here and here 所述。但这不适用于与数据源的启动连接。
如何让我的 SpringBoot 应用程序在启动时以给定的时间间隔重试与数据库的连接,直到它成功连接到数据库?
将 HikariCP 的 initializationFailTimeout
属性 设置为 0(零)或负数。记录 here:
⌚
initializationFailTimeout
This property controls whether the pool will "fail fast" if the pool cannot be seeded with an initial connection successfully. Any positive number is taken to be the number of milliseconds to attempt to acquire an initial connection; the application thread will be blocked during this period. If a connection cannot be acquired before this timeout occurs, an exception will be thrown. This timeout is applied after the
connectionTimeout
period. If the value is zero (0), HikariCP will attempt to obtain and validate a connection. If a connection is obtained, but fails validation, an exception will be thrown and the pool not started. However, if a connection cannot be obtained, the pool will start, but later efforts to obtain a connection may fail. A value less than zero will bypass any initial connection attempt, and the pool will start immediately while trying to obtain connections in the background. Consequently, later efforts to obtain a connection may fail. Default: 1
有另一种方法可以做到这一点,它不依赖于特定的连接池库或特定的数据库。请注意,您将需要使用 spring-retry
通过这种方法实现所需的行为
首先,您需要将 spring-retry 添加到您的依赖项中:
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
<version>${spring-retry.version}</version>
</dependency>
然后您可以在 DataSource
上创建一个装饰器,它将扩展 AbstractDataSource
,如下所示:
@Slf4j
@RequiredArgsConstructor
public class RetryableDataSource extends AbstractDataSource {
private final DataSource dataSource;
@Override
@Retryable(maxAttempts = 5, backoff = @Backoff(multiplier = 1.3, maxDelay = 10000))
public Connection getConnection() throws SQLException {
log.info("getting connection ...");
return dataSource.getConnection();
}
@Override
@Retryable(maxAttempts = 5, backoff = @Backoff(multiplier = 2.3, maxDelay = 10000))
public Connection getConnection(String username, String password) throws SQLException {
log.info("getting connection by username and password ...");
return dataSource.getConnection(username, password);
}
}
然后您需要通过创建自定义 BeanPostProcessor
:
@Slf4j
@Order(value = Ordered.HIGHEST_PRECEDENCE)
@Component
public class RetryableDatabasePostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if(bean instanceof DataSource) {
log.info("-----> configuring a retryable datasource for beanName = {}", beanName);
return new RetryableDataSource((DataSource) bean);
}
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}
最后但同样重要的是,您需要通过向 spring main class 添加 @EnableRetry
注释来启用 Spring 重试,示例:
@EnableRetry
@SpringBootApplication
public class RetryableDbConnectionApplication {
public static void main(String[] args) {
SpringApplication.run(RetryableDbConnectionApplication.class, args);
}
}