Infinispan 集群管理器

这是一个为 Vert.x 使用 Infinispan 的集群管理器实现。

这个实现被打包在

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

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

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

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

  • 分布式 Map 支持

  • 分布式锁

  • 分布式计数器

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

使用此集群管理器

如果您从命令行使用 Vert.x,则此集群管理器对应的 JAR 包(它将被命名为 vertx-infinispan-5.0.1.jar)应位于 Vert.x 安装的 lib 目录中。

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

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

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

ClusterManager mgr = new InfinispanClusterManager();

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

配置此集群管理器

默认的集群管理器配置可以通过 infinispan.xml 和/或 jgroups.xml 文件进行修改。前者配置数据网格,后者配置组管理和成员发现。

您可以将其中一个或两个文件放在您的类路径中。如果您想将自定义文件嵌入到 fat jar 中,它必须位于 fat jar 的根目录。如果它是外部文件,则必须将包含该文件的**目录**添加到类路径中。例如,如果您正在使用 Vert.x 的 launcher 类,可以通过以下方式增强类路径:

# If infinispan.xml and/or jgroups.xml files are in the current directory:
java -jar my-app.jar -cp . -cluster

# If infinispan.xml and/or jgroups.xml files are in the conf directory:
java -jar my-app.jar -cp conf -cluster

覆盖配置的另一种方法是通过系统属性提供文件位置:vertx.infinispan.config 和/或 vertx.jgroups.config

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

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

集群管理器将首先在类路径中搜索文件,然后回退到文件系统。

系统属性(如果存在)将覆盖类路径中的任何 infinispan.xmljgroups.xml

这些 XML 文件是 Infinispan 和 JGroups 的配置文件,在 Infinispan 和 JGroups 网站的文档中有详细描述。

如果 jgroups.xml 文件在类路径中,或者您设置了 vertx.jgroups.config 系统属性,它将覆盖 Infinispan 配置文件中定义的任何 JGroups stack-file 路径。

默认的 JGroups 配置使用多播进行发现,使用 TCP 进行组管理。请确保您的网络上启用了多播以使其正常工作。

有关如何不同地配置传输或使用不同传输的完整文档,请查阅 Infinispan / JGroups 文档。

使用现有的 Infinispan 缓存管理器

您可以在集群管理器中传递一个现有的 DefaultCacheManager 以重用现有的缓存管理器

ClusterManager mgr = new InfinispanClusterManager(cacheManager);

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

在这种情况下,Vert.x 不是缓存管理器的所有者,因此在关闭时不会将其关闭。

请注意,自定义 Infinispan 实例需要配置为

<cache-container default-cache="distributed-cache">
  <distributed-cache name="distributed-cache"/>
  <replicated-cache name="__vertx.subs"/>
  <replicated-cache name="__vertx.haInfo"/>
  <replicated-cache name="__vertx.nodeInfo"/>
  <distributed-cache-configuration name="__vertx.distributed.cache.configuration"/>
</cache-container>

打包可执行的 uber JAR

Infinispan 使用 Java 的 ServiceLoader 机制在运行时发现一些类的实现。在创建可执行的 uber JAR(也称为 "fat" JAR)时,您必须配置您的构建工具以合并服务描述符文件。

如果您使用 Maven 和 Maven Shade Plugin,插件配置应如下所示:

<configuration>
  <transformers>
    <transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
    <!-- ... -->
  </transformers>
  <!-- ... -->
</configuration>

如果您使用 Gradle 和 Gradle Shadow Plugin

shadowJar {
  mergeServiceFiles()
}

此外,Infinispan 依赖于 多版本 JAR 文件(Multi-Release JAR Files)来允许某些类的多个、特定于 Java 版本的版本。因此,可执行 uber JAR 的清单文件必须包含以下条目

Multi-Release: true

为 Kubernetes 配置

在 Kubernetes 上,JGroups 可以配置为使用 Kubernetes API (KUBE_PING) 或 DNS (DNS_PING) 进行发现。本文档中,我们将使用 DNS 发现。

首先,通过系统属性强制 JVM 使用 IPv4。

-Djava.net.preferIPv4Stack=true

然后,将 vertx.jgroups.config 系统属性设置为 default-configs/default-jgroups-kubernetes.xml。此 JGroups 栈文件位于 infinispan-core JAR 中,并已为 Kubernetes 预配置。

-Dvertx.jgroups.config=default-configs/default-jgroups-kubernetes.xml

另外,设置 JGroups DNS 查询以查找成员。

-Djgroups.dns.query=MY-SERVICE-DNS-NAME

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

apiVersion: v1
kind: Service
metadata:
  name: clustered-app
spec:
  selector:
    cluster: clustered-app (2)
  ports:
    - name: jgroups
      port: 7800 (1)
      protocol: TCP
  publishNotReadyAddresses: true (3)
  clusterIP: None
1 JGroups TCP 端口
2 cluster=clustered-app 标签选择的集群成员
3 设置为 true,以便在不干扰您的就绪探测逻辑的情况下发现成员

