【AICon】AI 基础设施、LLM运维、大模型训练与推理,一场会议,全方位涵盖! >>> 了解详情
写点什么

基于 Java 9 模块系统和 Vert.x 开发持续集成系统

  • 2018-03-07
  • 本文字数:3925 字

    阅读完需:约 13 分钟

本文要点

  • Vert.x 兼容 Java 9,可一起用于构建应用程序。
  • 很多 Java 类库仍然不支持模块化。
  • 对“自动模块”要格外小心(一些类库还没有成为模块)。
  • Java 内置的 Nashorn JavaScript 运行环境对于 Vert.x 的应用程序来说十分有用。

这篇文章将介绍如何使用 Eclipse Vert.x 设计和开发一个基于消息驱动的响应式持续集成(CI)系统。我们将利用 Java 平台模块系统(JPMS)来构建一个由多个模块组成的应用程序,模块之间通过定义好的接口进行通信。

有了 JPMS,架构师和开发者就可以使用模块来重构大型的遗留系统,或者用它们来创建新的应用程序。不过,要在模块系统中使用已有的 Java 类库并不是件容易的事。因此,我们也会探讨在使用 JPMS 过程中可能遇到的各种问题,以及如何解决这些问题。

先让我们来定义这个 CI 系统的最小可用产品(MVP),我们将把它构建成 Docker 原生系统。这个系统需要提供如下特性,并通过 REST API 暴露出来:

  1. 支持针对仓库的 CRUD 操作。一个仓库代表一个项目,并带有 Git 仓库的连接地址。
  2. 支持“管道即代码”。管道定义了构建流程,并使用 JavaScript 来定义,JavaScript 脚本文件可以与代码保存在一起。
  3. 提供用于启动或关闭管道的 API。管道的一个实例就代表一次构建过程。

定义好 MVP 后,就可以开始构建我们的系统了。首先要创建项目的骨架,可以使用 IntelliJ 提供的多模块 Gradle 项目模板来创建骨架。因为要使用 JDK 9,所以最好可以选择最新版的 Gradle(在写这篇文章是最新版是 4.4.1)。我们还需要添加 Jigsaw 插件,并把代码兼容性设置为 Java 9。项目的主文件“build.gradle”应该看起来像下面这样:

与其他大多数系统一样,我们将会有一个公共库,用来放置实体类、工具类、共享常量、查询解析器等。我们把这个公共库定义成一个Java 9 模块。

之前已经讲过,Java 9 模块是接口、类和资源文件的集合,具有自描述的特点,而且有自己的名字。JPMS 引入了“module-info.java”文件,开发者用它定义模块的公共契约和对其他模块的依赖。我们也将使用这个文件来命名我们的模块,并指定对其他模块的依赖以及模块自身暴露出来的公共包。

下图是module-info.java 文件的示例代码:

每个“module-info.java”文件都以关键字“module”作为开头,后面跟上模块的名字。用在包命名上的反向域名命名方式也可以用在模块的命名上。

代码块中有两个新的关键词——“exports”和“requires”。“exports”用于声明由该模块暴露出来的公共包,也就是模块的公共API。“requires”用于声明对其他模块的依赖。

那么,问题来了,如果一个Java 9 模块的依赖包并不是模块,那该怎么办?这个时候,自动模块就派上用场了。

正如它的名字告诉我们的那样,非模块的JAR 包会被自动转成模块,并基于JAR 包的名字来生成模块名。模块名的生成遵循这样的规则:以JAR 包文件开头,去掉扩展名,用点号替代连字符,如果有版本号就把版本号去掉。这样的话,“vert-core-3.5.0.jar”对应的模块名就是“vertx.core”。不过,这种方式不一定都能奏效,后面我们会举一个与Netty 依赖包相关的例子。

除了核心模块,我们还要定义其他一些模块,用于访问数据库、用户认证、运行引擎以及与CI 系统中的其他插件交互。

在介绍了Java 模块化的一些概念后,接下来让我们来聊聊Vert.x。Vert.x 是一个工具套件,提供了非阻塞的API,也就是说,Vert.x 应用程序只需要使用很少的线程就可以处理大量的并发请求。Vert.x 采用了multi-reactor 模式来达到这个目的。

熟悉JavaScript 的开发者或许还记得单线程事件循环模型,multi-reactor 模式与之类似,只不过它使用了多个线程。Vert.x 根据给定服务器的CPU 核数创建相应个数的事件循环对象。

Vert.x 还提供了另一种基于 actor 的并发模型。在 Vert.x 生态系统中,actor 被称为“verticle”,verticle 之间通过 JSON 消息进行通信,这些消息通过事件总线进行传送。我们还可以指定部署多少个 verticle 实例。

事件总线可以是一个集群,使用集群管理器来管理,比如 Hazelcast 或 Zookeeper。我们可以把运行在 Vert.x 实例上的 verticle 或 verticle 组合看成是微服务。mutli-reactor 模型、verticle 和事件总线让 Vert.x 应用程序具备了高响应式、高弹性的特点,因此,我们可以说 Vert.x 应用程序是反应式的。

