Lastverteilung und Failover für entfernte EJB Clients in EAP6 und JBoss AS 7

In den letzten Posts dieser Serie über die Clusterfähigkeiten der JBoss EAP6 und des AS7 ging es um grundlegende Konzepte, Clusterknoten im Domain-Mode verwalten, und skalierbare Hochverfügbarkeitscluster. In diesem Post wird es um den Zugriff von entfernten EJB Clients auf Clustersysteme gehen. Wir werden erklären, wie ein entfernter Java Client transparent auf EJB Komponenten zugreifen kann und dabei sowohl clientseitiges Failover als auch clientseitige Lastverteilung realisiert wird. Darüber hinaus werden wir kurz darstellen wie EJB Komponenten serverseitig konfiguriert werden müssen.

Übersicht

Im Vergleich zu früheren Versionen des JBoss Application Servers hat sich die Art und Weise, auf die entfernte Aufrufe auf serverseitige EJB Komponenten gemacht werden, verändert. Um serverseitige EJB Komponenten aufrufen zu können, stellt der AS7 nun eine Bibliothek für Client EJB Anwendungen bereit. Die Bibliothek kann zu diesem Zweck auf verschiedene Arten benutzt werden:

  1. programmatisch, indem direkt die EJB Client API benutzt wird
  2. über die JNDI API
  3. über die JNDI API bei gleichzeitiger Nutzung des JBoss remote-naming Projektes

Die API programmatisch oder über die JNDI API zu nutzen, hat den Vorteil diverser automatischer Optimierungen, wie zum Beispiel die lokale Erstellung von Proxies für Stateless-Session-Beans, was Server-Roundtrips und Serialsierung des Proxies einspart. Der dritte Weg, das remote-naming Projekt zu nutzen, wird unter Umständen erforderlich, wenn die Clients sowohl gewönliche entfernte Objekte über JNDI laden wollen, als auch EJB Proxies. Als Entscheidungshilfe, welcher der drei Wege der individuell günstigste ist, sei an dieser Stelle eine detailliertere Erklärung aus der Community Dokumentation angeführt. Im Fokus dieses Blogposts soll der zweite Weg stehen. Dabei möchten wir besonders auf die Optionen eingehen, die spezifisch für Clustering sind, denn diese scheinen bisher noch nicht so gut dokumentiert zu sein.

Das Beispielszenario

Wir werden eine einfache Beispielanwendung benutzen, um clientseitige Lastverteilung wie auch clientseitiges Failover vorzuführen. Diese Beispielanwendung können Sie in unserem github repository im Ordner cluster-client-example finden und herunterladen. Das Projekt der Beispielanwendung beinhaltet sowohl eine serverseitige Anwendung als auch einen Swing-basierten EJB Client. Das ganze Projekt kann mittels des Kommandos mvn package kompiliert werden. Für unser Beispielszenario nehmen wir im Folgenden an, dass die EJB Komponenten auf zwei Servern deployt sind und dass (EJB-) Sessiondaten zwischen diesen Servern repliziert werden.

JBoss AS7 - EJB cluster topology and client-side load-balancing with session failover
Clustertopologie des Beispielszenarios

Die serverseitige Anwendung

Die serverseitige Anwendung besteht aus einer Stateful-Session-Bean, welche die Aufrufe auf sich mitzählt und einer Stateless-Session-Bean, welche den Namen des Clusterknotens, auf dem sie ausgeführt wird, zurückgibt. Beide Session-Beans implementieren ein Remote Interface, um entfernten Zugriff zu unterstützen. Um nun eine EJB zu clustern, muss sie entsprechend markiert sein. Das geht einerseits im Quelltext mit Hilfe der @Clustered Annotation oder anderseits mit Hilfe des <clustered> Elements im JBoss spezifischen Deploymentdeskriptor für EJB Komponenten: META-INF/jboss-ejb3.xml. Das Folgende Listing skizziert eine beispielhafte Implementierung einer Stateful-Session-Bean.

