Infinispan 集群管理器
本项目基于 Infinispan 实现了一个集群管理器。
这个集群管理器的实现由以下依赖引入:
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-infinispan</artifactId>
<version>4.0.3</version>
</dependency>
Vert.x 集群管理器包含以下几项功能:
-
发现并管理集群中的节点
-
管理集群的 EventBus 地址订阅清单(这样就可以轻松得知集群中的哪些节点订阅了哪些 EventBus 地址)
-
分布式 Map 支持
-
分布式锁
-
分布式计数器
Vert.x 集群器 并不 处理节点之间的通信。在 Vert.x 中,集群节点间通信是直接由 TCP 连接处理的。
使用集群管理器
如果通过命令行来使用 Vert.x,对应集群管理器的 jar
包(名为 vertx-infinispan-4.0.3.jar
)
应该在 Vert.x 中安装路径的 lib
目录中。
若您希望在集群中使用此集群管理器,只需要在您的Vert.x Maven 或 Gradle 工程中添加依赖:
io.vertx:vertx-infinispan:4.0.3
。
如果(集群管理器的)jar 包在 classpath 中,Vert.x将自动检测到并将其作为集群管理器。 需要注意的是,要确保 Vert.x 的 classpath 中没有其它的集群管理器实现, 否则会使用错误的集群管理器。
编程内嵌 Vert.x 可以在创建 Vert.x 实例时, 通过代码显式配置 Vert.x 集群管理器,例如:
ClusterManager mgr = new InfinispanClusterManager();
VertxOptions options = new VertxOptions().setClusterManager(mgr);
Vertx.clusteredVertx(options, res -> {
if (res.succeeded()) {
Vertx vertx = res.result();
} else {
// 失败!
}
});
配置集群管理器
默认的集群管理器配置可以通过 infinispan.xml
及/或 jgroups.xml
文件进行修改。
前者配置数据网格(data grid),后者配置组管理和集群成员发现。
您可以在 classpath 中替换这些配置文件(其中一个或两个)。 如果想在 fat jar 中内嵌自己的配置文件,这些文件必须在 fat jar 的根目录中。 如果这些配置文件是外部文件,则必须将其所在的 目录 添加至 classpath 中。 举个例子,如果使用 Vert.x 的 launcher 类启动应用,则 classpath 应该设置为:
# 如果 infinispan.xml 及/或 jgroups.xml 在当前目录:
java -jar my-app.jar -cp . -cluster
# 如果 infinispan.xml 及/或 jgroups.xml 在 conf 目录:
java -jar my-app.jar -cp conf -cluster
还可以通过配置系统属性 vertx.infinispan.config
及/或 vertx.jgroups.config
覆盖默认配置、指定配置文件:
# 指定一个外部文件为自定义配置文件
java -Dvertx.infinispan.config=./config/my-infinispan.xml -jar ... -cluster
# 或从 classpath 中加载一个文件为自定义配置文件
java -Dvertx.infinispan.config=my/package/config/my-infinispan.xml -jar ... -cluster
集群管理器优先在 classpath 查找指定的配置文件,如果没有找到,则在文件系统中查找指定的配置文件。
如果设置了上述系统属性,则会覆盖 classpath 中的 infinispan.xml
或 jgroups.xml
文件。
jgroup.xml
与 infinispan.xml
分别是 JGroups 、 Infinispan 配置文件。在对应的官方可以网站可以详细的配置攻略。
重要
|
如果 classpath 中包含 jgroups.xml 文件,同时也设置了 vertx.jgroups.config 系统属性,
那么 Infinispan 配置中所有的 JGroups 的 stack-file 路径配置会被 jgroups.xml 中的覆盖。
|
在默认的 JGroups 配置中,节点发现使用组播,组管理使用 TCP 。 请确认您的网络支持组播。
有关如何配置或使用其他传输方式的完整文档, 请查询 Infinispan 或 JGroups 文档。
使用已有的 Infinispan 缓存管理器
构造集群管理器时,传入已有的 DefaultCacheManager
可以复用已有的缓存管理器。
ClusterManager mgr = new InfinispanClusterManager(cacheManager);
VertxOptions options = new VertxOptions().setClusterManager(mgr);
Vertx.clusteredVertx(options, res -> {
if (res.succeeded()) {
Vertx vertx = res.result();
} else {
// 失败!
}
});
在这种情况下,Vert.x 并不是缓存管理器的所有者,因此不能在关闭 Vert.x 时停止 Infinispan 。
请注意,可通过如下配置以定制 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>
打包可执行的 fat-jar 包
Infinispan 在运行时使用 Java SPI 机制(ServiceLoader
) 发现一些类/接口的实现。
构建可执行的超级 JAR(也称为 fat-JAR )包时,必须在构建工具中配置将服务描述文件打包。
如果您使用 Maven 及 Maven Shade 插件,插件应该配置如下:
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
<!-- ... -->
</transformers>
<!-- ... -->
</configuration>
如果您使用 Gradle 及 Gradle Shadow 插件:
shadowJar {
mergeServiceFiles()
}
适配 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 的 stack-file 在 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
的取值必须是一个 Kubernetes 无头 服务(Headless Service) 名,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
-
JGroups TCP 端口
-
由
cluster=clustered-app
标签选择的集群成员 -
设置为true,则可以在不干涉就绪探针(readiness probe)逻辑的前提下,发现集群成员
最后,属于集群的所有 Kubernetes 部署需要增加 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)
-
在升级过程中允许 不可用的最大 Pod 数
-
允许超过预期创建数量的最大 Pod 数(译者注:即,实际创建的 Pod 数量 ≤ 预期 Pod 数量 + maxSurge)
同样地,Pod 的就绪探针(readiness probe)必须考虑集群状态。 请参阅 集群管理 章节,了解如何使用 Vert.x 健康检查 实现准备情况探针。
适配 Docker Compose
确认 JVM 在启动时 设置了下面的配置:
-Djava.net.preferIPv4Stack=true -Djgroups.bind.address=NON_LOOPBACK
通过上述两项系统配置,JGroups 才能正确地选择 Docker 创建的虚拟网络接口。
集群故障排查
如果默认的组播配置不能正常运行,通常有以下原因:
使用错误的网络接口
如果机器上有多个网络接口(也有可能是在运行 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
系统属性来指定 JGroups 的网络接口:
-Djgroups.bind.address=192.168.1.20
Vert.x 运行在集群模式时,必须确保 Vert.x 获取到正确的网络接口。
在 Vert.x 命令行模式下,可以通过 cluster-host
选项指定集群的网络接口:
vertx run myverticle.js -cluster -cluster-host your-ip-address
其中 your-ip-address
与 JGroups 配置中指定的IP地址一致。
若使用编码的方式启动 Vert.x,可以通过 .setHost(java.lang.String)
设置集群的网络接口。
使用VPN
使用VPN是上述问题的变种。 VPN 软件工作时通常会创建虚拟网络接口,但往往不支持组播。 在 VPN 环境中,如果 JGroups 与 Vert.x 没有配置正确的话, 将会选择 VPN 创建的网络接口,而不是正确的网络接口。
所以,如果您的应用运行在 VPN 环境中,请参考上述章节, 设置正确的网络接口。
组播不可用
在某些情况下,由于特殊的运行环境,可能无法使用组播。
在这种情况下,应该配置为其他协议,例如配置 TCPPING
以使用 TCP 套接字,或配置 S3_PING
以使用亚马逊 EC2。
有关其他可用的 JGroups 发现协议及其如何配置的更多信息,请查阅 JGroups文档 。
使用IPv6的问题
如果在 IPv6 地址配置遇到困难,可以通过设置系统属性 java.net.preferIPv4Stack
强制使用 IPv4:
-Djava.net.preferIPv4Stack=true
开启日志
在排除故障时,开启 Infinispan 和 JGroups 日志很有帮助,可以观察是否组成了集群。
使用默认的 JUL 日志时,在 classpath 中添加 vertx-default-jul-logging.properties
文件可开启 Infinispan 和 JGroups 日志。
这是一个标准 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包放入 classpath 中,JBoss Logging 能够自动检测到并使用。
如果在 classpath 有多种日志框架,可以通过设置系统变量 org.jboss.logging.provider
来指定具体的实现。
例如:
-Dorg.jboss.logging.provider=log4j2
更多配置信息请参考 JBoss日志指南 。
JGroups 日志配置
JGroups 默认采用 JDK Logging 实现。同时也支持 log4j 与 log4j2 ,只要相应的 jar 包 在 classpath 中。
如果想查阅更详细的信息,或实现自己的日志后端,请参考 JGroups日志文档。
SharedData 扩展
AsyncMap 内容流
InfinispanAsyncMap
API支持将 AsyncMap 的键、值及 Entry 作为流进行读取。
如果您需要遍历读取很大的 AsyncMap 并进行批量处理,这将很有帮助。
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 集群管理器时,应遵循 Infinispan 的管理准则。
首先介绍下再平衡(Rebalancing)和脑裂。
再平衡(Rebalancing)
每个 Vert.x 节点都包含部分集群数据,包括:EventBus 订阅,异步 Map,分布式计数器等等。
当有节点加入或离开集群时,Infinispan 会在新的集群拓扑中重新平衡分配(rebalance)缓存条目。 换句话说,它可以移动数据以适应新的集群拓扑。 此过程可能需要一些时间,具体取决于集群数据量和节点数量。
脑裂
在理想环境中,不会出现网络设备故障。 实际上,集群迟早会被分成多个小组,彼此之间不可见。
Infinispan 能够将节点合并回单个集群。 但是,就像数据分区迁移一样,此过程可能需要一些时间。 在集群变回可用之前,某些 EventBus 的消费者可能无法获取到消息。 否则,重新部署故障的 Verticle 过程中无法保证高可用。
注意
|
很难(或者说基本不可能)区分脑裂和:
|
建议
考虑到上面讨论的常见集群问题,建议遵循下述的最佳实践。
逐个添加或移除节点
滚动更新新版本应用时,或扩大/缩小集群时,应该一个接一个地添加或移除节点。
逐个停止节点可避免集群误以为发生了脑裂。 逐个添加节点可以进行干净的增量数据分区迁移。
可以使用 Vert.x 运行状况检查 来验证集群安全性:
Handler<Promise<Status>> procedure = ClusterHealthCheck.createProcedure(vertx, true);
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));