Spring Boot и Spring Data с Cassandra: продолжить работу при неудачном подключении к базе данных

Я использую Spring Boot и Spring Data с Cassandra. При запуске приложения Spring устанавливает соединение с базой данных для настройки схемы и инициализации репозиториев данных Spring. Если база данных недоступна, приложение не запустится.

Я хочу, чтобы приложение просто выдавало ошибку и запускалось. Конечно, я больше не могу использовать репозитории, но другие службы (контроллеры отдыха и т. д.), которые не зависят от базы данных, должны работать. Также было бы неплохо увидеть в проверке работоспособности привода, что cassandra не работает.

Для JDBC существует свойство spring.datasource.continue-on-error. Я не смог найти что-то подобное для Кассандры.

Я также пытался создать пользовательскую конфигурацию cassandra и пытался поймать исключение при создании CqlSession, но не смог добиться желаемого поведения.

EDIT: как было предложено @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);
    }
}

РЕДАКТИРОВАТЬ 2: теперь у меня есть рабочий пример (запуск приложения, пользовательская проверка работоспособности для 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 (Fake session implementation)

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

Какие-либо предложения?


person spx01    schedule 30.07.2020    source источник
comment
Я также пытался создать пользовательскую конфигурацию cassandra и пытался поймать исключение при создании CqlSession, но не смог добиться желаемого поведения. Почему? Если вы объявите другой bean-компонент типа CqlSession, то значение по умолчанию, объявленное в CassandraAutoConfiguration, использоваться не будет; тогда вы можете создать свою сессию по своему усмотрению, поэтому я удивлен, что вы не смогли поймать исключение, созданное SessionBuilder.build().   -  person adutra    schedule 30.07.2020
comment
@adutra: я создал собственную реализацию CqlSession, но есть много зависимостей. Многие геттеры должны возвращать допустимые значения (иначе NPE). Я думаю, что можно было бы реализовать все необходимые методы, но это похоже на хак, и я пытаюсь найти более элегантное решение.   -  person spx01    schedule 30.07.2020
comment
Вам не нужно реализовывать CqlSession. Я предлагал перехватить исключение: public CqlSession cassandraSession(CqlSessionBuilder cqlSessionBuilder) { try { return cqlSessionBuilder.build(); } поймать (Исключение e) { вернуть ноль; } }   -  person adutra    schedule 31.07.2020
comment
Если вместо CqlSession будет возвращено значение null, это нарушит работу приложения: все компоненты, которые получают репозитории на Autowired, не могут быть созданы. Также нарушается автоконфигурация Spring. Неудовлетворенная зависимость, выраженная через метод «reactiveCassandraSession».   -  person spx01    schedule 31.07.2020
comment
Конечно. Если ваш компонент может быть нулевым, вы не можете внедрить его напрямую. Вам нужно обернуть его в необязательный или, что еще лучше, в ObjectProvider: docs.spring.io/spring-framework/docs/current/javadoc-api/org/   -  person adutra    schedule 02.08.2020


Ответы (2)


Вы можете установить для параметра datastax-java-driver.advanced.reconnect-on-init значение true, чтобы добиться желаемого эффекта. Его использование объясняется на справочной странице конфигурации в документации драйвера:

Следует ли планировать попытки повторного подключения, если все точки контакта недоступны при первой попытке инициализации. Если это правда, драйвер повторит попытку в соответствии с политикой повторного подключения. Вызов SessionBuilder.build() — или будущее, возвращенное SessionBuilder.buildAsync() — не завершится, пока не будет достигнута точка контакта. Если это неверно и точки контакта недоступны, драйвер выйдет из строя с ошибкой AllNodesFailedException.

Однако будьте осторожны: если для этого параметра установлено значение true, как указано выше, любой компонент, пытающийся получить доступ к CqlSession bean-компоненту, даже если сессионный bean-компонент является ленивым, будет блокироваться до тех пор, пока драйвер не сможет подключиться, и может заблокироваться навсегда, если контактные точки неверны.

Если это неприемлемо для вас, я предлагаю вам обернуть bean-компонент CqlSession в другой bean-компонент, который будет проверять, выполнено ли будущее, возвращаемое SessionBuilder.buildAsync(), и либо блокировать, либо выбрасывать, либо возвращать null, в зависимости от ожиданий вызывающего.

person adutra    schedule 30.07.2020
comment
Спасибо за этот совет, я попробую. - person spx01; 30.07.2020

[EDIT] Вчера вечером я связался с командой DataStax Drivers, и компания adutra ответила, поэтому я отзываю свой ответ.

person Erick Ramirez    schedule 30.07.2020
comment
Нет это не так. Это может быть случай, когда кому-то нужно, чтобы приложение продолжало работать с некоторыми функциями без базы данных, в каком-то монолите или в случае с динамической базой данных. Кроме того, целесообразно не зависеть от какой-либо инициализации bean-компонента, такой как источник данных. - person Artem Ptushkin; 30.07.2020
comment
Это зависит от варианта использования: иногда лучше иметь отказоустойчивую систему, если внешняя зависимость (база данных, поставщик сообщений и т. д.) недоступна, запуск приложения должен завершиться сбоем немедленно. Но иногда у вас может быть приложение, которое, например. 3 разные внешние зависимости. И если он недоступен -> можно сообщить об ошибке и начать. - person spx01; 30.07.2020