[sourcecode language=”java”]
@Stateful
@Clustered
@Remote(RemoteStateful.class)
public class ClusteredStatefulBean implements RemoteStateful {

@Override
public int getAndIncrementCounter() {

}

@Override
public String getNodeName() {

}

@Remove
public void destroy() {
}
}
[/sourcecode]

Indem man eine Stateless-Session-Bean als geclustert markiert, schaltet man die Lastverteilung über den Cluster von entfernten Clients ein. Für eine Stateful-Session-Bean wird dadurch neben der Lastverteilung auch die Replikation der Sessiondaten zwischen den Servern des Clusters aktiviert.

Eine Stateful-Session-Bean zu clustern bringt wesentlich mehr Aufwand mit sich, als eine Stateless-Session-Bean zu clustern, da der transiente Zustand der Stateful-Session-Bean innerhalb des Clusters verwaltet werden muss. Der Server erledigt dies zwar transparent für den Entwickler, dieser sollte sich der zusätzlichen Komplexität, aber bewusst sein, denn diese kann die Skalierbarkeit der Anwendung einschränken.

Die EJB Komponenten der Beispielanwendung sind in einem EJB Archiv mit dem Modulnamen cluster gebündelt. Dieser Modulname wir im META-INF/ejb-jar.xml Deploymentdeskriptor definiert.

Ein Cluster formiert sich automatisch und kann seine Topologie unabhängig von der Clientseite ändern. Im Prinzip sind nur zwei Dinge nötig, um eine EJB Komponente zu clustern: Einerseits muss die EJB Komponente als geclustert markiert werden und anderseits müssen die Server mit einem HA-Profil gestartet werden. Letzteres bedeutet im Standalone-Mode, dass die Server mit der standalone-ha.xml gestartet werden müssen. Im Domain-Mode müsste das ha Profil verwendet werden. Eine detailliertere Erklärung, wie man einen einfachen Cluster aufsetzt, finden Sie in unserem ersten Blogpost zum Thema Clustering.

Die clientseitige Anwendung

Der Client ist eine einfache Swing-basierte Anwendung die es ermöglicht, die serverseitigen EJB Komponenten aufzurufen.

Remote JBoss AS7 - EJB client application
Die Clientanwendung
Grundlegende EJB Client Konfiguration

Wie bereits erwähnt verwendet unsere Anwendung die EJB Client Bibliothek über die JNDI API. Deshalb benötigen wir eine jndi.properties Datei im Classpath unserer Anwendung. In der Datei wird das Package-Prefix der Objekt-Factory Implementierung für den JBoss-spezifischen ejb Namensbereich angegeben.

[sourcecode]
java.naming.factory.url.pkgs=org.jboss.ejb.client.naming
[/sourcecode]

jndi.properties

Der nächste Schritt ist es, den EJB Client Context zu konfigurieren. Dieser wird in der Datei jboss-ejb-client.properties konfiguriert, die ebenfalls im Classpath unserer Anwendung liegen muss. Der EJB Client Context speichert die Informationen, die benötigt werden, um Verbindungen zu entfernten Servern herzustellen.

Um einen EJB Client mit einem EJB Receiver aufzusetzen, wird folgende minimale jboss-ejb-client.properties benötigt:

[sourcecode]
endpoint.name=client-endpoint
remote.connectionprovider.create.options.org.xnio.Options.SSL_ENABLED=false

remote.connections=default
remote.connection.default.host=localhost
remote.connection.default.port=4447
remote.connection.default.connect.options.org.xnio.Options.SASL_POLICY_NOANONYMOUS=false
[/sourcecode]

jboss-ejb-client.properties

Damit sich ein Client mit einem Cluster verbinden kann, muss auf dem Client eine initiale Verbindung, ein sogenannter EJB Receiver, als Eintrittspunkt in den Cluster konfiguriert werden. Beim ersten Aufruf wird dieser EJB Reciever dann über die genauere Clustertopologie informiert. Danach ist der Client unabhängig vom initialen Server. Falls dieser Server abstürzt, kennt der Client die anderen Server des Clusters und kann Failover und Lastverteilung betreiben. Somit ist diese minimale Konfiguration bereits für einzelnen Servern als auch für eine geclusterten Umgebungen ausreichend, um EJB Komponenten aufzurufen.

