Hazelcast 集群管理器

这是 Vert.x 的一个集群管理器实现,它使用 Hazelcast

它是 Vert.x CLI 中使用的默认集群管理器,但可以由其他实现替代,因为 Vert.x 集群管理器是可插拔的。

此实现打包在

<dependency>
  <groupId>io.vertx</groupId>
  <artifactId>vertx-hazelcast</artifactId>
  <version>5.0.1</version>
</dependency>

在 Vert.x 中,集群管理器用于多种功能,包括

  • 集群中 Vert.x 节点的发现和组成员管理

  • 维护集群范围的主题订阅者列表(以便我们知道哪些节点对哪些事件总线地址感兴趣)

  • 分布式 Map 支持

  • 分布式锁

  • 分布式计数器

集群管理器处理事件总线节点间传输,这由 Vert.x 直接通过 TCP 连接完成。

使用此集群管理器

如果您通过命令行使用 Vert.x,则此集群管理器对应的 jar (名为 vertx-hazelcast-5.0.1.jar) 应该位于 Vert.x 安装目录的 lib 文件夹中。

如果您想在 Vert.x Maven 或 Gradle 项目中使用此集群管理器进行集群化,只需在您的项目中添加对工件 io.vertx:vertx-hazelcast:5.0.1 的依赖即可。

如果 JAR 包如上所示位于您的类路径中,Vert.x 将自动检测并使用它作为集群管理器。请确保您的类路径中没有其他集群管理器,否则 Vert.x 可能会选择错误的管理器。

如果您嵌入 Vert.x,也可以在创建 Vert.x 实例时通过在选项中指定来以编程方式指定集群管理器,例如

ClusterManager mgr = new HazelcastClusterManager();

Vertx
  .builder()
  .withClusterManager(mgr)
  .buildClustered()
  .onComplete(res -> {
  if (res.succeeded()) {
    Vertx vertx = res.result();
  } else {
    // failed!
  }
});

配置此集群管理器

使用 XML 配置

通常,集群管理器由一个名为 default-cluster.xml 的文件配置,该文件打包在 jar 中。

如果您想覆盖此配置,可以在您的类路径上提供一个名为 cluster.xml 的文件,它将代替默认文件使用。如果您想将 cluster.xml 文件嵌入到胖 jar 中,它必须位于胖 jar 的根目录。如果它是外部文件,则必须将包含该文件的目录添加到类路径中。例如,如果您使用 Vert.x 的 launcher 类,类路径增强可以按如下方式完成:

# If the cluster.xml is in the current directory:
java -jar ... -cp . -cluster
vertx run MyVerticle -cp . -cluster

# If the cluster.xml is in the conf directory
java -jar ... -cp conf -cluster

另一种覆盖配置的方法是通过提供系统属性 vertx.hazelcast.config 并指定一个位置:

# Use a cluster configuration located in an external file
java -Dvertx.hazelcast.config=./config/my-cluster-config.xml -jar ... -cluster

# Or use a custom configuration from the classpath
java -Dvertx.hazelcast.config=classpath:my/package/config/my-cluster-config.xml -jar ... -cluster

当存在 vertx.hazelcast.config 系统属性时,它会覆盖类路径上的任何 cluster.xml,但如果从该系统属性加载失败,则会回退到 cluster.xml 或 Hazelcast 默认配置。

Vert.x 不支持使用 -Dhazelcast.config 系统属性配置 Hazelcast,不应使用。

xml 文件是一个 Hazelcast 配置文件,Hazelcast 网站的文档中对其进行了详细描述。

以编程方式配置

如果嵌入,您可以以编程方式指定配置:

Config hazelcastConfig = new Config();

// Now set some stuff on the config (omitted)

ClusterManager mgr = new HazelcastClusterManager(hazelcastConfig);

Vertx
  .builder()
  .withClusterManager(mgr)
  .buildClustered()
  .onComplete(res -> {
  if (res.succeeded()) {
    Vertx vertx = res.result();
  } else {
    // failed!
  }
});

自定义现有 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 {
    // failed!
  }
});

