javascript
容器化Spring Data Cassandra应用程序
我正在繼續(xù)學(xué)習(xí)Docker的旅程。 在這一點(diǎn)上,我仍然保持簡單。 這次,我將解決將Spring和Cassandra應(yīng)用程序轉(zhuǎn)換為使用容器而不是在主機(jī)上本地運(yùn)行的問題。 更準(zhǔn)確地說,使用Spring Data Cassandra整理應(yīng)用程序。
我希望我前幾天看過進(jìn)行此更改。 我在Cassandra上寫了很多文章,每次我必須cd到正確的目錄或有啟動(dòng)它的快捷方式時(shí)。 我想這沒什么大不了的,但是還涉及其他一些事情。 例如,刪除和重新創(chuàng)建鍵空間,以便可以從頭開始測試我的應(yīng)用程序。 現(xiàn)在,我只刪除容器并重新啟動(dòng)它。 無論如何對(duì)我來說,這是有幫助的!
這篇文章與我以前的文章《 使用Docker將現(xiàn)有應(yīng)用程序推送到容器》稍有不同。 取而代之的是,我將更多地關(guān)注于應(yīng)用程序端,并刪除僅使用Docker的中間步驟,而直接跳入Docker Compose。
集裝箱集裝箱
我認(rèn)為最好從項(xiàng)目的容器端開始,因?yàn)閼?yīng)用程序取決于Cassandra容器的配置。
我們走吧!
FROM openjdk:10-jre-slim LABEL maintainer="Dan Newton" ARG JAR_FILE ADD target/${JAR_FILE} app.jar ENTRYPOINT ["java", "-jar", "/app.jar"]這里沒有太多的事情。 這個(gè)Dockerfile構(gòu)建了Spring應(yīng)用程序映像,稍后將其放入容器中。
接下來是docker-compose文件。 這將同時(shí)構(gòu)建Spring應(yīng)用程序和Cassandra容器:
version: '3' services:app:build:context: .args:JAR_FILE: /spring-data-cassandra-docker-1.0.0.jarrestart: alwayscassandra: image: "cassandra"同樣,這里沒有太多。 app容器使用Dockerfile定義的Dockerfile構(gòu)建Spring應(yīng)用程序。 cassandra容器而是依賴于現(xiàn)有的映像,適當(dāng)?shù)孛麨閏assandra 。
突出的一件事是,將restart屬性設(shè)置為always 。 這是我的懶惰嘗試,試圖超越Cassandra啟動(dòng)所需的時(shí)間,而且所有容器都以docker-compose開頭的事實(shí)是同時(shí)啟動(dòng)的。 這導(dǎo)致應(yīng)用程序在未準(zhǔn)備就緒的情況下嘗試連接到Cassandra的情況。 不幸的是,這導(dǎo)致應(yīng)用程序崩潰。 我希望它對(duì)內(nèi)置的初始連接將有一些重試功能……但事實(shí)并非如此。
當(dāng)我們遍歷代碼時(shí),我們將看到如何以編程方式處理初始Cassandra連接,而不是依賴于應(yīng)用程序死掉并重新啟動(dòng)多次。 您仍然會(huì)看到我處理連接的版本…我并不是真正的解決方案擁護(hù)者,但是我嘗試的所有其他操作都使我更加痛苦。
一點(diǎn)代碼
我說過這篇文章將把重點(diǎn)更多地放在應(yīng)用程序代碼上,但是我們不會(huì)深入研究我在該應(yīng)用程序中放置的所有內(nèi)容以及如何使用Cassandra。 有關(guān)此類信息,您可以查看我的較舊文章,這些文章將在最后鏈接。 不過,我們要做的是檢查配置代碼,該代碼創(chuàng)建連接到Cassandra的bean。
首先,讓我們看一下設(shè)置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的初始連接,則將更少。 無論如何,讓我們將這一部分留出幾分鐘,集中討論本課程中的其他要點(diǎn)。
我創(chuàng)建ClusterConfig的最初原因是創(chuàng)建應(yīng)用程序?qū)⑹褂玫拿荑€空間。 為此, getKeyspaceCreations被覆蓋。 當(dāng)應(yīng)用程序連接時(shí),它將執(zhí)行此方法中定義的查詢以創(chuàng)建鍵空間。
如果不需要這樣做,并且以其他某種方式創(chuàng)建了鍵空間,例如,作為創(chuàng)建Cassandra容器的一部分執(zhí)行的腳本,則可以依靠Spring Boot的自動(dòng)配置。 實(shí)際上,這允許整個(gè)應(yīng)用程序由application.properties定義的屬性進(jìn)行配置,而僅此而已。 las,這本來不是。
由于我們定義了AbstractClusterConfiguration ,Spring Boot將在此區(qū)域中禁用其配置。 因此,我們需要通過重寫getContactPoints方法來手動(dòng)定義contactPoints (我將其命名為變量hosts )。 最初,這僅在application.properties定義。 我意識(shí)到一旦開始出現(xiàn)以下錯(cuò)誤,我需要進(jìn)行此更改:
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))在創(chuàng)建ClusterConfig之前,地址為cassandra而不是localhost 。
無需為集群配置其他任何屬性,因?yàn)樵谶@種情況下,Spring的默認(rèn)值就足夠了。
在這一點(diǎn)上,我已經(jīng)提到太多application.properties了,我應(yīng)該向您展示其中的內(nèi)容。
spring.data.cassandra.keyspace-name=mykeyspace spring.data.cassandra.schema-action=CREATE_IF_NOT_EXISTS spring.data.cassandra.contact-points=cassandrakeyspace-name和contact-points已經(jīng)彈出,因?yàn)樗鼈兣c配置集群有關(guān)。 根據(jù)項(xiàng)目中的實(shí)體創(chuàng)建表需要schema-action 。 我們不需要在這里做任何其他事情,因?yàn)樽詣?dòng)配置仍在該領(lǐng)域中工作。
contact-points值設(shè)置為cassandra的事實(shí)非常重要。 該域名源自提供給容器的名稱,在本例中為cassandra 。 因此,既可以使用cassandra也可以使用容器的實(shí)際IP。 域名絕對(duì)容易,因?yàn)椴渴鹬g始終是靜態(tài)的。 只是為了驗(yàn)證這一理論,您可以將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連接上重試。 這很煩人,但是我無法提出另一個(gè)簡單的解決方案。 如果您有更好的選擇,請(qǐng)告訴我!
我所做的實(shí)際上很簡單,但是代碼本身并不是很好。 除了RetryingCassandraClusterFactoryBean (我自己的類)之外, cluster方法是AbstractClusterConfiguration重寫版本的副本。 原始函數(shù)改為使用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驅(qū)動(dòng)程序來創(chuàng)建Cassandra集群的表示形式。 正如我在整個(gè)帖子中提到的。 如果無法建立連接,則將引發(fā)異常,如果未捕獲,將導(dǎo)致應(yīng)用程序終止。 這就是上面代碼的重點(diǎn)。 它將afterPropertiesSet包裝在為可能引發(fā)的異常指定的try-catch塊中。
添加了sleep ,使Cassandra有一些時(shí)間可以真正啟動(dòng)。 上一次嘗試失敗時(shí),嘗試立即重新連接沒有任何意義。
使用此代碼,應(yīng)用程序最終將連接到Cassandra。
在這一點(diǎn)上,我通常會(huì)向您顯示一些毫無意義的日志,以證明該應(yīng)用程序可以正常工作,但是在這種情況下,它實(shí)際上并沒有帶來任何好處。 當(dāng)您說以下命令時(shí),請(qǐng)相信我:
mvn clean install && docker-compose up然后創(chuàng)建Spring應(yīng)用程序映像,并旋轉(zhuǎn)兩個(gè)容器。
結(jié)論
我們已經(jīng)看過如何將連接到Cassandra數(shù)據(jù)庫的Spring應(yīng)用程序放入容器中。 一個(gè)用于應(yīng)用程序,另一個(gè)用于Cassandra。 應(yīng)用程序映像是從項(xiàng)目的代碼構(gòu)建的,而Cassandra映像是從Docker Hub獲取的。 圖像名稱為cassandra只是為了確保沒有人忘記。 通常,將兩個(gè)容器連接在一起相對(duì)簡單,但是應(yīng)用程序需要進(jìn)行一些調(diào)整,以便在連接到另一個(gè)容器中運(yùn)行的Cassandra時(shí)允許重試。 這使代碼有些丑陋,但是至少可以工作……由于本文中編寫的代碼,現(xiàn)在我有了另一個(gè)不需要在自己的機(jī)器上設(shè)置的應(yīng)用程序。
這篇文章中使用的代碼可以在我的GitHub上找到 。
如果您認(rèn)為這篇文章有幫助,可以在Twitter上@LankyDanDev關(guān)注我,以跟上我的新文章。
鏈接到我的Spring Data Cassandra帖子
- Spring Data Cassandra入門
- 使用Spring Data Cassandra分隔鍵空間
- 使用單個(gè)Spring Data CassandraTemplate的多個(gè)鍵空間
- 使用Spring Data Cassandra進(jìn)行更復(fù)雜的建模
- Spring Data Cassandra中的啟動(dòng)和關(guān)閉腳本
- Spring Data Cassandra的反應(yīng)流
- Spring Data Cassandra中自動(dòng)配置隨附的管道
- 使用Datastax Java驅(qū)動(dòng)程序與Cassandra進(jìn)行交互
哇,我沒意識(shí)到我寫了那么多Cassandra帖子。
翻譯自: https://www.javacodegeeks.com/2018/09/spring-data-cassandra-application.html
總結(jié)
以上是生活随笔為你收集整理的容器化Spring Data Cassandra应用程序的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java 正则表达式使用_如何用正则表达
- 下一篇: 使用React和Spring Boot构