Nach dem ersten Kontakt mit dem initialen Server wird der Client über die vollstämdige Clustertopologie informiert. Dieser initiale Server muss verfügbar sein, bis der Client die Clustertopologie kennt.

Wenn man das Loglevel des Clients erhöht, wird auf der Konsole auch die Nachricht mit der aktuellen Topologie protokolliert:

DEBUG: Received a cluster node(s) addition message, for cluster named ejb with 2 nodes [ClusterNode{clusterName='ejb', nodeName='jb1', clientMappings=[ClientMapping{sourceNetworkAddress=/0:0:0:0:0:0:0:0, sourceNetworkMaskBits=0, destinationAddress='127.0.0.1', destinationPort=4447}], resolvedDestination=[Destination address=127.0.0.1, destination port=4447]}, ClusterNode{clusterName='ejb', nodeName='jb2', clientMappings=[ClientMapping{sourceNetworkAddress=/0:0:0:0:0:0:0:0, sourceNetworkMaskBits=0, destinationAddress='127.0.0.1', destinationPort=4547}], resolvedDestination=[Destination address=127.0.0.1, destination port=4547]}]

Weitere Dokumnetation zur Kommunikation der Clustertopologie ist in der Community Dokumentation enthalten.

Um auch bei Ausfall des initialen Servers noch die Verbindung neuer Clients zu ermöglichen, können mehrere EJB Receiver für eine initiale Verbindung konfiguriert werden. Das erfolgt durch eine via Kommata getrennte Liste von symbolische Verbindungsnamen (Alias):

[sourcecode]
remote.connections=default, other
remote.connection.default.<option>=…

remote.connection.other.<option>=…

[/sourcecode]

Dieses Feature ist leider aktuell noch nicht stabil nutzbar. Der Client stürzt manchmal ab (JBPAPP-9349).

Es gibt natürlich noch weitere Konfigurationsoptionen, die für geclusterte Umgebungen interessant sind. Diese werden später im Text genauer besprochen.

Lookup und Aufruf einer entfernten EJB Komponente

Der Clientcode ist derselbe, egal ob der Aufruf auf eine geclusterte Umgebung erfolgt oder nicht. Der folgende Quellcode zeigt die Initialisierung des JNDI Kontextes, sowie den Lookup auf einer entfernten SLSB und SFSB.

[sourcecode language=”java”]
class RemoteEJBClient
{
private final Context context = new InitialContext();

public RemoteStateless lookupRemoteStatelessBean()
throws NamingException
{
final String appName = "";

// module-name in ejb-jar.xml
final String moduleName = "cluster";

// distinct-name in jboss-ejb3.xml
final String distinctName = "";

// ejb:/cluster//ClusteredStatelessBean
// !de.akquinet.jbosscc.cluster.RemoteStateless

final String jndiName = "ejb:" + appName + ‘/’ + moduleName +
‘/’+ distinctName + ‘/’ +
ClusteredStatelessBean.class.getSimpleName() + ‘!’ +
RemoteStateless.class.getName();

return (RemoteStateless) context.lookup(jndiName);
}

public RemoteStateful lookupRemoteStatefulBean()
throws NamingException
{
final String appName = "";

// module-name in ejb-jar.xml
final String moduleName = "cluster";

// distinct-name in jboss-ejb3.xml
final String distinctName = "";

// ejb:/cluster//ClusteredStatefulBean
// !de.akquinet.jbosscc.cluster.RemoteStateful?stateful
final String jndiName = "ejb:" + appName + ‘/’ + moduleName +
‘/’ + distinctName + ‘/’ +
ClusteredStatefulBean.class.getSimpleName() + ‘!’ +
RemoteStateful.class.getName() + "?stateful";

return (RemoteStateful) context.lookup(jndiName);
}
}
[/sourcecode]

