6.4. HA 单例服务
群集单例服务也称为高可用性(HA)单例,是在群集的多个节点上部署的服务。该服务仅在其中一个节点上提供。运行单例服务的节点通常称为 master 节点。
当 master 节点出现故障或关闭时,将从剩余的节点中选择另一个 master,并在新 master 上重新启动该服务。除了一个 master 已停止并且另一个主机尚未接管时的简短间隔之外,该服务由一个(仅一个)节点提供。
HA Singleton ServiceBuilder API
JBoss EAP 7 引入了一个新的公共 API,用于构建单例服务,显著简化流程。
SingletonServiceConfigurator
实施会安装其服务,以便异步启动,防止 Modular Service Container(MSC)死锁。
HA Singleton Service Election 策略
如果哪个节点应启动 HA 单例,您可以在 ServiceActivator
类中设置选择策略。
JBoss EAP 提供两种选择策略:
简单选择策略
简单的选择策略会根据相对年龄选择 master 节点。所需的年龄在 location 属性中配置,这是可用节点列表中的索引,其中:
- 位置 = 0 - 代表最旧的节点.这是默认值。
- 位置 = 1 - 是指第 2 个最早的位置,以此类推。
位置也可能为负数,表示最年轻的节点。
- 位置 = -1 - 是指最年轻的节点.
- 位置 = -2 - 是指第二大节点,以此类推。
随机选择策略
随机选择策略选择随机成员作为单例服务的提供商。
HA 单例服务首选项
HA 单例服务选择策略可以选择性地指定一个或多个首选服务器。此首选服务器在可用时将成为该策略下所有单例应用的主服务器。
您可以通过节点名称或通过出站套接字绑定名称来定义首选项。
节点首选项总是优先于选择策略的结果。
默认情况下,JBoss EAP 高可用性配置提供一个简单的选择策略,名为 default
,无首选服务器。您可以通过创建自定义策略和定义首选服务器来设置首选项。
仲裁
当存在网络分区时,单例服务可能出现潜在问题。在这种情况下,也称脑裂情景,节点子集无法互相通信。每组服务器都将另一组中的服务器视为故障,并作为存活的群集继续工作。这可能会导致数据一致性问题。
JBoss EAP 允许您在选择策略中指定仲裁,以防止出现脑裂情形。在进行单例供应商选择前,仲裁指定要存在的最少节点数。
典型的部署场景使用 N/2 + 1 的仲裁,其中 N 是预期的集群大小。这个值可以在运行时更新,并且会立即影响任何活跃的单例服务。
HA Singleton Service Election Listener
选择新的主单例服务提供商后,会触发任何注册的 SingletonElectionListener
,向群集的每个成员通知新主提供程序。以下示例演示了 SingletonElectionListener
的用法:
public class MySingletonElectionListener implements SingletonElectionListener { @Override public void elected(List<Node> candidates, Node primary) { // ... } } public class MyServiceActivator implements ServiceActivator { @Override public void activate(ServiceActivatorContext context) { String containerName = "foo"; SingletonElectionPolicy policy = new MySingletonElectionPolicy(); SingletonElectionListener listener = new MySingletonElectionListener(); int quorum = 3; ServiceName name = ServiceName.parse("my.service.name"); // Use a SingletonServiceConfiguratorFactory backed by default cache of "foo" container Supplier<SingletonServiceConfiguratorFactory> factory = new ActiveServiceSupplier<SingletonServiceConfiguratorFactory>(context.getServiceRegistry(), ServiceName.parse(SingletonDefaultCacheRequirement.SINGLETON_SERVICE_CONFIGURATOR_FACTORY.resolve(containerName))); ServiceBuilder<?> builder = factory.get().createSingletonServiceConfigurator(name) .electionListener(listener) .electionPolicy(policy) .requireQuorum(quorum) .build(context.getServiceTarget()); Service service = new MyService(); builder.setInstance(service).install(); } }
创建 HA 单例服务应用
以下是作为集群范围的单例创建和部署应用所需的步骤的缩写示例。本例演示了一个查询服务,它会定期查询单例服务来获取运行它的节点的名称。
要查看单例行为,您必须将应用部署到至少两台服务器。无论单例服务是否在同一节点上运行,还是远程获取该值,都是透明的。
创建
SingletonService
类。getValue()
方法由查询服务调用,提供有关在其上运行的节点的信息。class SingletonService implements Service { private Logger LOG = Logger.getLogger(this.getClass()); private Node node; private Supplier<Group> groupSupplier; private Consumer<Node> nodeConsumer; SingletonService(Supplier<Group> groupSupplier, Consumer<Node> nodeConsumer) { this.groupSupplier = groupSupplier; this.nodeConsumer = nodeConsumer; } @Override public void start(StartContext context) { this.node = this.groupSupplier.get().getLocalMember(); this.nodeConsumer.accept(this.node); LOG.infof("Singleton service is started on node '%s'.", this.node); } @Override public void stop(StopContext context) { LOG.infof("Singleton service is stopping on node '%s'.", this.node); this.node = null; } }
创建查询服务。它调用单例服务的
getValue()
方法以获取运行它的节点的名称,然后将结果写入服务器日志。class QueryingService implements Service { private Logger LOG = Logger.getLogger(this.getClass()); private ScheduledExecutorService executor; @Override public void start(StartContext context) throws { LOG.info("Querying service is starting."); executor = Executors.newSingleThreadScheduledExecutor(); executor.scheduleAtFixedRate(() -> { Supplier<Node> node = new PassiveServiceSupplier<>(context.getController().getServiceContainer(), SingletonServiceActivator.SINGLETON_SERVICE_NAME); if (node.get() != null) { LOG.infof("Singleton service is running on this (%s) node.", node.get()); } else { LOG.infof("Singleton service is not running on this node."); } }, 5, 5, TimeUnit.SECONDS); } @Override public void stop(StopContext context) { LOG.info("Querying service is stopping."); executor.shutdown(); } }
实施
SingletonServiceActivator
类,以构建和安装单例服务和查询服务。public class SingletonServiceActivator implements ServiceActivator { private final Logger LOG = Logger.getLogger(SingletonServiceActivator.class); static final ServiceName SINGLETON_SERVICE_NAME = ServiceName.parse("org.jboss.as.quickstarts.ha.singleton.service"); private static final ServiceName QUERYING_SERVICE_NAME = ServiceName.parse("org.jboss.as.quickstarts.ha.singleton.service.querying"); @Override public void activate(ServiceActivatorContext serviceActivatorContext) { SingletonPolicy policy = new ActiveServiceSupplier<SingletonPolicy>( serviceActivatorContext.getServiceRegistry(), ServiceName.parse(SingletonDefaultRequirement.POLICY.getName())).get(); ServiceTarget target = serviceActivatorContext.getServiceTarget(); ServiceBuilder<?> builder = policy.createSingletonServiceConfigurator(SINGLETON_SERVICE_NAME).build(target); Consumer<Node> member = builder.provides(SINGLETON_SERVICE_NAME); Supplier<Group> group = builder.requires(ServiceName.parse("org.wildfly.clustering.default-group")); builder.setInstance(new SingletonService(group, member)).install(); serviceActivatorContext.getServiceTarget() .addService(QUERYING_SERVICE_NAME, new QueryingService()) .setInitialMode(ServiceController.Mode.ACTIVE) .install(); serviceActivatorContext.getServiceTarget().addService(QUERYING_SERVICE_NAME).setInstance(new QueryingService()).install(); LOG.info("Singleton and querying services activated."); } }
-
在
META-INF/services/
目录中创建一个名为org.jboss.msc.service.ServiceActivator
的文件,其中包含ServiceActivator
类的名称,例如org.jboss.as.quickstarts.ha.singleton.service.SingletonServiceActivator
。
请参阅 JBoss EAP 随附的 ha-singleton-service
快速入门,以获取完整的工作示例。此快速入门还提供了第二个示例,它演示了安装有备份服务的单例服务。备份服务在所有不选择运行单例服务的节点中运行。最后,此快速入门还演示了如何配置几个不同的选择策略。