Spring Boot 和 Spring Data with Cassandra:在数据库连接失败时继续

Posted

技术标签:

【中文标题】Spring Boot 和 Spring Data with Cassandra:在数据库连接失败时继续【英文标题】:Spring Boot and Spring Data with Cassandra: Continue on failed database connection 【发布时间】:2020-11-19 23:04:02 【问题描述】:

我在 Cassandra 中使用 Spring Boot 和 Spring Data。在应用程序启动时,spring 建立与数据库的连接以设置模式并初始化 spring 数据存储库。如果数据库不可用,应用程序将无法启动。

我希望应用程序只记录错误并启动。当然,我不能再使用存储库了,但是独立于数据库的其他服务(休息控制器等)应该可以工作。如果在执行器运行状况检查中看到 cassandra 已关闭,那也很好。

对于 JDBC,有一个 spring.datasource.continue-on-error 属性。我找不到适合 Cassandra 的类似内容。

我还尝试创建自定义 cassandra 配置并尝试在创建 CqlSession 时捕获异常,但我无法实现所需的行为。

编辑:根据@adutra 的建议,我尝试设置advanced.reconnect-on-init,应用程序尝试建立连接,但应用程序未完全初始化(例如,无法访问 REST 控制器)

@Configuration
public class CustomCassandraConfiguration extends CassandraAutoConfiguration 
    @Bean
    public DriverConfigLoaderBuilderCustomizer driverConfigLoaderBuilderCustomizer() 
        return builder -> builder.withBoolean(DefaultDriverOption.RECONNECT_ON_INIT, true);
    

EDIT2:我现在有工作示例(应用程序启动,cassandra 的自定义健康检查),但如果感觉很丑:

CustomCassandraAutoConfiguration

@Configuration
public class CustomCassandraAutoConfiguration extends CassandraAutoConfiguration 
private final Logger logger = LoggerFactory.getLogger(getClass());

@Override
@Bean
public CqlSession cassandraSession(CqlSessionBuilder cqlSessionBuilder) 
    try 
        return super.cassandraSession(cqlSessionBuilder);
     catch (AllNodesFailedException e) 
        logger.error("Failed to establish the database connection", e);
    
    return new DatabaseNotConnectedFakeCqlSession();


@Bean
public CassandraReactiveHealthIndicator cassandraHealthIndicator(ReactiveCassandraOperations r, CqlSession session) 
    if (session instanceof DatabaseNotConnectedFakeCqlSession) 
        return new CassandraReactiveHealthIndicator(r) 
            @Override
            protected Mono<Health> doHealthCheck(Health.Builder builder) 
                return Mono.just(builder.down().withDetail("connection", "was not available on startup").build());
            
        ;
    
    return new CassandraReactiveHealthIndicator(r);

CustomCassandraDataAutoConfiguration

@Configuration
public class CustomCassandraDataAutoConfiguration extends CassandraDataAutoConfiguration 

public CustomCassandraDataAutoConfiguration(CqlSession session) 
    super(session);


@Bean
public SessionFactoryFactoryBean cassandraSessionFactory(CqlSession session, Environment environment, CassandraConverter converter) 
    SessionFactoryFactoryBean sessionFactoryFactoryBean = super.cassandraSessionFactory(environment, converter);

    // Disable schema action if database is not available
    if (session instanceof DatabaseNotConnectedFakeCqlSession) 
        sessionFactoryFactoryBean.setSchemaAction(SchemaAction.NONE);
    
    return sessionFactoryFactoryBean;

DatabaseNotConnectedFakeCqlSession(假会话实现)

public class DatabaseNotConnectedFakeCqlSession implements CqlSession 
   
@Override
public String getName() 
    return null;


   
@Override
public Metadata getMetadata() 
    return null;


@Override
public boolean isSchemaMetadataEnabled() 
    return false;


   
@Override
public CompletionStage<Metadata> setSchemaMetadataEnabled( Boolean newValue) 
    return null;


   
@Override
public CompletionStage<Metadata> refreshSchemaAsync() 
    return null;


   
@Override
public CompletionStage<Boolean> checkSchemaAgreementAsync() 
    return null;


   
@Override
public DriverContext getContext() 
    return new DefaultDriverContext(new DefaultDriverConfigLoader(), ProgrammaticArguments.builder().build());


   
