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

}