Der Quellcode zur Initialisierung des JNDI Kontext und des JNDI Lookups der entfernten SLSB und SFSB.

Für Stateless-Session-Beans wurde in der EJB Client Bibliothek eine Optimierung eingeführt: Für den Lookup einer SLSB ist kein Server-Roundtrip notwendig. Die initiale Anfrage an den Server wird zurückgestellt bis zum ersten Methodenaufruf auf die SLSB. Damit wird nicht nur ein Server-Roundtrip eingespart, sondern der Server muss zum Zeitpunkt des Lookups auch nicht zwingend verfügbar sein.

[sourcecode language=”java”]
RemoteStateless statelessProxy = remoteEJBClient.lookupRemoteStatelessBean();

String nodeName = statelessProxy.getNodeName();
// More invocations on the SLSB…
[/sourcecode]

Lookup und Aufruf einer SLSB

Für Stateful-Session-Beans wird die obige Optimierung nicht durchgeführt, da extra für den Client eine serverseitige Session initialisiert werden muss. Das wird während des Lookups getan.

[sourcecode language=”java”]
RemoteStateful statefulSession = remoteEJBClient.lookupRemoteStatefulBean();

int counterValue = statefulSession.getAndIncrementCounter();
String nodeName = statefulSession.getNodeName();
// More invocations on the SFSB…

// destroy the session
statefulSession.destroy();
[/sourcecode]

Lookup und Aufruf einer SFSB

Lastverteilung und Failover

Ein Client stellt zunächst eine initiale Verbindung zu einem Server her, der den entsprechenden EJB Aufruf behandeln kann. Nachdem diese initiale Verbindung aufgebaut wurde, versorgt der Server den Client mit Informationen über die Clustertopologie. Wenn damit nun die Verbindung zum Cluster hergestellt ist, kann jeder beliebige Server des Clusters, insbesondere auch der initiale Server, abstürzen oder neue Server können dem Cluster beitreten. Solange zumindest ein Server funktioniert ist die Replikation der Sessiondaten garantiert und der Client kann bei einen Failover auf einen anderen Clusterknoten ausweichen, ohne die Sessiondaten zu verlieren.

Ein Cluster bezeichnet im Zusammenhang der EJB Client-Bibliothek immer mehrere Server, die den Sessionstatus untereinander replizieren. Für die Lastverteilung ist die Session Replikation nicht zwingend erforderlich. Lastverteilung kann auch mit mehreren unabhängigen Servern ohne Clusterfähigkeiten erreicht werden. Dabei müssen die EJB Komponenten noch nicht einmal als Cluster-fähig markiert werden. Allerdings verliert man durch die fehlende Replikation die Möglichkeit des Failovers.

Lastverteilung und Failover werden anhand von Selektoren kontrolliert. Es gibt zwei Arten von Knotenselektoren:

  1. Deploymentknotenselektoren (DeploymentNodeSelector) und
  2. Clusterknotenselektoren (ClusterNodeSelector)

Deploymentknotenselektoren

Die erste Art ist nicht cluster-spezifisch und wichtig für die Lastverteilung zur Auswahl der initialen Verbindung. Ein Deploymentknotenselektor wird benutzt um einen EJB Receiver für einen Server auszuwählen, der den EJB Aufruf behandeln kann. Failover spielt in diesem Kontext noch keine Rolle.

Standardmäßig benutzt die EJB Client Bibliothek die Implementierung RandomDeploymentNodeSelector. Eine eigene Implementierung muss das Interface org.jboss.ejb.client.DeploymentNodeSelector implementieren. Eine solche eigene Implementierung kann in der jboss-ejb-client.properties wie folgt konfiguriert werden:

deployment.node.selector=de.akquinet.jbosscc.cluster.client.RoundRobinDeploymentNodeSelector

Clusterknotenselektoren

