Hazelcast 集群管理器
Vert.x 基于 Hazelcast 实现了一个集群管理器。
这是 Vert.x CLI默认的集群管理器。由于 Vert.x 集群管理器的可插拔性,可轻易切换至其它的集群管理器。
这个集群管理器由以下依赖引入:
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-hazelcast</artifactId>
<version>4.3.8</version>
</dependency>
Vert.x 集群管理器包含以下几项功能:
-
发现并管理集群中的节点
-
管理集群的 EventBus 地址订阅清单(这样就可以轻松得知集群中的哪些节点订阅了哪些 EventBus 地址)
-
分布式 Map 支持
-
分布式锁
-
分布式计数器
Vert.x 集群器 并不 处理节点之间的通信。在 Vert.x 中,集群节点间通信是直接由 TCP 连接处理的。
使用集群管理器
如果通过命令行来使用 Vert.x,对应集群管理器的 jar
包(名为 vertx-hazelcast-4.3.8.jar
)
应该在 Vert.x 中安装路径的 lib
目录中。
如果在 Maven 或者 Gradle 工程中使用 Vert.x, 只需要在工程依赖中加上依赖:
io.vertx:vertx-hazelcast:4.3.8
。
如果(集群管理器的)jar 包在 classpath 中,Vert.x将自动检测到并将其作为集群管理器。 需要注意的是,要确保 Vert.x 的 classpath 中没有其它的集群管理器实现, 否则会使用错误的集群管理器。
编程内嵌 Vert.x 时,可以在创建 Vert.x 实例时, 通过编程的方式显式配置 Vert.x 集群管理器,例如:
ClusterManager mgr = new HazelcastClusterManager();
VertxOptions options = new VertxOptions().setClusterManager(mgr);
Vertx.clusteredVertx(options, res -> {
if (res.succeeded()) {
Vertx vertx = res.result();
} else {
// 失败
}
});
配置集群管理器
使用XML文件配置
通常情况下,集群管理器的相关配置默认是通过打包在jar中的配置文件
default-cluster.xml
配置的。
如果要覆盖此配置,可以在 classpath 中添加一个 cluster.xml
文件。如果想在 fat jar 中内嵌 cluster.xml
,此文件必须在 fat jar 的根目录中。如果此文件是一个外部文件,则必须将其所在的 目录 添加至 classpath 中。举个例子,如果使用 Vert.x 的 launcher 启动应用,则 classpath 应该设置为:
# 如果 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
# 从 classpath 中加载一个文件为自定义配置文件
java -Dvertx.hazelcast.config=classpath:my/package/config/my-cluster-config.xml -jar ... -cluster
如果 vertx.hazelcast.config
值不为空时,将用其覆盖 classpath 中所有的 cluster.xml
文件;
但是如果加载 vertx.hazelcast.config
系统配置失败时,
系统将选取 classpath 任意一个 cluster.xml
,甚至直接使用默认配置。
小心
|
Vert.x 并不支持 -Dhazelcast.config 设置方式,
请不要使用。
|
这里的 xml 是 Hazelcast 的配置文件, 可以在 Hazelcast 官网找到详细的配置文档。
通过编程配置
您也可以通过编程的形式配置集群管理器:
Config hazelcastConfig = new Config();
// 设置相关的hazlcast配置,在这里省略掉,不再赘述
ClusterManager mgr = new HazelcastClusterManager(hazelcastConfig);
VertxOptions options = new VertxOptions().setClusterManager(mgr);
Vertx.clusteredVertx(options, res -> {
if (res.succeeded()) {
Vertx vertx = res.result();
} else {
// 失败!
}
});
您也可以对已存在的XML配置进行修改。 比如修改集群名:
Config hazelcastConfig = ConfigUtil.loadConfig();
hazelcastConfig.setClusterName("my-cluster-name");
ClusterManager mgr = new HazelcastClusterManager(hazelcastConfig);
VertxOptions options = new VertxOptions().setClusterManager(mgr);
Vertx.clusteredVertx(options, res -> {
if (res.succeeded()) {
Vertx vertx = res.result();
} else {
// 失败!
}
});
ConfigUtil#loadConfig
方法会加载 Hazelcast 的XML配置文件,并将其转换为 Config
对象。
读取的XML配置文件来自:
-
vertx.hazelcast.config
系统配置指定的文件,若不存在则 -
classpath 内的
cluster.xml
文件,若不存在则 -
默认的配置文件
使用已存在的 Hazelcast 集群
可以向集群管理器传入 HazelcastInstance
来复用现有集群:
ClusterManager mgr = new HazelcastClusterManager(hazelcastInstance);
VertxOptions options = new VertxOptions().setClusterManager(mgr);
Vertx.clusteredVertx(options, res -> {
if (res.succeeded()) {
Vertx vertx = res.result();
} else {
// 失败!
}
});
在这种情况下,Vert.x不是 Hazelcast 集群的所有者,所以不要关闭 Vert.x 时关闭 Hazlecast 集群。
请注意,自定义 Hazelcast 实例需要以下配置:
<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>
<cp-subsystem>
<cp-member-count>0</cp-member-count>
<semaphores>
<semaphore>
<name>__vertx.*</name>
<jdk-compatible>false</jdk-compatible>
<initial-permits>1</initial-permits>
</semaphore>
</semaphores>
</cp-subsystem>
重要
|
不支持 Hazelcast 客户端及智能客户端。 |
重要
|
要确保 Hazelcast 集群 先于 Vert.x 集群启动,后于 Vert.x 集群关闭。
同时需要禁用 shutdown hook (参考上述的 xml 配置,或通过系统变量来实现)。
|
修改故障节点的超时配置
缺省情况下,Hazelcast 会移除集群中超过300秒没收到心跳的节点。
通过系统配置 hazelcast.max.no.heartbeat.seconds
可以修改这个超时时间,如:
-Dhazelcast.max.no.heartbeat.seconds=5
修改后,超过5秒没发出心跳的节点会被移出集群。
请参考 Hazelcast 系统配置 。
集群故障排除
如果默认的组播配置不能正常运行,通常有以下原因:
使用错误的网络接口
如果机器上有多个网络接口(也有可能是在运行 VPN 的情况下), 那么 Hazelcast 很有可能使用错误的网络接口。
为了确保 Hazelcast 使用正确的网络接口,在配置文件中将 interface
设置为指定IP地址。
同时确保 enabled
属性设置为 true
。例如:
<interfaces enabled="true"> <interface>192.168.1.20</interface> </interfaces>
使用VPN
VPN 软件工作时通常会创建虚拟网络接口,但往往不支持组播。 在 VPN 环境中,如果 Hazelcast 与 Vert.x 没有配置正确的话, 将会选择 VPN 创建的网络接口,而不是正确的网络接口。
所以,如果您的应用运行在 VPN 环境中,请参考上述章节, 设置正确的网络接口。
Hazelcast 日志配置
Hazelcast 的日志默认采用 JDK
的实现(即 JUL)。
如果想切换至其他日志库,通过设置系统配置 hazelcast.logging.type
即可:
-Dhazelcast.logging.type=slf4j
详细文档请参考 hazelcast 文档 。
使用其他 Hazelcast 版本
当前的 Vert.x HazelcastClusterManager 使用的 Hazelcast 版本为 4.2.7
。
如果开发者想使用其他版本的 Hazelcast,需要做以下工作:
-
将目标版本的 Hazelcast 依赖添加至 classpath 中
-
如果是 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>4.3.8</version>
</dependency>
对于某些版本,您可能需要排除掉一些(冲突的)依赖。
对于 Gradle 可以使用下面代码:
dependencies {
compile ("io.vertx:vertx-hazelcast:4.3.8"){
exclude group: 'com.hazelcast', module: 'hazelcast'
}
compile "com.hazelcast:hazelcast:ENTER_YOUR_VERSION_HERE"
}
配置 Kubernetes
Kubernetes 上的 Hazelcast 要配置为使用 Hazelcast Kubernetes 插件。
首先在项目中增加依赖: io.vertx:vertx-hazelcast:${vertx.version}
和 com.hazelcast:hazelcast-kubernetes:${hazelcast-kubernetes.version}
。
对于 Maven,参考下面代码:
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-hazelcast</artifactId>
<version>${vertx.version}</version>
</dependency>
<dependency>
<groupId>com.hazelcast</groupId>
<artifactId>hazelcast-kubernetes</artifactId>
<version>${hazelcast-kubernetes.version}</version>
</dependency>
注意
|
如果您使用了其他版本的 Hazelcast core 依赖,请确保兼容 Kubernetes discovery 插件。 |
然后在 Hazelcast 配置中配置 Kubernetes discovery 插件,可以通过自定义的 cluster.xml
文件进行配置,或通过编程方式配置(参考 配置集群管理器)。
Kubernetes discovery 插件提供了两种可选的 发现模式: Kubernetes API 和 DNS Lookup 。 关于这两种模式的利弊,请参阅该插件的项目网站。
在本文中,我们使用 DNS Lookup 发现模式。请修改/增加以下的配置:
<hazelcast>
<properties>
<property name="hazelcast.discovery.enabled">true</property> (1)
</properties>
<network>
<join>
<multicast enabled="false"/> (2)
<tcp-ip enabled="false" />
<discovery-strategies>
<discovery-strategy enabled="true"> (3)
class="com.hazelcast.kubernetes.HazelcastKubernetesDiscoveryStrategy">
<properties>
<property name="service-dns">MY-SERVICE-DNS-NAME</property> (4)
</properties>
</discovery-strategy>
</discovery-strategies>
</join>
</network>
</hazelcast>
-
启用 Discovery SPI
-
停用其他发现模式
-
启用 Kubernetes 插件
-
服务DNS,通常以
MY-SERVICE-NAME.MY-NAMESPACE.svc.cluster.local
的形式出现,视乎 Kubernetes 的分布配置
MY-SERVICE-DNS-NAME
的取值必须是 Kubernetes 的一个 无头 服务(Headless Services),Hazelcast 将用其识别所有集群成员节点。
无头服务的创建配置可参考下面代码:
apiVersion: v1
kind: Service
metadata:
namespace: MY-NAMESPACE
name: MY-SERVICE-NAME
spec:
selector:
component: MY-SERVICE-NAME (1)
clusterIP: None
ports:
- name: hz-port-name
port: 5701
protocol: TCP
-
按标签选择的集群成员
最终,属于集群的所有 Kubernetes 部署需要追加 component
标签:
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
namespace: MY-NAMESPACE
spec:
template:
metadata:
labels:
component: MY-SERVICE-NAME
更多关于配置的详情请参考 Hazelcast Kubernetes 插件页面。
滚动更新
在滚动更新期间,建议逐一更换 Pod。
为此,我们必须将 Kubernetes 配置为:
-
不要同时启动多个新 Pod
-
在滚动更新过程中,不可用的 Pod 不能多于一个
spec:
strategy:
type: Rolling
rollingParams:
updatePeriodSeconds: 10
intervalSeconds: 20
timeoutSeconds: 600
maxUnavailable: 1 (1)
maxSurge: 1 (2)
-
在升级过程中允许 不可用的最大 Pod 数
-
允许超过预期创建数量的最大 Pod 数(译者注:即,实际创建的 Pod 数量 ≤ 预期 Pod 数量 + maxSurge)
同样地,Pod 的准备情况探针(readiness probe)必须考虑集群状态。 请参阅 集群管理 章节,了解如何使用 Vert.x 健康检查 实现准备情况探针。
集群管理
Hazelcast 集群管理器的工作原理是将 Vert.x 节点作为 Hazelcast 集群的成员。 因此,Vert.x 使用 Hazelcast 集群管理器时,应遵循 Hazelcast 的管理准则。
首先介绍下分区数据和脑裂。
分区数据
每个 Vert.x 节点都包含部分集群数据,包括:EventBus 订阅,异步 Map,分布式计数器等等。
当有节点加入或离开集群时,Hazelcast 会迁移分区数据。 换句话说,它可以移动数据以适应新的集群拓扑。 此过程可能需要一些时间,具体取决于集群数据量和节点数量。
脑裂
在理想环境中,不会出现网络设备故障。 实际上,集群早晚会被分成多个小组,彼此之间不可见。
Hazelcast 能够将节点合并回单个集群。 但是,就像数据分区迁移一样,此过程可能需要一些时间。 在集群变回可用之前,某些 EventBus 的消费者可能无法获取到消息。 否则,重新部署故障的 Verticle 过程中无法保证高可用。
注意
|
很难(或者说基本不可能)区分脑裂和:
|
建议
考虑到上面讨论的常见集群问题,建议遵循下述的最佳实践。
逐个添加或移除节点
滚动更新新版本应用时,或扩大/缩小集群时,应该一个接一个地添加或移除节点。
逐个停止节点可避免集群误以为发生了脑裂。 逐个添加节点可以进行干净的增量数据分区迁移。
可以使用 Vert.x 运行状况检查 来验证集群安全性:
Handler<Promise<Status>> procedure = ClusterHealthCheck.createProcedure(vertx);
HealthChecks checks = HealthChecks.create(vertx).register("cluster-health", procedure);
完成集群创建后,可以通过 Vert.x Web 路由 Handler 编写的HTTP程序进行健康检查:
Router router = Router.router(vertx);
router.get("/readiness").handler(HealthCheckHandler.createWithHealthChecks(checks));
使用轻量级成员(Lite Members)
为了尽量减少 Vert.x 集群适应新拓扑的时间,您可以使用外部数据节点,并将 Vert.x 节点标记为 轻量级成员。
轻量级成员 像普通成员一样加入 Hazelcast 集群,但是他们不拥有任何数据分区。 因此,添加或删除此类成员时,Hazelcast 不需要迁移数据分区。
重要
|
您必须事先启动外部数据节点,因为 Hazelcast 不会只使用 轻量级成员 节点创建集群。 |
启动外部数据节点可以使用 Hazelcast 分发启动脚本,或以编程方式进行。
可以在XML配置中将Vert.x节点标记为 轻量级成员 节点:
<lite-member enabled="true"/>
还可以通过编程实现:
Config hazelcastConfig = ConfigUtil.loadConfig()
.setLiteMember(true);
ClusterManager mgr = new HazelcastClusterManager(hazelcastConfig);
VertxOptions options = new VertxOptions().setClusterManager(mgr);
Vertx.clusteredVertx(options, res -> {
if (res.succeeded()) {
Vertx vertx = res.result();
} else {
// failed!
}
});