@Override
public Optional<CqlIdentifier> getKeyspace() 
    return Optional.empty();


   
@Override
public Optional<Metrics> getMetrics() 
    return Optional.empty();



@Override
public <RequestT extends Request, ResultT> ResultT execute( RequestT request, GenericType<ResultT> resultType) 
    return null;



@Override
public CompletionStage<Void> closeFuture() 
    return null;


   
@Override
public CompletionStage<Void> closeAsync() 
    return null;


   
@Override
public CompletionStage<Void> forceCloseAsync() 
    return null;


@Override
public Metadata refreshSchema() 
    return null;

有什么建议吗?

【问题讨论】:

“我还尝试创建自定义 cassandra 配置并尝试在创建 CqlSession 时捕获异常,但我无法实现所需的行为。”为什么?如果您声明另一个 CqlSession 类型的 bean,则不会使用在 CassandraAutoConfiguration 中声明的默认 bean;那么您可以随意创建会话,所以我很惊讶您无法捕获 SessionBuilder.build() 引发的异常。 @adutra:我创建了自己的 CqlSession 实现,但是有很多依赖项。许多 getter 必须返回有效值(否则为 NPE)。我认为,这将有可能实现所有必要的方法,但感觉就像一个 hack,我试图找到更优雅的解决方案。 你不需要实现CqlSession。我建议捕捉异常: public CqlSession cassandraSession(CqlSessionBuilder cqlSessionBuilder) try return cqlSessionBuilder.build(); 捕捉(异常 e) 返回空值; 如果将返回 null 而不是 CqlSession,这将破坏应用程序:无法实例化每个“自动装配”获取存储库的所有组件。 Spring自动配置也被破坏“通过方法'reactiveCassandraSession'表达的不满足的依赖关系”。 确实如此。如果你的 bean 可以为空,你不能直接注入它。您需要将其包装到可选的或更好的 ObjectProvider 中:docs.spring.io/spring-framework/docs/current/javadoc-api/org/… 【参考方案1】:

你可以将选项datastax-java-driver.advanced.reconnect-on-init设置为true来达到你想要的效果。它的用法在驱动程序文档中的configuration reference page 中进行了说明:

如果在第一次初始化尝试时所有接触点都无法访问,是否安排重新连接尝试。 如果这是真的,驱动程序将根据重新连接策略重试。 SessionBuilder.build() 调用 - 或 SessionBuilder.buildAsync() 返回的未来 - 在到达联系点之前不会完成。如果这是错误的并且没有可用的联系点,则驱动程序将失败并返回 AllNodesFailedException

但是要小心:如果将此选项设置为 true,如上所述,任何尝试访问 CqlSession bean 的组件,即使会话 bean 是惰性的,都会阻塞,直到驱动程序可以连接,如果接触点错误可能会永远阻塞。

如果这对您来说不可接受,我建议您将 CqlSession bean 包装在另一个 bean 中,该 bean 将检查 SessionBuilder.buildAsync() 返回的未来是否完成,并阻止、抛出或返回 null,具体取决于根据来电者的期望。

【讨论】:

感谢您的提示,我会尝试的。【参考方案2】:

[编辑]昨晚我在内部联系了 DataStax 驱动程序团队,adutra 已做出回应,因此我撤回了我的回应。

【讨论】:

不,不是。可能有人需要应用程序在没有数据库、某些单体或动态数据库的情况下继续运行某些功能。此外,不依赖于一些像数据源这样的 bean 初始化也是可行的 这取决于用例:有时最好有一个“快速失败”系统,如果外部依赖项(数据库、消息提供程序等)不可用,应用程序启动应该立即失败.但有时你可能有一个应用程序,例如3种不同的外部依赖。并且如果一个不可用 -> 可以报告错误并开始。

以上是关于Spring Boot 和 Spring Data with Cassandra:在数据库连接失败时继续的主要内容,如果未能解决你的问题,请参考以下文章

spring boot整合spring Data JPA和freemarker

使用 spring-boot 和 spring-data 全局启用休眠过滤器

Spring Boot 和 Spring Data MongoDB:在 ResponseBody 中隐藏字段

spring boot系列spring boot 配置spring data jpa (查询方法)

无法将 Spring Data MongoDB + Spring Data JPA 与 Spring Boot 一起使用

使用 spring-data-jpa 和 MockMvc 进行 spring boot junit 测试