Spring 重试连接,直到数据源可用

Leo*_*hra 21 mysql spring spring-boot hikaricp docker-compose

我有一个 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
Run Code Online (Sandbox Code Playgroud)

我可以编辑我的 docker-compose 文件以确保在应用程序启动之前数据库始终处于启动状态,但我希望应用程序能够自己处理这种情况,而不是在无法到达数据库地址时立即退出。

有多种方法可以在 application.properties 文件中配置数据源,以使应用程序重新连接到数据库,如此此处所述。但这不适用于到数据源的启动连接。

如何让我的 SpringBoot 应用程序在启动时以给定的时间间隔重试连接到数据库,直到它成功连接到数据库?

bre*_*ttw 20

将 HikariCP 的initializationFailTimeout属性设置为 0(零)或负数。如此处所述

?initializationFailTimeout

此属性控制池是否会“快速失败”,如果池无法成功地设置初始连接的种子。任何正数都被视为尝试获取初始连接的毫秒数;在此期间,应用程序线程将被阻塞。如果在此超时发生之前无法获取连接,则会抛出异常。此超时被应用connectionTimeout时期。如果该值为零 (0),HikariCP 将尝试获取并验证连接。如果获得了连接,但验证失败,则会抛出异常并且池不会启动。但是,如果无法获得连接,则池将启动,但稍后获得连接的努力可能会失败。小于零的值将绕过任何初始连接尝试,并且池将在尝试在后台获取连接时立即启动。因此,以后获得连接的努力可能会失败。默认值:1


Abd*_*ssi 7

有一种替代方法可以做到这一点,它不依赖于特定的连接池库或特定的数据库。请注意,您将需要使用spring-retry此方法来实现所需的行为

首先,您需要将 spring-retry 添加到您的依赖项中:

<dependency>
    <groupId>org.springframework.retry</groupId>
    <artifactId>spring-retry</artifactId>
    <version>${spring-retry.version}</version>
</dependency>
Run Code Online (Sandbox Code Playgroud)

然后你可以创建一个装饰器,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);
    }
}
Run Code Online (Sandbox Code Playgroud)

然后,您需要通过创建自定义的方法将此自定义 DataSource 装饰器注入到 Spring 上下文中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;
    }
}
Run Code Online (Sandbox Code Playgroud)

最后但并非最不重要的一点是,您需要通过@EnableRetry向 spring 主类添加注释来启用 Spring 重试,例如:

@EnableRetry
@SpringBootApplication
public class RetryableDbConnectionApplication {

    public static void main(String[] args) {
        SpringApplication.run(RetryableDbConnectionApplication.class, args);
    }

}
Run Code Online (Sandbox Code Playgroud)