现在让我们来看看这个 CI 系统的整体流程:

如上图所示,有好几个verticle 通过Vert.x 事件总线进行通信。要注意,图中的插件也是verticle。Server verticle 是CI 系统的入口,对外暴露了一个REST API,命令行或GUI 客户端可以通过这个API 指定代码仓库的连接地址、创建和运行构建管道。

下面的代码告诉我们如何在Vert.x 中定义API 和路由:

我们使用Vert.x 的Web 类库来定义REST API,而且所有的路由均以“/api/v1/”作为前缀。Vert.x 还提供了很多其他类库,用于快速开发反应式应用程序。

例如,我们可以使用Web API 类库来设计一个基于OpenAPI 3 的应用程序API,这个类库会帮我们处理好请求验证和安全验证问题。Vert.x 的OAuth 类库可用来提高应用程序API 的安全性,OAuth 厂商可以是谷歌、Facebook,也可以自定义。

在上一张图片中,Engine verticle 负责协调管道的执行。在客户端调用Server verticle 提供的API 之后,Server verticle 向Engine verticle 发送消息,Engine verticle 在收到消息之后会初始化一个新的flow 对象。

flow 对象实际上是一个简单的状态机,用来跟踪管道的执行状态。在任意时刻,flow 对象可能处于这三种状态中的一种:setup、run 或 teardown。它会根据输入消息来改变状态。在进入一个新的状态时,flow 对象会触发一个事件,并将事件发送到事件总线。

注册到事件总线上插件会处理这些消息,并把处理结果通过事件总线异步传回。下面的代码演示了如何注册一个消息处理器、创建 flow 对象以及处理流入的消息:

Engine verticle 也负责定位和部署其他插件或 verticle。我们使用了在 Java 6 中引入并在 Java 9 中改进过的服务加载器机制,用它在服务器启动过程中定位和部署插件。为了更好地理解服务加载机制,有必要讨论一下服务和服务提供者。

服务其实就是一个已知的接口或类(通常是抽象类),而服务提供者则是服务的具体实现。ServiceLoader 类用于加载实现了给定服务的服务提供者。我们可以在模块中声明它使用了某个特定的服务,然后使用 ServiceLoader 来定位和加载部署在运行环境中的服务提供者。

例如,server 模块声明了它要使用 Plugin 接口,workspace 模块则声明它将提供两个实现了 Plugin 接口的服务。

因此,server 模块在启动的时候,它会调用ServiceLoader,找到两个插件,然后把它们部署成verticle:

插件会完成很多工作,包括注册消息处理器,用于处理感兴趣的管道事件。例如,workspace 插件负责同步Git 代码,而脚本解析器插件负责扫描workspace,找出和执行管道脚本文件(使用JavaScript 编写)。执行完脚本文件会生成一些shell 命令,Docker 容器中的脚本执行器插件会执行这些命令。因为Vert.x 使用了Java 内置的Nashorn 引擎,所以完全可以运行JavaScript 代码。要知道,Vert.x 还可以支持JavaScript、Kotlin 和Groovy。

下面是管道脚本文件的部分代码:

收到消息后,脚本执行器插件将会下载Docker 镜像、创建容器并执行shell 命令。

Docker 的 REST API 通常是通过基于 unix domain socket 的 HTTP 来暴露的,并非传统的基于 TCP socket 的 HTTP(S)。这也是 Vert.x 得以发挥其作用的地方之一,我们可以使用 Vert.x 的异步客户端与 Docker 进行交互,而不是使用普通的同步阻塞式方案。

如果项目中包含了由 Netty 提供的原生传输类库,Vert.x 就会转而使用原生传输。如果我们在“build.gradle”和“module-info.java”文件中指定了类似“netty-transport-native-kqueue”这样的依赖,就会发生这样的情况。

或许 Vert.x 下一个版本会支持基于 unix domain socket 的 HTTP。目前,我们可以通过修改 Vert.x 类库的少量代码来解决这个问题。与 Docker 引擎交互的插件代码看起来是这样的:

加入非模块JAR 包“netty-transport-native-kqueue-4.1.15.Final-osx-x86_64.jar”作为依赖将会自动生成一个模块名,不过因为JAR 包含了Java 关键字native,我们的应用程序无法正常编译。

Netty 将在下一个版本中解决这个问题,而在问题得到解决之前,我们可以在 JAR 包的 manifest 文件中加入“Automatic-Module-Name”来绕过这个问题。为此,我们需要解压 JAR 包,修改“MANIFEST.MF”文件,加入“Automatic-Module-Name: io.netty.transport.kqueue”,然后通过下面的命令重新打包:

可以通过下面的命令来验证在manifest 文件中指定的自动模块名是否可以被正确识别:

我们还要使用相同的命令解决其他非模块JAR 包的命名问题。

接下来就可以构建和运行我们的CI 系统了。下面是运行应用程序的命令:

为了支持JPMS,我们在“javac”和“java”命令中增加了一些新的参数,这些参数告诉Java 编译器和运行时,使用模块路径和模块JAR 包来替代原先的类路径。

一些值得注意的参数:

“-p”或“—module-path”用于告诉Java 系统在指定的目录中查找Java 模块。

“-m”或“—module”用于指定模块和主类。

在这篇文章里,我们基于Vert.x 设计了一个模块化的微服务应用程序。我们使用了JPMS 和JDK 9,并构建了一个Docker 原生的CI 系统。可以在 GitHub 上下载相关代码,了解如何基于 Vert.x 和模块化系统开发一个小型的自包含模块化 Java 应用程序。

关于作者

Uday Tatiraju 是 Oracle 的首席工程师,在电子商务平台、搜索引擎、后端系统和 Web 与移动编程领域拥有十年的开发经验。

查看英文原文: Building a CI System with Java 9 Modules and Vert.x Microservices

2018-03-07 16:423508
用户头像

发布了 322 篇内容, 共 134.6 次阅读, 收获喜欢 144 次。

关注

评论

发布
暂无评论
发现更多内容

兆骑科创创业赛事活动举办平台,投融资对接,线上直播路演

兆骑科创凤阁

有了国产 DevOps 工具 ,还怕数字化转型成本高?

SoFlu软件机器人

赶紧收藏!!!我直接上瘾!百万人都在学的Docker

指剑

签约计划第三季 8月月更

web前端培训班哪个好选择

小谷哥

原来如此!谷歌架构师10年呕心沥血汇成的《23种设计模式》,这才是正解

冉然学Java

架构 23种设计模式 java; 编程、

面向云时代的龙蜥操作系统 是 CentOS 替代的最佳选择

OpenAnolis小助手

龙蜥操作系统 开放原子全球开源峰会 OpenAnolis 分论坛 CentOS 停服 CentOS 迁移最佳选择

一文读懂字节跳动“埋点验证平台”

字节跳动数据平台

字节跳动 数据治理 埋点治理 数据研发 埋点验证

秋招冲刺版!奉上[Java一线大厂高岗面试题解析合集]

冉然学Java

面试 面试题 大厂 java; 秋招

Java面试项目推荐,15个项目吃透两个offer拿到手软

冉然学Java

offer java; 技术栈 MAll java项目实战分享

前端培训机构课程怎么样

小谷哥

携手数字创新 共筑国产生态 7月份AntDB与5款产品完成互认证

亚信AntDB数据库

AntDB 国产数据库 AISWare AIDB

基于DevCloud进行黑白棋实时对战游戏开发实践【华为云至简致远】

科技怪咖

有了这个开源工具后,我五点就下班了!

IT学习日记

EasyExcel 签约计划第三季 seaweedfs文件系统 java excel导出导入 java csv导出导入

优雅地实时检测和更新 Web 应用

领创集团Advance Intelligence Group

Web Web应用

零基础培训学习大数据课程

小谷哥

干货:从零设计高并发架构

C++后台开发

高并发 架构师 C/C++后台开发 C/C++开发 高并发架构

JavaScript 里三个点 ...,可不是省略号啊···

华为云开发者联盟

JavaScript 前端 运算符 函数

【有奖征文 第13期】至简致远,“云”响世界,大胆秀出你的华为云技术主张,高额激励等你拿

科技怪咖

开源一夏 | 自己画一块ESP32-C3 的开发板(PCB到手)

矜辰所致

开源 硬件设计 8月月更 ESP32-C3

兆骑科创赛事服务平台对接,海内外高层次人才引进

兆骑科创凤阁

大数据培训课程哪个好呢?

小谷哥

湖北钠斯网络数字藏品交易系统

开源直播系统源码

NFT 数字藏品

带你玩转“超大杯”ECS特性及实验踩坑【华为云至简致远】

科技怪咖

国产数据库的红利还能“吃”多久?

墨天轮

数据库 国产数据库

阿里巴巴最新分享Spring Cloud核心笔记,全程实例讲解,通俗易懂

Java工程师

Java spring spring-cloud

什么是低代码开发?大家都真的看好低代码开发吗?

优秀

低代码开发

基于微信小程序的幼儿园招生报名系统开发笔记

CC同学

如何选择ui设计机构

小谷哥

开源一夏 | Spring事务传播机制

六月的雨在InfoQ

开源 Spring事务 8月月更

LED显示屏在会议室如何应用

Dylan

LED显示屏 led显示屏厂家

「Gitee篇」如何用Git平台账号登录建木CI

Jianmu

git 开源 DevOps 低代码 gitee

基于Java 9模块系统和Vert.x开发持续集成系统_Java_Uday Tatiraju_InfoQ精选文章