8.4. HA シングルトンの実装
JBoss Enterprise Application Platform 5 では、HA シングルトンアーカイブは他のデプロイメントとは別に deploy-hasingleton/
ディレクトリーにデプロイされていました。これは自動デプロイメントが発生しないようにするためで、また確実に HASingletonDeployer サービスがデプロイメントを制御し、クラスターのマスターノードのみにアーカイブがデプロイされるようにするための処置でした。ホットデプロイメント機能がなかったため、再デプロイメントにはサーバーの再起動が必要でした。また、マスターノードに障害が発生し、他のノードがマスターとして引き継ぐ必要がある場合、シングルトンサービスはサービスを提供するためデプロイメントプロセス全体を実行する必要がありました。
手順8.3 HA シングルトンサービスの実装
HA シングルトンサービスアプリケーションを作成します。
シングルトンサービスとしてデプロイされる SingletonService デコレーターでラッピングされたサービスの簡単な例は次のとおりです。シングルトンサービスを作成します。
以下のリストは、シングルトンサービスの例です。package com.mycompany.hasingleton.service.ejb; import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Logger; import org.jboss.as.server.ServerEnvironment; import org.jboss.msc.inject.Injector; import org.jboss.msc.service.Service; import org.jboss.msc.service.ServiceName; import org.jboss.msc.service.StartContext; import org.jboss.msc.service.StartException; import org.jboss.msc.service.StopContext; import org.jboss.msc.value.InjectedValue; /** * @author <a href="mailto:wfink@redhat.com">Wolf-Dieter Fink</a> */ public class EnvironmentService implements Service<String> { private static final Logger LOGGER = Logger.getLogger(EnvironmentService.class.getCanonicalName()); public static final ServiceName SINGLETON_SERVICE_NAME = ServiceName.JBOSS.append("quickstart", "ha", "singleton"); /** * A flag whether the service is started. */ private final AtomicBoolean started = new AtomicBoolean(false); private String nodeName; private final InjectedValue<ServerEnvironment> env = new InjectedValue<ServerEnvironment>(); public Injector<ServerEnvironment> getEnvInjector() { return this.env; } /** * @return the name of the server node */ public String getValue() throws IllegalStateException, IllegalArgumentException { if (!started.get()) { throw new IllegalStateException("The service '" + this.getClass().getName() + "' is not ready!"); } return this.nodeName; } public void start(StartContext arg0) throws StartException { if (!started.compareAndSet(false, true)) { throw new StartException("The service is still started!"); } LOGGER.info("Start service '" + this.getClass().getName() + "'"); this.nodeName = this.env.getValue().getNodeName(); } public void stop(StopContext arg0) { if (!started.compareAndSet(true, false)) { LOGGER.warning("The service '" + this.getClass().getName() + "' is not active!"); } else { LOGGER.info("Stop service '" + this.getClass().getName() + "'"); } } }
サーバー起動時にサービスを SingletonService として起動するためにシングルトン EJB を作成します。
以下のリストは、サーバー起動時に SingletonService を起動するシングルトン EJB の例です。package com.mycompany.hasingleton.service.ejb; import java.util.Collection; import java.util.EnumSet; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import javax.ejb.Singleton; import javax.ejb.Startup; import org.jboss.as.clustering.singleton.SingletonService; import org.jboss.as.server.CurrentServiceContainer; import org.jboss.as.server.ServerEnvironment; import org.jboss.as.server.ServerEnvironmentService; import org.jboss.msc.service.AbstractServiceListener; import org.jboss.msc.service.ServiceController; import org.jboss.msc.service.ServiceController.Transition; import org.jboss.msc.service.ServiceListener; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * A Singleton EJB to create the SingletonService during startup. * * @author <a href="mailto:wfink@redhat.com">Wolf-Dieter Fink</a> */ @Singleton @Startup public class StartupSingleton { private static final Logger LOGGER = LoggerFactory.getLogger(StartupSingleton.class); /** * Create the Service and wait until it is started.<br/> * Will log a message if the service will not start in 10sec. */ @PostConstruct protected void startup() { LOGGER.info("StartupSingleton will be initialized!"); EnvironmentService service = new EnvironmentService(); SingletonService<String> singleton = new SingletonService<String>(service, EnvironmentService.SINGLETON_SERVICE_NAME); // if there is a node where the Singleton should deployed the election policy might set, // otherwise the JGroups coordinator will start it //singleton.setElectionPolicy(new PreferredSingletonElectionPolicy(new NamePreference("node2/cluster"), new SimpleSingletonElectionPolicy())); ServiceController<String> controller = singleton.build(CurrentServiceContainer.getServiceContainer()) .addDependency(ServerEnvironmentService.SERVICE_NAME, ServerEnvironment.class, service.getEnvInjector()) .install(); controller.setMode(ServiceController.Mode.ACTIVE); try { wait(controller, EnumSet.of(ServiceController.State.DOWN, ServiceController.State.STARTING), ServiceController.State.UP); LOGGER.info("StartupSingleton has started the Service"); } catch (IllegalStateException e) { LOGGER.warn("Singleton Service {} not started, are you sure to start in a cluster (HA) environment?",EnvironmentService.SINGLETON_SERVICE_NAME); } } /** * Remove the service during undeploy or shutdown */ @PreDestroy protected void destroy() { LOGGER.info("StartupSingleton will be removed!"); ServiceController<?> controller = CurrentServiceContainer.getServiceContainer().getRequiredService(EnvironmentService.SINGLETON_SERVICE_NAME); controller.setMode(ServiceController.Mode.REMOVE); try { wait(controller, EnumSet.of(ServiceController.State.UP, ServiceController.State.STOPPING, ServiceController.State.DOWN), ServiceController.State.REMOVED); } catch (IllegalStateException e) { LOGGER.warn("Singleton Service {} has not be stopped correctly!",EnvironmentService.SINGLETON_SERVICE_NAME); } } private static <T> void wait(ServiceController<T> controller, Collection<ServiceController.State> expectedStates, ServiceController.State targetState) { if (controller.getState() != targetState) { ServiceListener<T> listener = new NotifyingServiceListener<T>(); controller.addListener(listener); try { synchronized (controller) { int maxRetry = 2; while (expectedStates.contains(controller.getState()) && maxRetry > 0) { LOGGER.info("Service controller state is {}, waiting for transition to {}", new Object[] {controller.getState(), targetState}); controller.wait(5000); maxRetry--; } } } catch (InterruptedException e) { LOGGER.warn("Wait on startup is interrupted!"); Thread.currentThread().interrupt(); } controller.removeListener(listener); ServiceController.State state = controller.getState(); LOGGER.info("Service controller state is now {}",state); if (state != targetState) { throw new IllegalStateException(String.format("Failed to wait for state to transition to %s. Current state is %s", targetState, state), controller.getStartException()); } } } private static class NotifyingServiceListener<T> extends AbstractServiceListener<T> { @Override public void transition(ServiceController<? extends T> controller, Transition transition) { synchronized (controller) { controller.notify(); } } } }
クライアントよりサービスへアクセスするためステートレスセッション Bean を作成します。
以下は、クライアントからサービスにアクセスするステートレスセッション Bean の例です。package com.mycompany.hasingleton.service.ejb; import javax.ejb.Stateless; import org.jboss.as.server.CurrentServiceContainer; import org.jboss.msc.service.ServiceController; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * A simple SLSB to access the internal SingletonService. * * @author <a href="mailto:wfink@redhat.com">Wolf-Dieter Fink</a> */ @Stateless public class ServiceAccessBean implements ServiceAccess { private static final Logger LOGGER = LoggerFactory.getLogger(ServiceAccessBean.class); public String getNodeNameOfService() { LOGGER.info("getNodeNameOfService() is called()"); ServiceController<?> service = CurrentServiceContainer.getServiceContainer().getService( EnvironmentService.SINGLETON_SERVICE_NAME); LOGGER.debug("SERVICE {}", service); if (service != null) { return (String) service.getValue(); } else { throw new IllegalStateException("Service '" + EnvironmentService.SINGLETON_SERVICE_NAME + "' not found!"); } } }
SingletonService のビジネスロジックインターフェースを作成します。
以下は、SingletonService に対するビジネスロジックインターフェースの例です。package com.mycompany.hasingleton.service.ejb; import javax.ejb.Remote; /** * Business interface to access the SingletonService via this EJB * * @author <a href="mailto:wfink@redhat.com">Wolf-Dieter Fink</a> */ @Remote public interface ServiceAccess { public abstract String getNodeNameOfService(); }
クラスタリングが有効な状態で各 Jboss Enterprise Application Platform 6 インスタンスを起動します。
クラスターを有効化する方法は、サーバーがスタンドアロンであるか管理ドメインで実行されているかによって異なります。管理対象ドメインで実行されているサーバーに対してクラスタリングを有効にします。
管理 CLI を使用してクラスタリングを有効にするか、設定ファイルを手動で編集できます。管理 CLI を使用してクラスタリングを有効にします。
ドメインコントローラーを起動します。
使用しているオペレーティングシステムのコマンドプロンプトを開きます。
ドメインコントローラーの IP アドレスまたは DNS 名を渡して管理 CLI に接続します。
この例では、ドメインコントローラーの IP アドレスが192.168.0.14
であることを前提とします。- Linux の場合は、コマンドラインで以下を入力します。
$ EAP_HOME/bin/jboss-cli.sh --connect --controller=192.168.0.14 $ Connected to domain controller at 192.168.0.14
- Windows の場合は、コマンドラインで以下を入力します。
C:\>EAP_HOME\bin\jboss-cli.bat --connect --controller=192.168.0.14 C:\> Connected to domain controller at 192.168.0.14
main-server
サーバーグループを追加します。[domain@192.168.0.14:9999 /] /server-group=main-server-group:add(profile="ha",socket-binding-group="ha-sockets") { "outcome" => "success", "result" => undefined, "server-groups" => undefined }
server-one
という名前のサーバーを作成し、main-server
サーバーグループに追加します。[domain@192.168.0.14:9999 /] /host=station14Host2/server-config=server-one:add(group=main-server-group,auto-start=false) { "outcome" => "success", "result" => undefined }
main-server
サーバーグループに対して JVM を設定します。[domain@192.168.0.14:9999 /] /server-group=main-server-group/jvm=default:add(heap-size=64m,max-heap-size=512m) { "outcome" => "success", "result" => undefined, "server-groups" => undefined }
server-two
という名前のサーバーを作成し、別のサーバーグループに置き、ポートオフセットを 100 に設定します。[domain@192.168.0.14:9999 /] /host=station14Host2/server-config=server-two:add(group=distinct2,socket-binding-port-offset=100) { "outcome" => "success", "result" => undefined }
サーバー設定ファイルを手動で編集してクラスタリングを有効にします。
JBoss Enterprise Application Platform サーバーを停止します。
重要
変更がサーバーの再起動後にも維持されるようにするには、サーバー設定ファイルの編集前にサーバーを停止する必要があります。domain.xml
設定ファイルを開いて編集します。ha
プロファイルとha-sockets
ソケットバインディンググループを使用するサーバーグループを指定します。例は次のとおりです。<server-groups> <server-group name="main-server-group" profile="ha"> <jvm name="default"> <heap size="64m" max-size="512m"/> </jvm> <socket-binding-group ref="ha-sockets"/> </server-group> </server-groups>
host.xml
設定ファイルを開いて編集します。以下のようにファイルを変更します。<servers> <server name="server-one" group="main-server-group" auto-start="false"/> <server name="server-two" group="distinct2"> <socket-bindings port-offset="100"/> </server> <servers>
サーバーを起動します。
- Linux の場合は、
EAP_HOME/bin/domain.sh
と入力します。 - Microsoft Windows の場合は、
EAP_HOME\bin\domain.bat
と入力します。
スタンドアロンサーバーに対してクラスタリングを有効にします。
スタンドアロンサーバーに対してクラスタリングを有効にするには、次のようにノード名とstandalone-ha.xml
設定ファイルを使用してサーバーを起動します。- Linux の場合は、
EAP_HOME/bin/standalone.sh --server-config=standalone-ha.xml -Djboss.node.name=UNIQUE_NODE_NAME
と入力します。 - Microsoft Windows の場合は、
EAP_HOME\bin\standalone.bat --server-config=standalone-ha.xml -Djboss.node.name=UNIQUE_NODE_NAME
と入力します。
注記
1 つのマシン上で複数のサーバーが実行されている時にポートの競合が発生しないようにするため、別のインターフェースでバインドするように各サーバーインスタンスに対してstandalone-ha.xml
ファイルを設定します。または、コマンドラインで-Djboss.socket.binding.port-offset=100
のような引数を使用し、ポートオフセットを持つ後続のサーバーインスタンスを開始して対応することも可能です 。アプリケーションをサーバーにデプロイします。
Maven を使用してアプリケーションをデプロイする場合は、次の Maven コマンドを使用してデフォルトのポートで稼働しているサーバーへデプロイします。mvn clean install jboss-as:deploy
追加のサーバーをデプロイするには、サーバー名とポート番号をコマンドラインに渡します。mvn clean package jboss-as:deploy -Ddeploy.hostname=localhost -Ddeploy.port=10099