最后,将 cluster=clustered-app 标签应用于所有应属于集群的部署

apiVersion: apps/v1
kind: Deployment
spec:
  template:
    metadata:
      labels:
        cluster: clustered-app

滚动更新

在滚动更新期间,Infinispan 团队建议逐个替换 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 健康检查实现就绪探测的详细信息,请参阅集群管理部分。

为 Docker Compose 配置

确保使用这些系统属性启动 Java 虚拟机

-Djava.net.preferIPv4Stack=true -Djgroups.bind.address=NON_LOOPBACK

这将使 JGroups 选择 Docker 创建的虚拟专用网络的接口。

集群故障排除

如果默认的多播发现配置不起作用,这里有一些常见原因

机器上未启用多播。

在 OSX 机器上,多播默认被禁用的情况非常常见。请搜索相关信息以了解如何启用它。

使用错误的网络接口

如果您的机器上有多个网络接口(如果您正在运行 VPN 软件,也可能出现这种情况),那么 JGroups 可能会使用错误的接口。

要告诉 JGroups 使用特定接口,您可以在配置的 bind_addr 元素中提供该接口的 IP 地址。例如

<TCP bind_addr="192.168.1.20"
     ...
     />
<MPING bind_addr="192.168.1.20"
     ...
     />

另外,如果您想坚持使用捆绑的 jgroups.xml 文件,可以设置 jgroups.bind.address 系统属性。

-Djgroups.bind.address=192.168.1.20

当 Vert.x 运行在集群模式下时,您还应该确保 Vert.x 知道正确的接口。在命令行运行时,通过指定 cluster-host 选项来完成此操作

vertx run myverticle.js -cluster -cluster-host your-ip-address

其中 your-ip-address 是您在 JGroups 配置中指定的相同 IP 地址。

如果以编程方式使用 Vert.x,您可以使用 setHost 来指定。

使用 VPN

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

因此,如果您正在运行 VPN,您可能需要按照上一节所述,配置 JGroups 和 Vert.x 以使用正确的接口。

当多播不可用时

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

有关可用 JGroups 发现协议以及如何配置它们的更多信息,请查阅 JGroups 文档

IPv6 问题

如果您在配置 IPv6 主机时遇到问题,请使用 java.net.preferIPv4Stack 系统属性强制使用 IPv4。

-Djava.net.preferIPv4Stack=true

启用日志记录

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

org.infinispan.level=INFO
org.jgroups.level=INFO

并且

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

Infinispan 日志记录

Infinispan 依赖于 JBoss logging。JBoss Logging 是一个日志桥接,提供与众多日志框架的集成。

将您选择的日志 JAR 添加到类路径中,JBoss Logging 将自动识别它们。

如果您的类路径中有多个日志后端,您可以使用 org.jboss.logging.provider 系统属性强制选择。例如

-Dorg.jboss.logging.provider=log4j2

有关更多详细信息,请参阅此 JBoss Logging 指南

JGroups 日志记录

JGroups 默认使用 JDK 日志记录。如果类路径中找到相应的 JAR,则支持 log4j 和 log4j2。

如果您需要更多详细信息或想实现自己的日志后端,请参阅 JGroups 日志记录文档

SharedData 扩展

AsyncMap 内容流

InfinispanAsyncMap API 允许以流的形式检索键、值和条目。如果您需要遍历大型映射的内容进行批量处理,这会很有用。

InfinispanAsyncMap<K, V> infinispanAsyncMap = InfinispanAsyncMap.unwrap(asyncMap);
ReadStream<K> keyStream = infinispanAsyncMap.keyStream();
ReadStream<V> valueStream = infinispanAsyncMap.valueStream();
ReadStream<Map.Entry<K, V>> entryReadStream = infinispanAsyncMap.entryStream();

集群管理

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

首先,让我们退一步,介绍一下再平衡(rebalancing)和脑裂(split-brain syndrome)。

再平衡

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

当成员加入或离开集群时,Infinispan 会在新成员集合上重新平衡缓存条目。换句话说,它会移动数据以适应新的集群拓扑。这个过程可能需要一些时间,具体取决于集群数据量和节点数量。

脑裂(Split-brain syndrome)

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

Infinispan 能够将节点合并回单个集群。但就像再平衡一样,这个过程可能需要一些时间。在集群再次完全正常运行之前,一些事件总线消费者可能无法接收消息。或者高可用性可能无法重新部署失败的 verticle。

区分网络分区和以下情况是很困难的(如果可能的话):

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

  • 由于您部署了新版本的应用程序,导致许多节点同时被强制终止

建议

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

优雅关机

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

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

逐个节点

在推出应用程序新版本、扩展或缩减集群时,请逐个添加或删除节点。

逐个停止节点可以防止集群误认为发生了网络分区。逐个添加它们则可以实现干净、增量的再平衡操作。

集群的健康状况可以通过 Vert.x 健康检查进行验证。

Handler<Promise<Status>> procedure = ClusterHealthCheck.createProcedure(vertx, true);
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));