ConfigUtil#loadConfig 加载 Hazelcast 配置 XML 并将其转换为 Config 对象。内容从以下位置读取:

  1. 如果存在 vertx.hazelcast.config 系统属性所表示的位置,或者

  2. 如果存在类路径上的 cluster.xml 文件,或者

  3. 默认配置文件

发现选项

Hazelcast 支持多种发现选项。默认配置使用多播,因此您的网络必须启用多播才能使其工作。

有关如何以不同方式配置发现的完整文档,请查阅 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 {
    // failed!
  }
});

在这种情况下,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 客户端或智能客户端。
请确保 Hazelcast 在 Vert.x 之前启动并在 Vert.x 之后关闭。此外,应禁用 Hazelcast 关闭钩子(参见 xml 示例,或通过系统属性禁用)。

更改故障节点的超时时间

默认情况下,如果 Hazelcast 在 60 秒内没有收到心跳,节点将被从集群中移除。要更改此值,请使用 hazelcast.max.no.heartbeat.seconds 系统属性,例如:

-Dhazelcast.max.no.heartbeat.seconds=5

此后,如果节点在 5 秒内没有心跳,它将被从集群中移除。

请参阅 Hazelcast 系统属性

集群故障排除

如果默认的多播配置不起作用,以下是一些常见原因:

机器上未启用多播。

在 OSX 机器上,多播默认被禁用是很常见的。请搜索有关如何启用此功能的信息。

使用错误的网络接口

如果您的机器上有多个网络接口(如果您在机器上运行 VPN 软件也可能如此),那么 Hazelcast 可能会使用错误的接口。

要告诉 Hazelcast 使用特定接口,您可以在配置的 interfaces 元素中提供接口的 IP 地址。请确保将 enabled 属性设置为 true。例如:

<interfaces enabled="true">
  <interface>192.168.1.20</interface>
</interfaces>

使用 VPN

这是上述情况的一个变体。VPN 软件通常通过创建一个虚拟网络接口来工作,该接口通常不支持多播。如果您运行着 VPN 并且没有在 hazelcast 配置和 Vert.x 中指定要使用的正确接口,则可能会选择 VPN 接口而不是正确的接口。

因此,如果您运行着 VPN,您可能需要按照上一节中的描述,配置 Hazelcast 和 Vert.x 都使用正确的接口。

当多播不可用时

在某些情况下,您可能无法使用多播,因为它在您的环境中可能不可用。在这种情况下,您应该配置其他传输,例如使用 TCP 套接字的 TCP,或在 Amazon EC2 上运行时使用 AWS。

有关可用 Hazelcast 传输及其配置方法的更多信息,请查阅 Hazelcast 文档。

启用日志记录

在对 Hazelcast 集群问题进行故障排除时,通常获取 Hazelcast 的一些日志输出来查看其是否正确形成集群会很有用。您可以通过在类路径中添加一个名为 vertx-default-jul-logging.properties 的文件来完成此操作(当使用默认 JUL 日志记录时)。这是一个标准的 java.util.logging (JUL) 配置文件。在其中设置:

com.hazelcast.level=INFO

并且

java.util.logging.ConsoleHandler.level=INFO
java.util.logging.FileHandler.level=INFO

Hazelcast 日志记录

Hazelcast 使用的日志后端默认为 jdk(即 JUL)。如果您想将日志重定向到其他库,您需要设置 hazelcast.logging.type 系统属性,例如:

-Dhazelcast.logging.type=slf4j

有关更多详细信息,请参阅 hazelcast 文档

使用不同版本的 Hazelcast

您可能希望使用不同版本的 Hazelcast。默认版本是 ${hazelcast.version}。为此,您需要:

  • 将您想要的版本放入应用程序类路径中

  • 如果您正在运行胖 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.1</version>
</dependency>

根据版本,您可能需要排除一些传递依赖。

在 Gradle 上,您可以使用以下方法实现相同的重载:

dependencies {
 compile ("io.vertx:vertx-hazelcast:5.0.1"){
   exclude group: 'com.hazelcast', module: 'hazelcast'
 }
 compile "com.hazelcast:hazelcast:ENTER_YOUR_VERSION_HERE"
}

Kubernetes 配置

