6.4. HA Singleton 서비스

클러스터형 Singleton 서비스(고가용성(HA) Singleton이라고도 함)는 클러스터의 여러 노드에 배포된 서비스입니다. 서비스는 노드 중 하나에서만 제공됩니다. Singleton 서비스를 실행 중인 노드를 일반적으로 마스터 노드라고 합니다.

마스터 노드가 실패하거나 종료되면 나머지 노드에서 다른 마스터가 선택되고 새 마스터에서 서비스가 다시 시작됩니다. 하나의 마스터가 중지되고 다른 마스터가 아직 인계되지 않은 경우의 간략한 간격 이외의 서비스는 하나와 하나의 노드에서만 제공됩니다.

HA Singleton ServiceBuilder API

JBoss EAP 7은 프로세스를 크게 단순화하는 Singleton 서비스 구축을 위한 새로운 공용 API를 도입했습니다.

SingletonServiceConfigurator 구현은 서비스를 설치하므로 비동기적으로 시작하여 MSC(Modular Service Container)의 교착 상태가 발생하지 않습니다.

HA Singleton 서비스 선택 정책

HA Singleton을 시작해야 하는 노드가 선호하는 경우 ServiceActivator 클래스에서 선택 정책을 설정할 수 있습니다.

JBoss EAP는 다음 두 가지 선택 정책을 제공합니다.

  • 단순 선택 정책

    간단한 선택 정책은 상대 기간에 따라 마스터 노드를 선택합니다. 필요한 기간은 사용 가능한 노드 목록의 인덱스인 position 속성에 구성됩니다. 여기서 다음과 같습니다.

    • position = 0 - 가장 오래된 노드를 나타냅니다. 이는 기본값입니다.
    • position = 1 - 가장 오래된 2번째 등을 나타냅니다.

    위치는 가장 오래된 노드를 나타내기 위해 음수일 수도 있습니다.

    • position = -1 - 가장 오래된 노드를 나타냅니다.
    • position = -2 - 두 번째 가장 오래된 노드 등을 나타냅니다.
  • 임의 선택 정책

    임의 선택 정책은 Singleton 서비스 공급자로 임의 구성원을 선택합니다.

HA Singleton 서비스 환경 설정

HA Singleton 서비스 선택 정책은 선택적으로 하나 이상의 기본 서버를 지정할 수 있습니다. 이 선호하는 서버는 해당 정책에 따라 모든 Singleton 애플리케이션에 대한 마스터가 됩니다.

노드 이름 또는 아웃바운드 소켓 바인딩 이름을 통해 기본 설정을 정의할 수 있습니다.

참고

노드 기본 설정은 항상 선택 정책의 결과보다 우선합니다.

기본적으로 JBoss EAP 고가용성 구성에서는 default 라는 간단한 선택 정책에 기본 서버가 없는 간단한 선택 정책을 제공합니다. 사용자 지정 정책을 생성하고 기본 서버를 정의하여 기본 설정을 설정할 수 있습니다.

쿼럼

Singleton 서비스에 대한 잠재적인 문제는 네트워크 파티션이 있는 경우 발생합니다. 이 경우 split-brain 시나리오라고도 하며 노드의 하위 집합은 서로 통신할 수 없습니다. 각 서버 집합은 다른 세트의 모든 서버가 실패한 것으로 간주하고 생존 클러스터로 계속 작동합니다. 이로 인해 데이터 일관성 문제가 발생할 수 있습니다.

JBoss EAP를 사용하면 선택 정책에 쿼럼을 지정하여 split-brain 시나리오를 방지할 수 있습니다. 쿼럼은 Singleton 공급자 선택을 수행하기 전에 존재할 최소 노드 수를 지정합니다.

일반적인 배포 시나리오에서는 N/2 + 1의 쿼럼을 사용합니다. 여기서 N은 예상되는 클러스터 크기입니다. 이 값은 런타임 시 업데이트할 수 있으며 활성 Singleton 서비스에 즉시 영향을 미칩니다.

HA Singleton 서비스 선택 리스너

새로운 기본 Singleton 서비스 공급자를 선택하고 나면 등록된 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 Singleton 서비스 애플리케이션 생성

다음은 애플리케이션을 클러스터 전체 Singleton 서비스로 생성하고 배포하는 데 필요한 단계의 축약된 예입니다. 이 예제에서는 Singleton 서비스를 정기적으로 쿼리하여 실행 중인 노드의 이름을 가져오는 쿼리 서비스를 보여줍니다.

Singleton 동작을 보려면 애플리케이션을 두 개 이상의 서버에 배포해야 합니다. Singleton 서비스가 동일한 노드에서 실행 중인지 또는 값을 원격으로 가져올지 여부는 명확합니다.

  1. 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;
        }
    }
  2. 쿼리 서비스를 생성합니다. Singleton 서비스의 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();
        }
    }
  3. SingletonServiceActivator 클래스를 구현하여 Singleton 서비스와 쿼리 서비스를 모두 빌드하고 설치합니다.

    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.");
        }
    }
  4. ServiceActivator 클래스 의 이름이 포함된 org.jboss.msc.service.ServiceActivator 라는 META-INF/services/ 디렉터리에 파일을 만듭니다(예: org.jboss.as.quickstarts.ha.singleton.service.SingletonServiceActivator ).

전체 작업 예는 JBoss EAP와 함께 제공되는 ha-singleton-service 빠른 시작을 참조하십시오. 이 빠른 시작에서는 백업 서비스를 사용하여 설치된 Singleton 서비스를 보여주는 두 번째 예제도 제공합니다. 백업 서비스는 Singleton 서비스를 실행 중이 아닌 모든 노드에서 실행 중입니다. 마지막으로 이 빠른 시작에서는 몇 가지 다른 선택 정책을 구성하는 방법도 보여줍니다.