Я продолжаю свой путь изучения Docker. Я все еще держу это простым в этом пункте. На этот раз я собираюсь заняться преобразованием приложений Spring и Cassandra для использования контейнеров вместо локального запуска на хост-компьютере. Точнее, используя Spring Data Cassandra для сортировки приложения.
Я хотел бы посмотреть на это изменение некоторое время назад. Я написал достаточное количество сообщений на Cassandra, и каждый раз мне приходилось cd
к нужному каталогу или иметь ярлык для его запуска. Я думаю, что это не так уж важно, но было несколько других вещей. Например, удаление и воссоздание ключей, чтобы я мог протестировать свое приложение с нуля. Теперь я просто удаляю контейнер и перезапускаю его. В любом случае, это полезно для меня!
Этот пост будет немного отличаться от моего предыдущего поста « Использование Docker для помещения существующего приложения в контейнеры» . Вместо этого я сконцентрируюсь немного больше на стороне приложения и удалю промежуточные этапы использования только Docker, а вместо этого перейду прямо к Docker Compose.
Контейнеры, контейнеры, контейнеры
Я думаю, что лучше всего начинать со стороны контейнера проекта, поскольку приложение зависит от конфигурации контейнера Cassandra.
Поехали!
1
2
3
4
5
|
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:
01
02
03
04
05
06
07
08
09
10
|
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
. Контейнер cassandra
вместо этого полагается на существующее изображение, соответственно названное cassandra
.
Одна вещь, которая выделяется, это то, что свойство restart
установлено в always
. Это была моя ленивая попытка узнать, сколько времени потребуется Кассандре, чтобы начать, и тот факт, что все контейнеры, запущенные с docker-compose
запускаются одновременно. Это приводит к ситуации, когда приложение пытается подключиться к Cassandra без его готовности. К сожалению, это приводит к смерти приложения. Я надеялся, что у него будет некоторая возможность повторения для встроенного подключения … Но это не так.
Когда мы пройдемся по коду, мы увидим, как обращаться с исходным соединением Cassandra программно, а не полагаться на то, что приложение умирает и перезапускается несколько раз. В любом случае, вы увидите мою версию обработки соединения … Я на самом деле не фанат своего решения, но все остальное, что я пробовал, причиняло мне гораздо больше боли.
Тире кода
Я сказал, что этот пост будет больше фокусироваться на коде приложения, что и будет, но мы не собираемся углубляться во все, что я вкладываю в это приложение, и как использовать Cassandra. Для такого рода информации, вы можете взглянуть на мои старые посты, которые я приведу в конце. Что мы будем делать, так это изучить конфигурационный код, который создает компоненты, которые подключаются к Cassandra.
Сначала давайте рассмотрим ClusterConfig
который настраивает кластер Cassandra:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
|
@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; } } |
Там не так уж много, но было бы еще меньше, если бы Spring повторил первоначальное подключение к Cassandra. В любом случае, давайте оставим эту часть на несколько минут и сосредоточимся на других моментах в этом классе.
Первоначальной причиной, по которой я создал ClusterConfig
было создание пространства ключей, которое будет использовать приложение. Для этого getKeyspaceCreations
был переопределен. Когда приложение подключается, оно выполняет запрос, определенный в этом методе, для создания пространства ключей.
Если в этом нет необходимости, и пространство ключей было создано другим способом, например, сценарием, выполняемым как часть создания контейнера Cassandra, вместо этого можно было бы полагаться на автоконфигурацию Spring Boot. Это фактически позволяет настроить все приложение с помощью свойств, определенных в application.properties
и ничего более. Увы, это не должно было быть.
Поскольку мы определили AbstractClusterConfiguration
, Spring Boot отключит его настройку в этой области. Поэтому нам нужно определить contactPoints
(я назвал переменную hosts
) вручную, переопределив метод getContactPoints
. Первоначально это было определено только в application.properties
. Я понял, что мне нужно внести это изменение, как только я начал получать следующую ошибку:
1
|
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)) |
До того, как я создал ClusterConfig
адрес был cassandra
а не localhost
.
Не нужно настраивать другие свойства для кластера, так как настройки Spring достаточно хороши для этого сценария.
Я так много упомянул application.properties
, я должен показать вам, что в нем.
1
2
3
|
spring.data.cassandra.keyspace-name=mykeyspace spring.data.cassandra.schema-action=CREATE_IF_NOT_EXISTS spring.data.cassandra.contact-points=cassandra |
keyspace-name
и contact-points
уже появились, поскольку они связаны с настройкой кластера. schema-action
необходимо для создания таблиц на основе сущностей в проекте. Здесь нам больше ничего не нужно делать, так как автоконфигурация все еще работает в этой области.
Тот факт, что значение contact-points
установлено на cassandra
, очень важно. Это доменное имя происходит от имени, данного контейнеру, в данном случае, cassandra
. Поэтому можно использовать либо cassandra
, либо фактический IP-адрес контейнера. Доменное имя определенно проще, так как оно всегда будет статичным между развертываниями. Просто чтобы проверить эту теорию, вы можете изменить имя контейнера cassandra
на любое cassandra
и он все равно будет подключаться, если вы также измените его в application.properties
.
Вернуться к ClusterConfig
. Точнее, cluster
боб. Я вставил приведенный ниже код, чтобы на него было проще смотреть:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
@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
метод представляет собой точную копию переопределенной версии из AbstractClusterConfiguration
, за исключением RetryingCassandraClusterFactoryBean
(мой собственный класс). Оригинальная функция использовала вместо этого CassandraClusterFactoryBean
(класс Spring).
Ниже приведено RetryingCassandraClusterFactoryBean
:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
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
в исходном CassandraClusterFactoryBean
принимает его значения и создает представление кластера Cassandra, наконец, делегируя его драйверу Datastax Java. Как я уже упоминал на протяжении всего поста. Если не удается установить соединение, будет выдано исключение, и если оно не будет перехвачено, приложение завершит работу. В этом весь смысл приведенного выше кода. Он оборачивает afterPropertiesSet
в блок try-catch, указанный для исключений, которые могут быть выброшены.
sleep
добавлен, чтобы дать Кассандре некоторое время для фактического запуска. Нет смысла пытаться восстановить соединение сразу, когда предыдущая попытка не удалась.
Используя этот код, приложение в конечном итоге подключится к Cassandra.
На этом этапе я обычно показываю вам несколько бессмысленных логов, чтобы доказать, что приложение работает, но в этой ситуации оно действительно ничего не приводит к таблице. Просто поверьте мне, когда я скажу, если вы запустите следующую команду:
1
|
mvn clean install && docker-compose up |
Затем создается образ приложения Spring, и оба контейнера запускаются.
Вывод
Мы рассмотрели, как поместить приложение Spring, которое подключается к базе данных Cassandra, в контейнеры. Один для приложения, а другой для Кассандры. Образ приложения создается из кода проекта, а образ Cassandra взят из Docker Hub. Название изображения — cassandra
чтобы никто не забыл. В общем, соединение двух контейнеров было относительно простым, но приложению требовались некоторые настройки, чтобы разрешить повторные попытки при подключении к Cassandra, работающему в другом контейнере. Это сделало код немного уродливым, но он работает по крайней мере … Благодаря коду, написанному в этом посте, у меня теперь есть другое приложение, которое мне не нужно устанавливать на моей собственной машине.
Код, используемый в этом посте, можно найти на моем GitHub .
Если вы нашли этот пост полезным, вы можете подписаться на меня в Twitter на @LankyDanDev, чтобы не отставать от моих новых сообщений.
Ссылки на мои сообщения Spring Data Cassandra
- Начало работы с Spring Data Cassandra
- Отдельные пространства клавиш с помощью Spring Data Cassandra
- Несколько пространств клавиш с использованием одного Spring Data CassandraTemplate
- Более сложное моделирование с помощью Spring Data Cassandra
- Скрипты запуска и завершения работы в Spring Data Cassandra
- Реактивные потоки с Spring Data Cassandra
- Сантехника включена в автоконфигурацию в Spring Data Cassandra
- Взаимодействие с Cassandra с использованием драйвера Datastax Java
Вау, я не осознавал, что написал так много постов Кассандры.
Опубликовано на Java Code Geeks с разрешения Дэна Ньютона, партнера нашей программы JCG . Смотрите оригинальную статью здесь: Контейнерные приложения Spring Data Cassandra
Мнения, высказанные участниками Java Code Geeks, являются их собственными. |