正如您可能注意到的,我们已经开始进行 Debezium 2.0 的开发。2.0 版本计划中的 更改之一将基线切换到 Java 11。虽然一些 Java 构建提供商仍然支持 Java 8,但其他 Java 8 发行版已经达到了其生命周期/支持的终点。用户反正都在转向 Java 11,正如 New Relic 的 Java 生态系统状况报告 等调查所示。但这不仅仅是支持问题:Java 11 带来各种性能改进,有用的工具(如 JDK Flight Recorder,它在 Java 11 中已开源),等等。所以我们觉得是时候开始考虑使用更近期的 JDK 作为 Debezium 的基线了,而新的主版本是一个自然的切换里程碑。

从 Debezium 2.0 的第一个版本 2.0.0.Alpha1 开始,Debezium 的组件将编译为 Java 11 字节码。因此,在下一次主要更新中运行 Debezium 将需要 Java 11。此外,如果您在项目中使用任何 Debezium 组件作为库(使用 Debezium 的 嵌入式引擎),您将不得不切换到 Java 11。

但是,标题中的 Java 11/17 是什么意思?它只是为了吓唬您,还是我们要立即切换到 Java 17?

<此处为戏剧性的停顿>

不,我们不想吓唬您。我们实际上计划切换到 Java 17,但仅限于测试套件。请注意,Java 11 和 17 都是长期支持 (LTS) 版本。我们暂时不希望将 Java 17 用于实际的 Debezium 工件,因为这可能会对大量的 Debezium 用户造成问题;例如,前面提到的 New Relic 报告显示,大多数用户仍在 O Java 11,我们当然不想排除他们。但是,在测试中使用 Java 17 不会以任何方式影响用户,并将允许我们在测试中使用一些更新的 Java 功能,例如:* 文本块,这可以简化多行 JSON 或 SQL 字符串的使用;* 记录,它可以提高我们测试中大量使用的流操作的可读性;* switch 表达式,等等。

是不是很棒?

实现

使用 Maven 为代码和测试设置不同的字节码级别非常容易,您只需要设置以下属性:

<maven.compiler.release>11</maven.compiler.release>
<maven.compiler.testRelease>17</maven.compiler.testRelease>

请注意,我们使用的是 release 选项而不是旧的 sourcetarget 选项,这可以防止意外使用目标 Java 版本中不存在的 Java API。有关更多详细信息,请参阅 Gunnar 的博客文章 ByteBuffer and the Dreaded NoSuchMethodError

切换到 Java 11 后,Maven Checkstyle 插件ImpSort 插件(一个负责正确导入排序的插件)开始失败。但是,将它们更新到最新版本已解决了所有问题。

这是比较容易的部分。最困难的部分是 Debezium 的 Apache Cassandra 连接器

Cassandra 连接器测试

1.9 版本以来,Cassandra 连接器同时支持 Cassandra 3 和 Cassandra 4。Cassandra 4 与 Java 11 兼容性良好,但运行 Cassandra 3 与 Java 11 不可能(或至少需要一些技巧)。该连接器的现有测试实现不像我们对其他所有数据库连接器所做的那样在容器中运行 Cassandra,而是以嵌入式模式运行 Cassandra,即在测试本身相同的 JVM 和进程中运行。因此,如果您想使用 Java 11(或 17)运行测试,Cassandra 3 连接器模块的测试将会失败。

显而易见的解决方案是以 Java 8 运行 Cassandra。这听起来不错,但这种方法有一个陷阱。Cassandra 连接器需要访问 Cassandra 日志文件,因为它从中获取 CDC 事件,因此测试需要访问容器中的 Cassandra 文件。这可以通过临时目录轻松解决,例如在 target 目录内,将其作为卷挂载到运行 Cassandra 的容器中。容器中运行的 Cassandra 之后可以使用此挂载卷来存储其数据。

真正的麻烦在于测试后的清理工作。由于 Cassandra 在容器中以名为 cassandra 的专用用户运行,该用户很可能在测试机上不存在(或具有不同的 UID/GID),因此在尝试删除包含 Cassandra 文件的临时目录时,清理工作会失败。这些文件是在挂载到容器的临时目录中创建的,而不是在 Docker FS overlay 中创建的,因此它们存在于 target 目录中。由于文件是由 cassandra 用户创建的,而该用户很可能与运行测试的用户不同,因此运行测试的用户没有足够的权限来删除由 cassandra 用户创建的文件。尝试在 Cassandra 退出时在某个包装脚本中从 Cassandra 容器中删除它们被证明相当麻烦且不可靠。

最有希望的解决方案是启动第二个具有相同 cassandra 用户并且可以访问挂载卷的容器,并在第一个 Cassandra 容器停止后清理文件。

我们考虑了两种运行容器的选项:

我们在项目的其他地方使用了 Fabric8 插件,这建议在此处也使用它以保持项目的一致性。另一方面,使用 Testcontainers 将使测试对开发人员来说更方便(毕竟是开发人员使用测试!),因为它允许直接从 IDE 运行测试,而无需手动启动容器。

最终,决定因素是 Fabric8 插件无法运行清理容器。Maven 不允许在同一阶段执行不同的配置,因此不可能在 post-integration-test 阶段停止 Cassandra 容器,同时在该阶段运行清理容器。Testcontainers 允许在需要时以编程方式启动和停止容器,让我们直接在测试代码中定义镜像,这样我们就不需要额外的 Dockerfile,并且清理容器只是隐藏在测试本身中的一个实现细节。能够直接从 IDE 运行测试,而无需手动启动和停止数据库容器,这额外的好处。

使用 Testcontainers 时唯一的棘手之处在于,当我们尝试使用 Docker 的 cmd 命令删除日志文件时,Testcontainers 会随机失败,并显示容器未启动,尽管实际上所有 Cassandra 文件都已被删除。容器的运行速度可能太快,以至于在 Testcontainers 察觉到之前就已完成。最后,我们通过在容器中添加一个短暂的 sleep 并执行另一个命令来解决这个问题,该命令进行清理。

使用 Testcontainers 的最终清理代码如下所示:

@AfterClass
public static void tearDownClass() throws IOException, InterruptedException {
    destroyTestKeyspace();
    cassandra.stop();

    GenericContainer cleanup = new GenericContainer(new ImageFromDockerfile()
            .withDockerfileFromBuilder(builder -> builder
                    .from("eclipse-temurin:8-jre-focal")
                    .volume("/var/lib/cassandra")
                    .cmd("sleep", "10") // Give TC some time to find out container is running.
                    .build()))
            .withFileSystemBind(cassandraDir, CASSANDRA_SERVER_DIR, BindMode.READ_WRITE);
    cleanup.start();
    cleanup.execInContainer(
            "rm", "-rf",
            CASSANDRA_SERVER_DIR + "/data",
            CASSANDRA_SERVER_DIR + "/cdc_raw_directory",
            CASSANDRA_SERVER_DIR + "/commitlog",
            CASSANDRA_SERVER_DIR + "/hints",
            CASSANDRA_SERVER_DIR + "/saved_caches");
    cleanup.stop();
}

一旦解决了 Cassandra 测试问题,我们就基本完成了,并准备好在主要的 Debezium 代码中使用 Java 11,在我们的测试中使用 Java 17。

待解决的问题

我们需要更多的实际测试来确保一切在 Java 11/17 下都能正常工作。您在测试和报告错误方面的帮助将非常有价值,并且非常欢迎。目前我们知道有一个与 Java 更新相关的小的未解决问题。某些 IDE 无法区分 maven.compiler.releasemaven.compiler.testRelease(或者我们不清楚如何设置它们)。例如,这个使用 文本块的测试在 IDE 中被标记为错误:

IntelliJ Idea 中使用文本块的测试。

您可以手动将 Java 版本设置为 17,但在这种情况下,您可能会无意中使用 Java > 11 的功能在非测试代码中,而 IDE 没有提醒您(这确实不是大问题,因为下一个 Maven 构建,例如在 CI 中,会捕获该问题)。此外,例如,Idea 会在 pom.xml 文件发生任何更改时重置代码级别。您是否解决了这个问题?或者您使用的 IDE 没有混合不同 Java 级别的问题?请在讨论中分享您的经验!

Vojtěch Juránek

Vojta 是 Red Hat 的一名软件工程师。他住在捷克共和国。

 


关于 Debezium

Debezium 是一个开源的分布式平台,可以将现有数据库转变为事件流,使应用程序能够几乎即时地看到并响应数据库中已提交的每个行级更改。Debezium 构建在 Kafka 之上,并提供了 Kafka Connect 兼容的连接器,用于监控特定的数据库管理系统。Debezium 将数据更改的历史记录在 Kafka 日志中,这样您的应用程序可以随时停止和重新启动,并可以轻松地消费在未运行时错过的所有事件,确保所有事件都被正确且完整地处理。Debezium 在 Apache 许可证 2.0 下是 开源 的。

参与进来

我们希望您觉得 Debezium 有趣且有用,并希望尝试一下。在 Twitter @debezium 上关注我们,在 Zulip 上与我们聊天,或加入我们的 邮件列表 与社区交流。所有代码都在 GitHub 上开源,因此请在本地构建代码,帮助我们改进现有连接器并添加更多连接器。如果您发现问题或有改进 Debezium 的想法,请告诉我们或 记录一个问题

版权所有 © Debezium 及其作者。保留所有权利。有关我们的商标详情,请访问我们的 商标政策商标列表。第三方商标属于其各自所有者,在此提及并不表示任何认可或关联。
×