Я продолжаю свое путешествие по изучению Докера. На данный момент я все еще сохраняю простоту. На этот раз я собираюсь заняться преобразованием приложения Spring и Cassandra для использования контейнеров вместо локального запуска на хост-компьютере. Точнее, используя Spring Data Cassandra для сортировки приложения.
Жаль, что я не посмотрел на это изменение некоторое время назад. Я написал довольно много постов на Кассандре, и каждый раз мне приходилось cd в нужный каталог или иметь ярлык для его запуска. Я думаю, что это не так уж и важно, но было еще несколько вещей, связанных с этим. Например, удаление и воссоздание пространств клавиш, чтобы я мог протестировать свое приложение с нуля. Теперь я просто удаляю контейнер и перезапускаю его. Во всяком случае, для меня это полезно!
Этот пост будет немного отличаться от моего предыдущего поста, в котором я использую Docker для размещения существующего приложения в контейнерах. Вместо этого я немного больше сосредоточусь на стороне приложения и удалю промежуточные этапы использования только Docker, а вместо этого перейду прямо в Docker Compose.
Контейнеры, контейнеры, контейнеры
Я думаю, что лучше всего начать с контейнерной части проекта, так как приложение зависит от конфигурации контейнера Cassandra.
Поехали!
FROM openjdk:10-jre-slim
LABEL maintainer="Dan Newton"
ARG JAR_FILE
ADD target/${JAR_FILE} app.jar
ENTRYPOINT ["java", "-jar", "/app.jar"]
Здесь не так уж много происходит. Этот Dockerfile создает образ приложения Spring, который будет помещен в контейнер через несколько минут.
Далее следует файл docker-compose . Это позволит создать как приложение Spring, так и контейнеры Cassandra:
version: '3'
services:
app:
build:
context: .
args:
JAR_FILE: /spring-data-cassandra-docker-1.0.0.jar
restart: always
cassandra:
image: "cassandra"
Опять же, здесь не так уж много. Контейнер app создает приложение Spring, используя Dockerfile , определенный ранее. Контейнер кассандра вместо этого полагается на существующий образ с соответствующим именем кассандра .
Одна вещь, которая выделяется, заключается в том, что свойство restart имеет значение всегда . Это была моя ленивая попытка понять, сколько времени требуется Кассандре для запуска, и тот факт, что все контейнеры начинались с docker-compose запускаются одновременно. Это приводит к ситуации, когда приложение пытается подключиться к Кассандре, не будучи готовым. К сожалению, это приводит к смерти приложения. Я надеялся, что у него будет возможность повторить попытку для встроенного начального подключения… Но это не так.
Когда мы пройдемся по коду, мы увидим, как справиться с первоначальным подключением Cassandra программно, вместо того, чтобы полагаться на то, что приложение умирает и перезапускается несколько раз. Вы все равно увидите мою версию обработки соединения… На самом деле я не поклонник своего решения, но все остальное, что я пробовал, причиняло мне гораздо больше боли.
Штришок кода
Я сказал, что этот пост будет больше посвящен коду приложения, что и будет, но мы не собираемся углубляться во все, что я вкладываю в это приложение, и в то, как использовать Cassandra. Для получения такой информации вы можете ознакомиться с моими старыми постами, которые я свяжу в конце. Однако что мы сделаем, так это изучим код конфигурации, который создает компоненты, подключающиеся к Cassandra.
Во-первых, давайте пройдем через Конфигурация кластера
@Configuration
public class ClusterConfig extends AbstractClusterConfiguration {
private final String keyspace;
private final String hosts;
ClusterConfig(
@Value("${spring.data.cassandra.keyspace-name}") String keyspace,
@Value("${spring.data.cassandra.contact-points}") String hosts) {
this.keyspace = keyspace;
this.hosts = hosts;
}
@Bean
@Override
public CassandraClusterFactoryBean cluster() {
RetryingCassandraClusterFactoryBean bean = new RetryingCassandraClusterFactoryBean();
bean.setAddressTranslator(getAddressTranslator());
bean.setAuthProvider(getAuthProvider());
bean.setClusterBuilderConfigurer(getClusterBuilderConfigurer());
bean.setClusterName(getClusterName());
bean.setCompressionType(getCompressionType());
bean.setContactPoints(getContactPoints());
bean.setLoadBalancingPolicy(getLoadBalancingPolicy());
bean.setMaxSchemaAgreementWaitSeconds(getMaxSchemaAgreementWaitSeconds());
bean.setMetricsEnabled(getMetricsEnabled());
bean.setNettyOptions(getNettyOptions());
bean.setPoolingOptions(getPoolingOptions());
bean.setPort(getPort());
bean.setProtocolVersion(getProtocolVersion());
bean.setQueryOptions(getQueryOptions());
bean.setReconnectionPolicy(getReconnectionPolicy());
bean.setRetryPolicy(getRetryPolicy());
bean.setSpeculativeExecutionPolicy(getSpeculativeExecutionPolicy());
bean.setSocketOptions(getSocketOptions());
bean.setTimestampGenerator(getTimestampGenerator());
bean.setKeyspaceCreations(getKeyspaceCreations());
bean.setKeyspaceDrops(getKeyspaceDrops());
bean.setStartupScripts(getStartupScripts());
bean.setShutdownScripts(getShutdownScripts());
return bean;
}
@Override
protected List getKeyspaceCreations() {
final CreateKeyspaceSpecification specification =
CreateKeyspaceSpecification.createKeyspace(keyspace)
.ifNotExists()
.with(KeyspaceOption.DURABLE_WRITES, true)
.withSimpleReplication();
return List.of(specification);
}
@Override
protected String getContactPoints() {
return hosts;
}
}
Там не так уж много, но было бы еще меньше, если бы Весна повторила первоначальное соединение с Кассандрой. В любом случае, давайте оставим эту часть на несколько минут и сосредоточимся на других моментах этого урока.
Первоначальная причина, по которой я создал Конфигурация кластера предназначалась для создания пространства ключей, которое будет использовать приложение. Для этого Создание getKeyspace было отменено. Когда приложение подключится, оно выполнит запрос, определенный в этом методе, для создания пространства ключей.
Если это не было необходимо, и пространство ключей было создано каким-либо другим способом, например, сценарием, выполняемым как часть создания контейнера Cassandra, вместо этого можно было полагаться на автоматическую настройку Spring Boot. Это фактически позволяет настраивать все приложение с помощью свойств, определенных в Это фактически позволяет настраивать все приложение с помощью свойств, определенных в
Поскольку мы определили Абстрактную конфигурацию кластера , Spring Boot отключит ее конфигурацию в этой области. Поэтому нам необходимо определить контактные точки ((Я назвал переменную hosts ) вручную, переопределив метод получить контактные точки . Первоначально это было определено только в application.properties . Я понял, что мне нужно внести это изменение, как только я начал получать следующую ошибку:
All host(s) tried for query failed (tried: localhost/127.0.0.1:9042 (com.datastax.driver.core.exceptions.TransportException: [localhost/127.0.0.1:9042] Cannot connect))
До того, как я создал Конфигурация кластера адрес был кассандра вместо localhost .
Никакие другие свойства кластера не нужно настраивать, так как значения по умолчанию Spring достаточно хороши для этого сценария.
Я уже упоминал application.properties так много на данный момент, я, вероятно, должен показать вам, что в нем есть.
spring.data.cassandra.keyspace-name=mykeyspace spring.data.cassandra.schema-action=CREATE_IF_NOT_EXISTS spring.data.cassandra.contact-points=cassandra
имя пространства ключей и контактные точки уже появились, поскольку они связаны с настройкой кластера. схема-действие необходимо для создания таблиц на основе сущностей в проекте. Нам больше ничего не нужно здесь делать, так как автоматическая настройка все еще работает в этой области.
Тот факт, что значение контактные точки установлено в кассандра очень важно. Это доменное имя происходит от имени, данного контейнеру, в данном случае кассандра . Поэтому можно использовать либо кассандру , либо фактический IP-адрес контейнера. Доменное имя определенно проще, так как оно всегда будет статичным между развертываниями. Просто чтобы проверить эту теорию, вы можете изменить имя контейнера cassandra на любое, какое захотите, и он все равно будет подключаться, если вы также измените его в application.properties .
Вернемся к Конфигурации кластера коду. Точнее, кластер компонент. Я снова вставил приведенный ниже код, чтобы его было легче просматривать:
@Configuration
public class ClusterConfig extends AbstractClusterConfiguration {
// other stuff
@Bean
@Override
public CassandraClusterFactoryBean cluster() {
RetryingCassandraClusterFactoryBean bean = new RetryingCassandraClusterFactoryBean();
bean.setAddressTranslator(getAddressTranslator());
bean.setAuthProvider(getAuthProvider());
bean.setClusterBuilderConfigurer(getClusterBuilderConfigurer());
bean.setClusterName(getClusterName());
bean.setCompressionType(getCompressionType());
bean.setContactPoints(getContactPoints());
bean.setLoadBalancingPolicy(getLoadBalancingPolicy());
bean.setMaxSchemaAgreementWaitSeconds(getMaxSchemaAgreementWaitSeconds());
bean.setMetricsEnabled(getMetricsEnabled());
bean.setNettyOptions(getNettyOptions());
bean.setPoolingOptions(getPoolingOptions());
bean.setPort(getPort());
bean.setProtocolVersion(getProtocolVersion());
bean.setQueryOptions(getQueryOptions());
bean.setReconnectionPolicy(getReconnectionPolicy());
bean.setRetryPolicy(getRetryPolicy());
bean.setSpeculativeExecutionPolicy(getSpeculativeExecutionPolicy());
bean.setSocketOptions(getSocketOptions());
bean.setTimestampGenerator(getTimestampGenerator());
bean.setKeyspaceCreations(getKeyspaceCreations());
bean.setKeyspaceDrops(getKeyspaceDrops());
bean.setStartupScripts(getStartupScripts());
bean.setShutdownScripts(getShutdownScripts());
return bean;
}
// other stuff
}
Этот код необходим только для разрешения повторных попыток при первоначальном подключении Cassandra. Это раздражает, но я не мог придумать другого простого решения. Если у вас есть что-нибудь получше, пожалуйста, дайте мне знать!
То, что я сделал, на самом деле довольно просто, но сам код не очень приятный. Метод cluster является точной копией переопределенной версии из Абстрактной конфигурации кластера , за исключением Повторяю попытку Cassandra Cluster FactoryBean (мой собственный класс). В исходной функции использовался Кластерный завод Кассандры (Весенний класс) вместо этого.
Ниже приведен Повторяю попытку Кассандра Кластер FactoryBean :
public class RetryingCassandraClusterFactoryBean extends CassandraClusterFactoryBean {
private static final Logger LOG =
LoggerFactory.getLogger(RetryingCassandraClusterFactoryBean.class);
@Override
public void afterPropertiesSet() throws Exception {
connect();
}
private void connect() throws Exception {
try {
super.afterPropertiesSet();
} catch (TransportException | IllegalArgumentException | NoHostAvailableException e) {
LOG.warn(e.getMessage());
LOG.warn("Retrying connection in 10 seconds");
sleep();
connect();
}
}
private void sleep() {
try {
Thread.sleep(10000);
} catch (InterruptedException ignored) {
}
}
}
Метод afterPropertiesSet в оригинале Кластерный завод Кассандры принимает его значения и создает представление кластера Cassandra, окончательно делегируя его драйверу Java Datastax. Как я уже упоминал на протяжении всего поста. Если ему не удастся установить соединение, будет выдано исключение, и если оно не будет поймано, это приведет к завершению работы приложения. В этом весь смысл приведенного выше кода. Он заключает afterPropertiesSet в блок try-catch, указанный для исключений, которые могут быть созданы.
sleep добавляется, чтобы дать Кассандре некоторое время для фактического запуска. Нет смысла пытаться восстановить соединение сразу, когда предыдущая попытка не удалась.
Используя этот код, приложение в конечном итоге подключится к Кассандре.
На этом этапе я обычно показываю вам несколько бессмысленных журналов, чтобы доказать, что приложение работает, но в данной ситуации это действительно ничего не дает. Просто поверьте мне, когда я говорю, если вы выполните следующую команду:
mvn clean install && docker-compose up
Затем создается образ приложения Spring, и оба контейнера разворачиваются.
Вывод
Мы рассмотрели, как поместить приложение Spring, которое подключается к базе данных Cassandra, в контейнеры. Один для приложения, а другой для Кассандры. Образ приложения построен из кода проекта, в то время как образ Кассандры взят из Docker Hub. Имя изображения кассандра просто чтобы убедиться, что никто не забудет. В целом соединение двух контейнеров было относительно простым, но приложению требовались некоторые настройки, чтобы разрешить повторные попытки при подключении к Кассандре, запущенной в другом контейнере. Это сделало код немного уродливее, но он, по крайней мере, работает… Благодаря коду, написанному в этом посте, у меня теперь есть еще одно приложение, которое мне не нужно настраивать на моей собственной машине.
Код, используемый в этом посте, можно найти на моем GitHub .
Если вы нашли этот пост полезным, вы можете подписаться на меня в Твиттере по адресу @Lankydandev , чтобы быть в курсе моих новых постов.
Ссылки на мои весенние данные, опубликованные Кассандрой
- Начало работы с весенними данными Кассандра
- Отдельные области ключей с весенними данными Кассандра
- Несколько пространств клавиш с использованием одной таблицы данных Spring CassandraTemplate
- Более сложное моделирование с использованием весенних данных Cassandra
- Сценарии запуска и завершения работы в Spring Data Cassandra
- Реактивные потоки с весенними данными Кассандра
- Сантехника включена в автоматическую настройку в Spring Data Cassandra
- Взаимодействие с Cassandra с помощью Java-драйвера Datastax
Вау, я и не подозревал, что написал так много постов Кассандры.
Оригинал: “https://dev.to/lankydandev/containerising-a-spring-data-cassandra-application-551p”