我正在继续学习Docker的旅程。 在这一点上,我仍然保持简单。 这次,我将解决将Spring和Cassandra应用程序转换为使用容器而不是在主机上本地运行的问题。 更准确地说,使用Spring Data Cassandra整理应用程序。
我希望我前几天看过进行此更改。 我在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.jarrestart: alwayscassandra:
image: "cassandra"
同样,这里没有太多。 app
容器使用Dockerfile
定义的Dockerfile
构建Spring应用程序。 cassandra
容器而是依赖于现有的映像,适当地命名为cassandra
。
突出的一件事是,将restart
属性设置为always
。 这是我的懒惰尝试,试图超越Cassandra启动所需的时间,而且所有容器都以docker-compose
开头的事实是同时启动的。 这导致应用程序在未准备就绪的情况下尝试连接到Cassandra的情况。 不幸的是,这导致应用程序崩溃。 我希望它对内置的初始连接将有一些重试功能……但事实并非如此。
当我们遍历代码时,我们将看到如何以编程方式处理初始Cassandra连接,而不是依赖于应用程序死掉并重新启动多次。 您仍然会看到我处理连接的版本…我并不是真正的解决方案拥护者,但是我尝试的所有其他操作都使我更加痛苦。
一点代码
我说过这篇文章将把重点更多地放在应用程序代码上,但是我们不会深入研究我在该应用程序中放置的所有内容以及如何使用Cassandra。 有关此类信息,您可以查看我的较旧文章,这些文章将在最后链接。 不过,我们要做的是检查配置代码,该代码创建连接到Cassandra的bean。
首先,让我们看一下设置Cassandra集群的ClusterConfig
:
@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@Overridepublic 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;}@Overrideprotected List getKeyspaceCreations() {final CreateKeyspaceSpecification specification =CreateKeyspaceSpecification.createKeyspace(keyspace).ifNotExists().with(KeyspaceOption.DURABLE_WRITES, true).withSimpleReplication();return List.of(specification);}@Overrideprotected String getContactPoints() {return hosts;}
}
那里并没有太多,但是如果Spring重试与Cassandra的初始连接,则将更少。 无论如何,让我们将这一部分留出几分钟,集中讨论本课程中的其他要点。
我创建ClusterConfig
的最初原因是创建应用程序将使用的密钥空间。 为此, getKeyspaceCreations
被覆盖。 当应用程序连接时,它将执行此方法中定义的查询以创建键空间。
如果不需要这样做,并且以其他某种方式创建了键空间,例如,作为创建Cassandra容器的一部分执行的脚本,则可以依靠Spring Boot的自动配置。 实际上,这允许整个应用程序由application.properties
定义的属性进行配置,而仅此而已。 las,这本来不是。
由于我们定义了AbstractClusterConfiguration
,Spring Boot将在此区域中禁用其配置。 因此,我们需要通过重写getContactPoints
方法来手动定义contactPoints
(我将其命名为变量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))
在创建ClusterConfig
之前,地址为cassandra
而不是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
keyspace-name
和contact-points
已经弹出,因为它们与配置集群有关。 根据项目中的实体创建表需要schema-action
。 我们不需要在这里做任何其他事情,因为自动配置仍在该领域中工作。
contact-points
值设置为cassandra
的事实非常重要。 该域名源自提供给容器的名称,在本例中为cassandra
。 因此,既可以使用cassandra
也可以使用容器的实际IP。 域名绝对容易,因为部署之间始终是静态的。 只是为了验证这一理论,您可以将cassandra
容器的名称更改为所需的名称,只要您在application.properties
中也将其更改,它仍然可以连接。
返回到ClusterConfig
代码。 更确切地说,是cluster
bean。 我再次粘贴了下面的代码,以便于查看:
@Configuration
public class ClusterConfig extends AbstractClusterConfiguration {// other stuff@Bean@Overridepublic 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连接上重试。 这很烦人,但是我无法提出另一个简单的解决方案。 如果您有更好的选择,请告诉我!
我所做的实际上很简单,但是代码本身并不是很好。 除了RetryingCassandraClusterFactoryBean
(我自己的类)之外, cluster
方法是AbstractClusterConfiguration
重写版本的副本。 原始函数改为使用CassandraClusterFactoryBean
(Spring类)。
以下是RetryingCassandraClusterFactoryBean
:
public class RetryingCassandraClusterFactoryBean extends CassandraClusterFactoryBean {private static final Logger LOG =LoggerFactory.getLogger(RetryingCassandraClusterFactoryBean.class);@Overridepublic 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) {}}
}
原始CassandraClusterFactoryBean
的afterPropertiesSet
方法采用其值,并通过最终委托给Datastax Java驱动程序来创建Cassandra集群的表示形式。 正如我在整个帖子中提到的。 如果无法建立连接,则将引发异常,如果未捕获,将导致应用程序终止。 这就是上面代码的重点。 它将afterPropertiesSet
包装在为可能引发的异常指定的try-catch块中。
添加了sleep
,使Cassandra有一些时间可以真正启动。 上一次尝试失败时,尝试立即重新连接没有任何意义。
使用此代码,应用程序最终将连接到Cassandra。
在这一点上,我通常会向您显示一些毫无意义的日志,以证明该应用程序可以正常工作,但是在这种情况下,它实际上并没有带来任何好处。 当您说以下命令时,请相信我:
mvn clean install && docker-compose up
然后创建Spring应用程序映像,并旋转两个容器。
结论
我们已经看过如何将连接到Cassandra数据库的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中自动配置随附的管道
- 使用Datastax Java驱动程序与Cassandra进行交互
哇,我没意识到我写了那么多Cassandra帖子。
翻译自: https://www.javacodegeeks.com/2018/09/spring-data-cassandra-application.html