Anders als ein Deploymentknotenselektor wird ein Clusterknotenselektor im Fall eines Failovers benutzt um einen Clusterknoten einer replizierten Cluster-Gruppe auszuwählen. Beispielsweise beim ersten Aufruf einer EJB entscheidet der Deploymentknotenselektor auf welchem Knoten dieser Aufruf ausgeführt werden soll. Im Falle einer Stateful-Session-Bean wird so lange mit diesem Knoten fortgefahren, bis dieser Knoten nicht mehr verfügbar ist. Der EJB Receiver hat eine Affinität zu diesem Knoten. Fällt dieser Knoten aus, wird der Clusterknotenselektor befragt, welcher Knoten für Failover genutzt werden soll. Im Fall von Stateless-Session-Beans ist das weitere Verfahren vom Proxy abhängig. Ein Proxy hat eine Affinität zu einem Cluster. Wenn derselbe Proxy weiter verwendet wird, wählt der Clusterknotenselektor den Knoten für den nächsten Aufruf aus. Wenn für jeden Aufruf ein neuer Proxy erstellt wird, wird der Deploymentknotenselektor befragt, auf welchen Knoten der Aufruf ausgeführt werden soll.

Standardmäßig nutzt die EJB Client Bibliothek die RandomClusterNodeSelector Implementierung. Eine eigene Implementierung muss das Interface org.jboss.ejb.client.ClusterNodeSelector implementieren. Eine solche eigene Implementierung kann in der jboss-ejb-client.properties wie folgt konfiguriert werden:

remote.clusters=ejb
...
remote.cluster.ejb.clusternode.selector=de.akquinet.jbosscc.cluster.client.RoundRobinClusterNodeSelector
...

Beachten Sie, dass die Property remote.clusters auch eine durch Kommata getrennte Liste verschiedener Hochverfügbarkeitscluster beinhalten kann. Der Clustername ejb im obigen Beispiel ist wichtig, da er mit dem Namen des Cache-Containers der EJB übereinstimmen muss. Der Name ejb ist der Standardname und ist im Infinispan Subsystem definiert. Weiterhin wird der Name vom cache-container Attribut des cluster-passivation-store Element im EJB3 Subsystem referenziert.

Der konfigurierte Clusterknotenselektor ist nur verantwortlich Knoten aus dem Cluster ejb auszuwählen.

Detailliertere Konfiguration

Bisher haben wir nur die grundlegenden Konzepte und die grundlegende Konfiguration erklärt. Aber für größere Anwendungen möchte man unter Umständen mehr Kontrolle über den Cluster haben oder hat die Anforderung mehrere Hochverfügbarkeitscluster parallel zu betreiben, wie im folgenden Bild.

example ha cluster topology
Beispielhafte Topologie eines Hochverfügbarkeitscluster

Falls der Client mehr Kontrolle über Konnektivität oder die Interaktion mit dem Cluster benötigt, können die folgenden Konfigurationen in der jboss-ejb-client.properties vorgenommen werden:

    • Kanal- und Endpunktkonfiguration
    • mehrere Verbindungen
    • verschiedene Timeouts
    • Sicherheitseinstellungen wie Nutzername und Passwort oder Callbackhandler
    • Konfiguration von Verbindungen für spezifische Cluster oder einzelne Knoten
    • Knotenselektoren

Ein Client, der mit dem oben abgebildeten Cluster interagiert, könnte folgende Konfiguration haben:

endpoint.name=client-endpoint
remote.connectionprovider.create.options.org.xnio.Options.SSL_ENABLED=false

# custom deployment selector
deployment.node.selector=de.akquinet.jbosscc.cluster.client.RoundRobinDeploymentNodeSelector

# timeout in milliseconds, that will be used for EJB invocations
invocation.timeout=3000

# timeout in milliseconds after reconnect tasks are submitted 
reconnect.tasks.timeout=2000

# user credentials
username=${username:admin}
password=${password:secret}

remote.connections=ejb,other

