<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-hazelcast</artifactId>
<version>5.0.12</version>
</dependency>
Hazelcast 集群管理器 (vertx-hazelcast)
该组件为 Vert.x 提供了基于 Hazelcast 内存数据网格(IMDG)的集群管理器(Cluster Manager)实现。
它是 Vert.x 命令行工具默认采用的集群管理器,但由于 Vert.x 的集群管理器设计完全可插拔,因此你也可以随时替换为其他实现。
在项目构建描述文件中添加以下依赖:
-
Maven(在
pom.xml中):
-
Gradle(在
build.gradle文件中):
compile 'io.vertx:vertx-hazelcast:5.0.12'
在 Vert.x 中,集群管理器承担以下核心协作职责:
-
集群中 Vert.x 节点的自动发现与成员拓扑维护
-
维护全集群范围的 Event Bus 主题订阅者映射表(以便获知哪些节点对哪些 Event Bus 地址感兴趣)
-
分布式映射(Distributed Map)支持
-
分布式锁(Distributed Locks)
-
分布式计数器(Distributed Counters)
请注意:集群管理器并不负责节点间的 Event Bus 实际消息传输;节点间的数据通信由 Vert.x 直接通过底层的 TCP 连接通道高效完成。
使用 Hazelcast 集群管理器
若使用 Vert.x 命令行工具启动应用,需确保将对应的 jar 包(如 vertx-hazelcast-5.0.12.jar)放置在 Vert.x 安装目录下的 lib 目录中。
在基于 Maven 或 Gradle 的工程中,只需直接引入依赖 io.vertx:vertx-hazelcast:5.0.12 即可。
当该 jar 包存在于应用的类路径(Classpath)中时,Vert.x 在启动集群模式时将自动检测并将其激活为默认集群管理器。 请确保类路径中未引入其他集群管理器实现,否则 Vert.x 可能会误选其他集群组件。
若通过代码方式嵌入式启动 Vert.x,可以在配置选项中显式指定该集群管理器:
ClusterManager mgr = new HazelcastClusterManager();
Vertx
.builder()
.withClusterManager(mgr)
.buildClustered()
.onComplete(res -> {
if (res.succeeded()) {
Vertx vertx = res.result();
// 集群 Vert.x 实例就绪
} else {
// 启动失败!
}
});
配置 Hazelcast 集群管理器
通过 XML 配置文件配置
集群管理器默认加载打包在 jar 内部的 default-cluster.xml 配置文件。
若需覆盖该默认配置,可以在类路径根目录下提供自定义的 cluster.xml 文件。
若将应用打包为可执行 Fat Jar,cluster.xml 必须位于 Fat Jar 的根路径下;若使用外部配置文件,必须将包含该文件的 目录 追加到类路径中。例如,配合 Vert.x 启动器使用时配置如下:
# 如果 cluster.xml 位于当前工作目录:
java -jar ... -cp . -cluster
vertx run MyVerticle -cp . -cluster
# 如果 cluster.xml 位于 conf 目录:
java -jar ... -cp conf -cluster
另一种更灵活的覆盖方式是通过系统属性 vertx.hazelcast.config 指定外部配置文件路径:
# 加载位于文件系统中的外部绝对/相对路径配置:
java -Dvertx.hazelcast.config=./config/my-cluster-config.xml -jar ... -cluster
# 或者加载类路径中指定包路径下的配置:
java -Dvertx.hazelcast.config=classpath:my/package/config/my-cluster-config.xml -jar ... -cluster
当系统属性 vertx.hazelcast.config 存在时,其优先级高于类路径下的任何 cluster.xml;若根据该系统属性加载失败,系统将平滑降级至 cluster.xml 或 Hazelcast 默认配置。
Vert.x 不支持 使用 Hazelcast 原生的 -Dhazelcast.config 系统属性,请勿使用该参数。
|
XML 配置文件的属性细节在 Hazelcast 官方手册中有全面详尽的说明。
以编程方式配置
你也可以完全通过代码构造 Hazelcast 配置:
Config hazelcastConfig = new Config();
// 此处配置具体属性(省略)
ClusterManager mgr = new HazelcastClusterManager(hazelcastConfig);
Vertx
.builder()
.withClusterManager(mgr)
.buildClustered()
.onComplete(res -> {
if (res.succeeded()) {
Vertx vertx = res.result();
} else {
// 启动失败!
}
});
有时在既有 XML 配置的基础上仅微调部分字段会非常便利,例如动态修改集群名称:
Config hazelcastConfig = ConfigUtil.loadConfig();
hazelcastConfig.setClusterName("my-cluster-name");
ClusterManager mgr = new HazelcastClusterManager(hazelcastConfig);
Vertx
.builder()
.withClusterManager(mgr)
.buildClustered()
.onComplete(res -> {
if (res.succeeded()) {
Vertx vertx = res.result();
} else {
// 启动失败!
}
});
ConfigUtil#loadConfig 负责解析 XML 配置文件并将其转换为 Hazelcast 的 Config 对象,解析优先级依次为:
-
vertx.hazelcast.config系统属性指定的文件路径(若存在) -
类路径下的
cluster.xml文件(若存在) -
默认内置的
default-cluster.xml
节点发现方式
Hazelcast 支持多种节点发现机制。 默认配置使用组播(Multicast),因此你的局域网环境必须支持并开启组播广播。
如需使用 TCP-IP、云厂商发现等其他方式,请参阅 Hazelcast 官方文档。
通过系统属性覆盖本地与公网发布地址
有时集群节点绑定的内部监听地址与其他成员节点无法直接互通(例如处于多网络平面或云环境安全组隔离)。可以通过以下系统属性分别指定本地绑定地址与对外广播的公网/路由地址:
-Dhazelcast.local.localAddress=172.16.5.131 -Dhazelcast.local.publicAddress=104.198.78.81
复用既有的外部 Hazelcast 集群
你可以传入一个现存的 HazelcastInstance 实例以复用已有集群:
ClusterManager mgr = new HazelcastClusterManager(hazelcastInstance);
Vertx
.builder()
.withClusterManager(mgr)
.buildClustered()
.onComplete(res -> {
if (res.succeeded()) {
Vertx vertx = res.result();
} else {
// 启动失败!
}
});
在此场景下,Vert.x 并不拥有该 Hazelcast 集群实例的生命周期主权,因此在 Vert.x 关闭时不会停机 Hazelcast 实例。
请注意:传入的自定义 Hazelcast 实例必须包含以下核心拓扑定义:
<member-attributes>
<attribute name="__vertx.nodeId">unique-identifier</attribute>
</member-attributes>
<multimap name="__vertx.subs">
<backup-count>1</backup-count>
<value-collection-type>SET</value-collection-type>
</multimap>
<map name="__vertx.haInfo">
<backup-count>1</backup-count>
</map>
<map name="__vertx.nodeInfo">
<backup-count>1</backup-count>
</map>
__vertx.nodeId 被 Vert.x 用作节点在集群内的唯一标识。必须确保不同成员节点配置了不同的唯一值。
|
| 本模块不支持 Hazelcast Client 或 Smart Client 模式。 |
| 必须确保 Hazelcast 优先于 Vert.x 启动并在 Vert.x 之后关闭;此外应禁用 Hazelcast 自带的 JVM Shutdown Hook(可通过配置或系统属性关闭)。 |
调整故障节点踢出超时时间
默认情况下,若 Hazelcast 连续 60 秒未收到某节点的心跳,该节点将被判定为下线并移出集群。
可以通过系统属性 hazelcast.max.no.heartbeat.seconds 调整该心跳阈值:
-Dhazelcast.max.no.heartbeat.seconds=5
设置后,无心跳达到 5 秒即可触发故障节点剔除。
更多详情请参阅 Hazelcast 系统属性官方说明。
集群故障排查指南
如果默认的组播配置无法组网,请按以下原因排查:
1. 主机未开启组播
在 macOS 等工作站环境中,组播通常默认处于未启用状态。请根据操作系统指引配置组播路由。
2. 绑定了错误的网络网卡
如果机器上存在多个网卡接口(例如安装了虚拟机或 VPN),Hazelcast 可能会绑定错误的网卡。
可以通过在 XML 配置的 interfaces 元素中显式指定 IP 地址(必须将 enabled 设置为 true):
<interfaces enabled="true">
<interface>192.168.1.20</interface>
</interfaces>
在集群模式下运行 Vert.x 时,还必须确保 Vert.x Event Bus 同样绑定到正确的网络接口。在命令行中可以通过 cluster-host 参数显式指定:
vertx run myverticle.js -cluster -cluster-host your-ip-address
其中 your-ip-address 应与 Hazelcast 配置中的 IP 保持一致。
3. VPN 软件网络干扰
VPN 软件通常会创建不支持组播的虚拟网络接口。若启用了 VPN 且未显式指定正确的物理网卡,系统可能会错误选择 VPN 接口导致无法通信。解决方法是按照上一小节显式指定物理局域网 IP。
4. 生产环境不支持组播时的替代方案
在公有云容器等生产环境中,组播通常被网络基础设施直接屏蔽。此时应切换为对应的静态或云端发现协议,例如使用 TCP-IP 静态列表组网,或在 AWS EC2 上使用 AWS 发现插件。更多详情请参阅 Hazelcast 官方文档。
5. 开启调试日志
若使用默认的 JUL 日志,可以在类路径中添加 vertx-default-jul-logging.properties 文件并配置:
com.hazelcast.level=INFO
java.util.logging.ConsoleHandler.level=INFO
java.util.logging.FileHandler.level=INFO
Hazelcast 日志框架
Hazelcast 默认采用 JDK 原生日志。若需重定向到其他日志门面(如 SLF4J),可以设置系统属性:
-Dhazelcast.logging.type=slf4j
详情请参阅 Hazelcast 日志配置官方手册。
覆盖 Hazelcast 驱动版本
你可以选用自定义版本的 Hazelcast 驱动。操作步骤为:
-
在应用类路径中引入目标版本的依赖
-
若构建 Fat Jar,需在构建脚本中排除传递依赖并显式指定新版本
Maven 示例:
<dependency>
<groupId>com.hazelcast</groupId>
<artifactId>hazelcast</artifactId>
<version>ENTER_YOUR_VERSION_HERE</version>
</dependency>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-hazelcast</artifactId>
<version>5.0.12</version>
</dependency>
Gradle 示例:
dependencies {
compile ("io.vertx:vertx-hazelcast:5.0.12"){
exclude group: 'com.hazelcast', module: 'hazelcast'
}
compile "com.hazelcast:hazelcast:ENTER_YOUR_VERSION_HERE"
}
Kubernetes 云原生容器环境配置
首要步骤是在 Hazelcast 配置中声明自动发现插件(通过自定义 cluster.xml 或代码均可,参阅 配置 Hazelcast 集群管理器)。
Hazelcast 支持两种 Kubernetes 发现模式:Kubernetes API 模式与 DNS Lookup 域名解析模式。本文推荐使用基于 DNS 解析的发现方式。
在网络配置中启用 Kubernetes 插件:
<hazelcast>
<network>
<join>
<auto-detection enabled="false"/> (1)
<multicast enabled="false"/> (2)
<kubernetes enabled="true"> (3)
<service-dns>MY-SERVICE-DNS-NAME</service-dns> (4)
</kubernetes>
</join>
</network>
</hazelcast>
| 1 | 禁用自动检测插件 |
| 2 | 禁用局域网组播发现 |
| 3 | 激活 Kubernetes 发现插件 |
| 4 | Service 的 DNS 域名,通常形如 MY-SERVICE-NAME.MY-NAMESPACE.svc.cluster.local |
|
上述配置仅包含网络发现片段,实际配置中必须同时包含 最小必需拓扑配置。 |
MY-SERVICE-DNS-NAME 对应一个 Kubernetes Headless Service 的域名,示例如下:
apiVersion: v1
kind: Service
metadata:
namespace: MY-NAMESPACE
name: MY-SERVICE-NAME
spec:
type: ClusterIP
clusterIP: None
selector:
component: MY-SERVICE-NAME (1)
ports:
- name: hazelcast
port: 5701
protocol: TCP
| 1 | 通过标签选择集群 Pod 成员 |
最后,在 Deployment 的 Pod 模板中打上对应的标签:
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
namespace: MY-NAMESPACE
spec:
template:
metadata:
labels:
component: MY-SERVICE-NAME
更多细节请查阅 Kubernetes Auto Discovery 官方文档。
滚动更新策略 (Rolling Updates)
在进行滚动更新时,推荐 逐个(One by One) 替换 Pod 实例。 为此需约束 Kubernetes 策略:
-
同一时刻最多只启动 1 个新 Pod
-
整个过程最多允许 1 个 Pod 处于不可用状态
spec:
strategy:
type: Rolling
rollingParams:
updatePeriodSeconds: 10
intervalSeconds: 20
timeoutSeconds: 600
maxUnavailable: 1 (1)
maxSurge: 1 (2)
| 1 | 更新期间允许不可用的 Pod 数量上限 |
| 2 | 允许超出目标副本数的最大并发创建 Pod 数量 |
此外,Pod 的就绪探针(Readiness Probe)必须紧密结合集群健康状态。详情请参阅 集群运维指南。
集群运维与高可用治理
Hazelcast 集群管理器将 Vert.x 节点作为 Hazelcast 集群中的节点协同运作,因此需遵循数据分区与脑裂防范准则。
数据分区迁移 (Data Partitioning)
每个 Vert.x 节点均持有集群共享数据的一部分切片(Event Bus 订阅映射、异步 Map 数据、集群计数器等)。 当有成员加入或脱离集群时,Hazelcast 会在节点间迁移并重新平衡数据分区。迁移耗时取决于集群节点数及数据体量。
脑裂现象 (Split-Brain Syndrome)
在复杂的网络基础设施中,网络分区故障在所难免,此时集群可能会被割裂为彼此失联的独立孤岛。 Hazelcast 具备在网络恢复后自动将各孤岛重新合并为统一集群的能力。但在此恢复期间,部分 Event Bus 消费者可能暂时收不到消息,高可用(HA)故障转移也可能暂时受阻。
|
在工程实践中,网络分区与以下异常现象高度相似,极易混淆: - 长时间 JVM GC 停顿(STW 导致心跳检测超时) - 部署新版本时一次性粗暴强杀大量节点 |
生产实践推荐
优雅停机 (Graceful Shutdown)
严禁在生产中强杀进程(如直接 kill -9)。优雅停机能够主动通知其余节点交接数据分区,使集群以最快速度稳定收敛。
逐节点平滑更替 (One Node After The Other)
在滚动更新、集群扩缩容时,必须 逐个(One by One) 增删节点。 逐个下线节点可避免集群误判为发生网络分区,逐个上线则允许轻量级递增迁移数据分区。
可以通过结合 Vert.x Health Checks 实现集群健康状态检查:
Handler<Promise<Status>> procedure = ClusterHealthCheck.createProcedure(vertx);
HealthChecks checks = HealthChecks.create(vertx).register("cluster-health", procedure);
然后通过 Vert.x Web 暴露为 HTTP 就绪端点供容器探针拉取:
Router router = Router.router(vertx);
router.get("/readiness").handler(HealthCheckHandler.createWithHealthChecks(checks));
采用轻量成员节点 (Lite Members)
为了将 Vert.x 集群在拓扑变更时的等待耗时降至最低,你可以引入外部专属的数据存储节点,并将 Vert.x 节点全部声明为 Lite Members(轻量成员)。
Lite Members 像常规成员一样完全参与集群通信与协调,但它们 不持有任何数据分区。因此在增删 Vert.x 节点时,Hazelcast 完全不需要执行数据分区迁移!
| 必须预先启动外部数据存储节点,因为 Hazelcast 不允许创建仅由 Lite Members 构成的纯计算集群。 |
Vert.x 节点可以在 XML 配置中声明为轻量成员:
<lite-member enabled="true"/>
也可以通过代码动态配置:
Config hazelcastConfig = ConfigUtil.loadConfig()
.setLiteMember(true);
ClusterManager mgr = new HazelcastClusterManager(hazelcastConfig);
Vertx
.builder()
.withClusterManager(mgr)
.buildClustered()
.onComplete(res -> {
if (res.succeeded()) {
Vertx vertx = res.result();
} else {
// 启动失败!
}
});