第一步是在您的 Hazelcast 配置中配置发现插件,通过提供自定义的 cluster.xml 文件或以编程方式,如配置此集群管理器中所述。

发现模式Kubernetes APIDNS Lookup。请参考插件项目页面了解两种模式的优缺点。

在本文档中,我们将使用 DNS Lookup 发现。以下属性必须更改/添加:

<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 服务 DNS,通常形式为 MY-SERVICE-NAME.MY-NAMESPACE.svc.cluster.local,但取决于 Kubernetes 分发版本

上述配置摘录仅包含网络相关设置。实际配置必须包含最低所需设置

MY-SERVICE-DNS-NAME 的值必须是一个无头 Kubernetes 服务名称,Hazelcast 将使用它来识别所有集群成员。无头服务可以通过以下方式创建:

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 按标签选择集群成员

最后,将 component 标签附加到所有应属于集群的部署:

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  namespace: MY-NAMESPACE
spec:
  template:
    metadata:
      labels:
        component: MY-SERVICE-NAME

更多配置详情可在 Kubernetes 自动发现页面上找到。

滚动更新

在滚动更新期间,建议逐个替换 Pod。

为此,我们必须配置 Kubernetes 以:

  • 绝不同时启动超过一个新 Pod

  • 在过程中禁止超过一个不可用 Pod

spec:
  strategy:
    type: Rolling
    rollingParams:
      updatePeriodSeconds: 10
      intervalSeconds: 20
      timeoutSeconds: 600
      maxUnavailable: 1 (1)
      maxSurge: 1 (2)
1 在更新过程中可以不可用的 Pod 的最大数量
2 可以创建的 Pod 数量超过所需 Pod 数量的最大值

此外,Pod 的就绪探针必须考虑集群状态。有关如何使用 Vert.x 健康检查实现就绪探针的详细信息,请参阅集群管理部分。

集群管理

Hazelcast 集群管理器通过将 Vert.x 节点转换为 Hazelcast 集群的成员来工作。因此,Vert.x 集群管理器管理应遵循 Hazelcast 管理指南。

首先,让我们退一步介绍数据分区和脑裂现象。

数据分区

每个 Vert.x 节点都持有部分集群数据:事件总线订阅、异步映射条目、集群计数器等。

当成员加入或离开集群时,Hazelcast 会迁移数据分区。换句话说,它会移动数据以适应新的集群拓扑。这个过程可能需要一些时间,具体取决于集群数据量和节点数量。

脑裂现象

在一个完美的世界里,不会有网络设备故障。然而,现实是,您的集群迟早会分裂成更小的组,彼此无法看到。

Hazelcast 能够将节点重新合并成一个单一的集群。但与数据分区迁移一样,这个过程可能需要一些时间。在集群完全恢复功能之前,一些事件总线消费者可能无法接收消息。或者高可用性可能无法重新部署失败的 verticle。

很难(如果可能的话)区分网络分区和:

  • 长时间的 GC 暂停(导致错过心跳检查),

  • 由于部署应用程序新版本,一次性大量节点被强制杀死。

建议

考虑到上面讨论的常见集群问题,建议遵循以下良好实践。

优雅关机

避免强制停止成员(例如,kill -9 一个节点)。

当然,进程崩溃是不可避免的,但优雅地关闭有助于让剩余节点更快地进入稳定状态。

一个接一个地

当滚动新版本的应用程序、扩展或缩减集群时,请一个接一个地添加或移除节点。

逐个停止节点可以防止集群误认为发生了网络分区。逐个添加节点可以实现干净、增量的数据分区迁移。

集群安全可以通过 Vert.x 健康检查进行验证。

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)像普通成员一样参与 Hazelcast 集群,但它们不拥有任何数据分区。因此,当添加或移除此类成员时,Hazelcast 不需要迁移分区。

您必须事先启动外部数据节点,因为 Hazelcast 不会只由 精简成员(Lite Members)构成集群。

要启动外部节点,您可以使用 Hazelcast 分发启动脚本,或以编程方式进行。

Vert.x 节点可以在 XML 配置中标记为 精简成员(Lite Members)

<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 {
    // failed!
  }
});