启动时重试连接到Cassandra节点

27

我希望使用Docker来启动我的应用程序和Cassandra数据库,并且我想使用Docker Compose来实现。不幸的是,Cassandra启动比我的应用程序慢得多,由于我的应用程序急切地初始化了Cluster对象,因此我会收到以下异常:

com.datastax.driver.core.exceptions.NoHostAvailableException: All host(s) tried for query failed (tried: cassandra/172.18.0.2:9042 (com.datastax.driver.core.exceptions.TransportException: [cassandra/172.18.0.2:9042] Cannot connect))
    at com.datastax.driver.core.ControlConnection.reconnectInternal(ControlConnection.java:233)
    at com.datastax.driver.core.ControlConnection.connect(ControlConnection.java:79)
    at com.datastax.driver.core.Cluster$Manager.init(Cluster.java:1454)
    at com.datastax.driver.core.Cluster.init(Cluster.java:163)
    at com.datastax.driver.core.Cluster.connectAsync(Cluster.java:334)
    at com.datastax.driver.core.Cluster.connectAsync(Cluster.java:309)
    at com.datastax.driver.core.Cluster.connect(Cluster.java:251)

根据堆栈跟踪和一些调试,看起来Cassandra Java驱动程序不会将重试策略应用于初始启动。这对我来说有点奇怪。是否有一种方式可以配置驱动程序,以便它将继续尝试连接到服务器直到成功?


你只有一个Cassandra节点吗? - Sotirios Delimanolis
1
@SotiriosDelimanolis 当然可以;至少目前来说,我在本地开发和测试方面不需要更多了。 - Vladimir Matveev
6个回答

13

您应该能够在NoHostAvailableException上编写一些try/catch逻辑,以便在等待5-10秒后重试连接。我建议在一定时间段内仅尝试几次,然后在那个时间点之后抛出异常,因为您知道此时应该已经启动。

示例伪代码

Connection makeCassandraConnection(int retryCount) {
    Exception lastException = new IllegalStateException();
    while (retryCount > 0) {
        try {
            return doConnectionStuff();
        } catch (NoHostAvailableException e) {
            lastException = e;
            retryCount--;
            Thread.sleep(TimeUnit.SECONDS.toMillis(5));
        }
    }
    throw lastException;
}

1
是的,看来这就是我必须要做的。很奇怪驱动程序没有自动执行此操作。我可能会在他们的错误跟踪器中创建一个功能请求单。 - Vladimir Matveev
如果有多个线程尝试调用这个方法怎么办?所有其他线程都必须等待,这会影响性能,对吧? - Radhakrishna Sharma Gorenta

2

如果您不想更改客户端代码,而且您的客户端应用程序的Docker容器因错误停止,您可以在docker-compose文件中为客户端应用程序使用以下属性。

restart: unless-stopped

这将使你的客户端应用程序容器在失败时重新启动。示例docker-compose.yml文件:

version: '2'
services:
  cassandra:
    image: cassandra:3.5
    ports:
      - "9042:9042"
      - "9160:9160"
    environment:
      CASSANDRA_CLUSTER_NAME: demo
  app:
    image: your-app
    restart: unless-stopped

是的,这是一个可能的解决方案,但如果应用程序重启不是免费的(例如,如果它有一些初始化工作要做),它就无法工作。目前我的程序不需要进行任何复杂的初始化,但未来很可能需要类似的操作。 - Vladimir Matveev

1
数据斯塔克斯驱动程序不能以这种方式配置。
如果这只是Docker的问题,并且您不希望更改代码,则可以考虑使用类似于wait-for-it的东西,它是一个简单的脚本,将在启动应用程序之前等待TCP端口处于侦听状态。 9042是卡桑德拉的本机传输端口。
其他选项在此处的docker文档中讨论,但我个人仅使用了wait-for-it,在使用Docker内的Cassandra时发现它非常有用。

实际上,我目前正好使用goss来达到这个目的。除此之外,它还能够检查TCP端口的可用性。但是,自然而然地,我希望驱动程序来完成这个任务。 - Vladimir Matveev

0

参考:https://dev59.com/nFYN5IYBdhLWcg3wAUH-#69612290

您可以通过添加健康检查来修改 Docker Compose 文件,如下所示。

version: '3.8'
services:
  applicaion-service:
    image: your-applicaion-service:0.0.1
    depends_on:
      cassandra:
        condition: service_healthy


  cassandra:
    image: cassandra:4.0.1
    ports:
      - "9042:9042"
    healthcheck:
      test: ["CMD", "cqlsh", "-u cassandra", "-p cassandra" ,"-e describe keyspaces"]
      interval: 15s
      timeout: 10s
      retries: 10


-1
如果你要编排多个Docker容器,建议使用带有depends on标签的Docker Compose。
version: '2'
services:
  cassandra:
    image: cassandra:3.5
    ports:
      - "9042:9042"
      - "9160:9160"
    environment:
      CASSANDRA_CLUSTER_NAME: demo
  app:
    image: your-app
    restart: unless-stopped
    depends_on:
      - cassandra

依赖标签只等待容器初始化,Cassandra本身仍需要额外的时间启动。 - Samyel
是的,“depends_on”实际上不起作用,这是我尝试的第一件事。 - Vladimir Matveev
甚至更好的是,在Docker 3中已经删除了depends_on的条件,因此您不能再等待Cassandra变得健康。 - George Marin

-2

尝试增加连接超时时间,这是在AWS等平台上有时会发生的事情。我认为你正在查看错误日志的后期阶段,在某个时候它应该告诉你由于超时或无法访问网络而无法连接,然后将节点标记为不可用。

使用phantom,代码如下:

val Connector = ContactPoints(Seq(seedHost))
    .withClusterBuilder(_.withSocketOptions(
      new SocketOptions()
      .setReadTimeoutMillis(1500)
      .setConnectTimeoutMillis(20000)
    )).keySpace("bla")

资源链接:

com.datastax.driver.core.exceptions.NoHostAvailableException #445


1
Cassandra在OP的示例中尚未开始侦听客户端。由于没有任何内容侦听相应的端口,因此连接尝试将被拒绝。超时不会改变这一点。 - Sotirios Delimanolis

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接