# first remote connection
remote.connection.ejb.host=192.168.0.1
remote.connection.ejb.port=4447
remote.connection.ejb.connect.options.org.xnio.Options.SASL_POLICY_NOANONYMOUS=false
remote.connection.ejb.connect.options.org.xnio.Options.SASL_POLICY_NOPLAINTEXT=false
remote.connection.ejb.connect.timeout=2000
remote.connection.ejb.username=${username:admin}
remote.connection.ejb.password=${password:secret}

# second remote connection
remote.connection.other.host=192.168.0.2
remote.connection.other.port=4447
remote.connection.other.username=${username:admin}
remote.connection.other.password=${password:secret}

# cluster configuration
remote.clusters=ejb, ejb-other

remote.cluster.ejb.clusternode.selector=de.akquinet.jbosscc.cluster.client.RoundRobinClusterNodeSelector
remote.cluster.ejb.connect.timeout=2500
remote.cluster.ejb.max-allowed-connected-nodes=2
remote.cluster.ejb.connect.options.org.xnio.Options.SASL_POLICY_NOANONYMOUS=false
remote.cluster.ejb.connect.options.org.xnio.Options.SSL_ENABLED=false

remote.cluster.ejb-other.clusternode.selector=de.akquinet.jbosscc.cluster.client.RoundRobinClusterNodeSelector
remote.cluster.ejb-other.connect.options.org.xnio.Options.SASL_POLICY_NOANONYMOUS=false
remote.cluster.ejb-other.connect.options.org.xnio.Options.SSL_ENABLED=false

Es ist möglich überaus komplexe Clustertopologien mit mehreren Clustern unterschiedlicher Namen abzubilden. Standardmäßig wird als Clustername ejb verwendet. Das ist der Name des cache-container, welcher unter Anpassung des cache-container Attributs des cluster-passivation-store Elementes geändert werden kann – zum Beispiel in der standalone-ha.xml oder domain.xml Konfigurationsdatei.

Auf der Transportschicht basiert die Kommunikation auf JBoss Remoting welches wiederum auf XNIO basiert. In der org.jboss.xnio.Options Implementierung findet man viele bekannte XNIO Konfigurationsoptionen für die Transportschicht und in der org.jboss.remoting3.RemotingOptions Implementierung sind weitere Remoting-spezifische Konfigurationsoptionen enthalten.

Wie man im obigen Beispiel sieht, können diverse Properties nicht nur für den ganzen Cluster, sondern auch für einzelne Server gesetzt werden. Konfiguration für einzelne Server hat dabei Vorrang gegenüber clusterweiter Konfiguration. Das ermöglicht eine feingranulare Konfiguration für Clusterverbindungen und spezifische Knoten.

Zusammenfassung

Die EJB Client Bibliothek bietet eine flexible und effiziente Möglichkeit auf entfernten EJB Komponenten zuzugreifen. Lastverteilung und Failover können mit geringen Konfigurationsaufwänden erreicht werden. Dennoch ermöglichen zahlreiche Konfigurationsmöglichkeiten eine feingranulare Konfiguration der Verbindungs- und der Transportart, sowohl für einzelne Applikationsserver als auch für Gruppen von Servern.

Des Weiteren ist es auch möglich die EJB Client Bibliothek für Aufrufe eines entfernten JBoss Applikationsservers zu verwenden. Der Hauptunterschied ist, dass ein EJB Client dann in der META-INF/jboss-ejb-client.xml Datei konfiguriert wird und nicht in jboss-ejb-client.properties Datei.

Wenn Sie Fragen haben, schreiben Sie einfach einen Kommentar oder schreiben Sie uns eine E-Mail:

      • lars.kuettner (at) akquinet.de
      • heinz.wilming (at) akquinet.de

In dieser Blogpostserie werden noch zwei weitere Artikel erscheinen, der nächste über Messaging Cluster und der letzte über JGroups & Cloudaspekte.

Ein Gedanke zu „Lastverteilung und Failover für entfernte EJB Clients in EAP6 und JBoss AS 7

Kommentare sind geschlossen.