Clustering im JBoss AS 7 und EAP 6

Übersicht

Die Möglichkeit verschiedene Server zu einem Cluster zu kombinieren, der seine internen Server vor den Clients verbirgt und eine virtuelle Plattform für eine Anwendung zur Verfügung stellt, ist für Geschäftsanwendungen wichtig. Clustering wird verwendet, um u. a. Folgendes zu erreichen:

  • Hohe Skalierbarkeit, indem dem Cluster günstige Rechnerressourcen auf Nachfrage hinzugefügt werden, oder
  • Hohe Verfügbarkeit, indem ein transparentes Failover verwendet wird, welches Ausfälle der internen Server verbirgt.

Normalerweise beschränken sich hohe Skalierbarkeit und hohe Verfügbarkeit gegenseitig. Es ist aber auch möglich, beides zu erreichen. So kann der JBoss Application Server konfiguriert werden, um beide Eigenschaften zu unterstützen.

Dieser Post ist der erste einer Blogpostserie über Clustering mit dem JBoss AS 7. Hier setzen wir den Fokus auf die grundlegenden Konzepte hinter JBoss AS 7 Clustering und zeigen, wie man eine grundlegende geclusterte Umgebung mit einer einfachen Java EE-Anwendung aufsetzt.

In dieser Serie konzentrieren wir uns auf JBoss AS 7 beziehungsweise die EAP 6, welche die von Red Hat unterstützte Version des JBoss Application Servers ist. Zukünftige Posts werden bestimmte Subsysteme des JBoss AS behandeln, wie HornetQ oder Infinispan.

Clustering Subsystem des JBoss AS 7

Um Hochverfügbarkeit zu unterstützen, müssen die Daten innerhalb des Clusters repliziert werden. Dies stellt sicher, dass der Absturz eines Servers nicht zu Datenverlust führt. Im AS 7 wird der verteilte Cache Infinispan als Fundament für die Replikation verwendet. Eine Java EE-Anwendung speichert Daten in verschiedenen Schichten. Dementsprechend hat der AS 7 vier vorkonfigurierte Cache-Container für die Replikation zwischen Clusterknoten:

  • web – Replikation von HTTP-Sessions
  • sfsb – Replikation von Stateful-Session-Beans
  • hibernate – Second-Level Entity-Cache für JPA/Hibernate
  • cluster – Replikation von allgemeinen Objekten in einem Cluster

Um tatsächlich replizierte Daten zwischen Clusterknoten zu übertragen, benutzt Infinispan JGroups als zugrundeliegendes Subsystem. JGroups unterstützt die Erstellung von Gruppen verteilter Knoten. Es stellt Operationen zur Verfügung, um neue Knoten hinzuzufügen, Knoten explizit zu entfernen und fehlerhafte Knoten automatisch auszusortieren. Der JBoss AS 7 beinhaltet zwei Protokoll-Stacks für verlässliche Kommunikation innerhalb einer Gruppe von Knoten. Einen UDP-basierten Protokoll-Stack, der IP-Multicasts verwendet, und einen TCP-basierten Protokoll-Stack für Umgebungen, die Multicast-Kommunikation nicht unterstützen. UDP ist der standardmäßige Protokoll-Stack im JBoss AS 7.

Um hohe Skalierbarkeit zu unterstützen, müssen die Anfragen der Clients unter den Clusterknoten verteilt werden (Lastverteilung). Für die Lastverteilung über HTTP-Clients unterstützt der AS 7 das mod_cluster-Modul für den Apache httpd Server. Es bietet intelligente und dynamische Lastverteilung für Web-Anwendungen. Es verwendet den AJP-Connector und kommuniziert zusätzlich über UDP mit dem httpd-Server-Modul. Im Gegensatz zu dem älteren mod_jk wird der httpd z. T. dynamisch konfiguriert.

Lastverteilung für EJB Remote-Clients wird von der JBoss Client Library für EJB-Anwendungen unterstützt. Diese Bibliothek erhält die Topologie des Clusters über die bereitgestellte Implementierung für entfernte Aufrufe. Standardmäßig wird ein Random-Node-Selector zur Lastverteilung von entfernten EJB-Aufrufen verwendet. Ein Remote-Client verwendet JBoss remote naming als Verzeichnisdienst mit einem JNDI-Interface, um auf den RMI-Stub zuzugreifen.

Die Beispiel-Anwendung

Um Clustering zu demonstrieren, beginnen wir mit einer einfachen Beispiel-Anwendung. Sie beinhaltet eine Stateful-Session-Bean, welche die Aufrufe zählt und die Implementierung einer stateless Session-Bean, die den Namen ihres Clusterknotens zurückgibt. Beide Session-Beans werden von einer JSF-Page aufgerufen. Die Anwendung liegt auf github im Verzeichnis cluster-example/. Die Anwendung wird mit Maven gebaut indem das mvn package Kommando ausgeführt wird. Das gepackte Web-Archiv ist dann im Verzeichniscluster-example/target zu finden.

Das Ziel der Beispiel-Anwendung ist es, zu demonstrieren, wie man eine Anwendung für Clustering auf unterschiedlichen Ebenen konfiguriert.

Clustering EJB Session-Beans

Enterprise Java Beans (EJB) sind die Kernkomponenten einer Java EE-Anwendung. EJBs stellen Transaktions- und Sicherheits-Management zur Verfügung und die meisten EJB-Container unterstützen auch Clustering.

Clustering einer Stateless-Session-Bean

Das Clustering einer Stateless-Session-Bean ist sehr einfach, dazu muss nur die Bean-Klasse mit @org.jboss.ejb3.annotation.Clustered annotiert werden oder die zugehörigen Tags im JBoss-spezifischen Deployment-Descriptor für EJB-Komponenten hinzugefügt werden.

@Stateless
@Clustered
@Named
public class ClusteredStatelessBean {
    private final static Logger LOG = 
            Logger.getLogger(ClusteredStatelessBean.class.getName());
  
    public String getNodeName() {
        LOG.info("invoke getNodeName()");
        try {
            String jbossNodeName = 
                    System.getProperty("jboss.node.name");
 
 
            return jbossNodeName != null ? jbossNodeName : 
                    InetAddress.getLocalHost().getHostName();
 
        } catch (UnknownHostException e) {
            throw new RuntimeException(e);
        }
    }
}

Diese beispielhafte Stateless-Session-Bean unserer Anwendung hat eine einfache Methode getNodeName(), die den Namen des Clusterknotens, der die Anfrage bearbeitet, zurückgibt.

Aber warum eine Stateless-Session-Bean clustern? Der einfache Grund ist, dass über die Stateless-Session-Beans dynamische Lastverteilung auf Clusterknoten betrieben werden kann. Das kann für rechenintensive Aufgaben nützlich sein. Daher ermöglicht Clustering von Stateless-Session-Beans Skalierbarkeit. Da sie keine Daten kapseln, besteht kein Risiko Informationen zu verlieren, wenn ein Knoten abstürzt. Somit ist Verfügbarkeit kein Problem.

Clustering einer Stateful-Session-Bean

Das Clustering einer Stateful-Session-Bean ist etwas komplexer als das Clustering einer Stateless-Session-Bean, weil ihr Zustand zwischen den Clusterknoten verwaltet werden muss. Dies übernimmt der Application-Server, aber der Entwickler sollte sich der hinzugefügten Komplexität bewusst sein, die die Skalierbarkeit reduzieren kann.

Die Konfiguration des JBoss, eine Stateful-Session-Bean zu clustern ist die gleiche wie für Stateless-Session-Beans: Die Bean-Klasse wird mit @org.jboss.ejb3.annotation.Clustered annotiert oder die zugehörigen Tags werden im XML-Deployment-Descriptor hinzugefügt. In der Standard-Konfiguration des AS 7 wird eine Stateful-Session-Bean mit anderen Cluster-Nodes nach jedem Aufruf repliziert. Also ist der Status der Session auch noch verfügbar, wenn ein Clusterknoten stirbt. Status-Replikation einer Stateful-Session-Bean bedeutet, dass die Bean nach jedem Aufruf passiviert wird und dann im ejb Cache-Container gespeichert wird. Beim nächsten Aufruf wird die Bean wieder aktiviert. Standardmäßig erhalten die anderen Knoten den neuen Status durch den Infinispan-Cache asynchron.

@Stateful
@SessionScoped
@Clustered
@Named
public class ClusteredStatefulBean {
 
    private final static Logger LOG = 
            Logger.getLogger(ClusteredStatefulBean.class.getName());
     
    private int counter;
 
    public int getCounterValue() {
        LOG.info("invoke getCounter()");
        return counter++;
    }
 
    @PrePassivate
    public void passivate() {
        LOG.info("passivate ejb component: " + this);
    }
 
    @PostActivate
    public void activate() {
        LOG.info("activate ejb component: " + this);
    }
}

Diese beispielhafte Stateful-Session-Bean ist eine CDI-Komponente, die mit der HTTP-Session assoziiert ist. Die Bean hat einen Zähler, der jedes Mal erhöht wird, wenn er von der Getter-Methode getCounterValue() gelesen wird. Mit dem Zähler kann verfolgt werden, wie oft die Bean während der Session bereits aufgerufen wurde.

HTTP Session-Replikation aktivieren

Replikation von HTTP-Sessions stellt sicher, dass die Sessions der Clients auf allen Clusterknoten verfügbar sind. Das bedeutet, dass ein Session-Objekt auf andere Clusterknoten repliziert wird. Intern wird das umgesetzt, indem die Session-Objekte in einen Infinispan Cache-Container gespeichert werden. Wenn ein Knoten abstürzt, kann damit im Prinzip die HTTP-Session des Clients von einem anderen Knoten fortgesetzt werden. Dafür ist aber ein externer Mechanismus erforderlich, welcher Failover und Lastverteilung für die Client-Seite (den Browser) transparent behandelt. mod_cluster kann das out-of-the-box – mehr dazu später.

Der JBoss AS wird für die Replikation der HTTP-Session konfiguriert, indem das Element <distributable/> in die web.xml hinzugefügt wird:

<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
    version="3.0">
 
    <distributable/>
</web-app>

Aufsetzen der JBoss AS 7-Instanzen

Um die Beispiel-Anwendung ausführen zu können, müssen wir einen Cluster aufsetzen. Wir werden einen vertikal skalierbaren (Verfügbarkeit) Cluster mit zwei JBoss AS 7-Instanzen aufsetzen, die sich auf derselben Maschine befinden.

Wir müssen die EAP 6 herunterladen, da wir auf mehrere bekannte Bugs getroffen sind, die mit den Clustering-Möglichkeiten des aktuellen Community Release 7.1.1.Final zusammenhängen. Diese Bugs sind im Development-Branch bereits gefixt worden. Von daher kann man auch den neusten Nightly Build ausprobieren oder den JBoss 7.1.2.Final tag von github herunterladen und das build.sh-Script ausführen.

Nach dem Download des JBoss AS muss das ZIP-Archiv in zwei verschiedene Verzeichnisse entpackt werden, beispielsweise jb1 und jb2. Für unsere Cluster-Umgebung führen wir die JBoss-Instanzen im Standalone-Modus aus. Dieser Modus ist identisch mit dem Betriebsmodus früherer JBoss AS-Versionen. Danach können wir unsere Beispiel-Anwendung deployen. Das Web-Archiv muss dazu in die beiden Verzeichnisse jb1/standalone/deployments/ und  jb2/standalone/deployments/ kopiert werden.

Bevor nun die beiden JBoss AS-Instanzen gestartet werden, schauen wir uns die Start-Kommandos an:

$./jb1/bin/standalone.sh -Djboss.node.name=jb1 \
    --server-config=standalone-ha.xml

$./jb2/bin/standalone.sh -Djboss.node.name=jb2 \
    --server-config=standalone-ha.xml \
    -Djboss.socket.binding.port-offset=100

Der erste Parameter setzt die Property jboss.node.name auf jb1 bzw. jb2. Normalerweise wird dieser Wert automatisch auf den Hostnamen der Maschine gesetzt, auf dem der Server läuft, da wir aber zwei JBoss-Instanzen auf derselben Maschine verwenden, würde durch den doppelten Namen ein Konflikt im Cluster entstehen. Der zweite Parameter --server-config teilt dem JBoss mit, dass er nicht die Standard-Konfigurationsdatei verwenden soll. Im  Verzeichnis standalone/configuration/ befinden sich einige Konfigurationsdateien, darunter auch standalone-ha.xml. Diese Datei enthält alle grundlegenden Konfigurationen für hohe Verfügbarkeit.

Da wir zwei JBoss AS 7-Instanzen auf einer Maschine laufen lassen, müssen wir Port-Konflikte vermeiden. Mit dem dritten Parameter des Start-Kommandos des zweiten JBoss AS wird für den Port ein Offset von 100 aktiviert. Offensichtlich beeinflusst diese Option auch den Port für die Management-Konsole. Wenn dieser auf 9990 konfiguriert ist, wird sie stattdessen auf Port 9990+100=10090 verfügbar sein. Ebenso werden alle anderen Ports um 100 nach hinten verschoben.

Falls verschiedene IP-Socket-Bindings anstelle des Port-Offsets verwendet werden sollen, muss die gleiche Multicast-Gruppe verwendet werden.

Jetzt starten wir mit dem ersten Knoten jb1 mit dem oben genannten Kommando. Wir werden auf der Konsole bzw. in der Logdatei sehen, dass die Cluster-Subsysteme aktiviert sind und aktuell ein Cluster-Mitglied registriert ist:

...
20:52:59,458 INFO  [org.jboss.as.clustering.infinispan] (ServerService Thread Pool -- 33) JBAS010280: Activating Infinispan subsystem.
20:52:59,459 INFO  [org.jboss.as.clustering.jgroups] (ServerService Thread Pool -- 37) JBAS010260: Activating JGroups subsystem.
...
20:52:59,643 INFO  [org.jboss.modcluster.ModClusterService] (ModClusterService lifecycle - 1) Initializing mod_cluster 1.2.1.Final-redhat-1
...
20:53:03,260 INFO  [org.jboss.as.clustering.infinispan] (CacheService lifecycle - 1) JBAS010281: Started remote-connector-client-mappings cache from ejb container
20:53:03,260 INFO  [org.jboss.as.clustering.infinispan] (CacheService lifecycle - 1) JBAS010281: Started default-host/cluster cache from web container
20:53:03,260 INFO  [org.jboss.as.clustering.infinispan] (CacheService lifecycle - 1) JBAS010281: Started repl cache from web container
20:53:03,260 INFO  [org.jboss.as.clustering.infinispan] (CacheService lifecycle - 1) JBAS010281: Started repl cache from ejb container
20:53:03,267 INFO  [org.jboss.as.clustering] (MSC service thread 1-2) JBAS010238: Number of cluster members: 1
20:53:03,267 INFO  [org.jboss.as.clustering] (MSC service thread 1-11) JBAS010238: Number of cluster members: 1
...

Als nächstes starten wir auch die zweite JBoss-Instanz jb2. Die Nachrichten auf der Konsole bzw. in der Logdatei sind identisch, außer, dass wir jetzt zwei registrierte Cluster-Mitglieder haben.

21:18:27,075 INFO  [org.jboss.as.clustering.infinispan] (ServerService Thread Pool -- 33) JBAS010280: Activating Infinispan subsystem.
21:18:27,081 INFO  [org.jboss.as.clustering.jgroups] (ServerService Thread Pool -- 37) JBAS010260: Activating JGroups subsystem.
21:18:27,262 INFO  [org.jboss.modcluster.ModClusterService] (ModClusterService lifecycle - 1) Initializing mod_cluster 1.2.1.Final-redhat-1
21:18:27,273 INFO  [org.jboss.modcluster.advertise.impl.AdvertiseListenerImpl] (ModClusterService lifecycle - 1) Listening to proxy advertisements on 224.0.1.105:23,364
21:18:28,156 INFO  [org.jboss.as.clustering.infinispan] (CacheService lifecycle - 1) JBAS010281: Started repl cache from web container
21:18:28,156 INFO  [org.jboss.as.clustering.infinispan] (CacheService lifecycle - 1) JBAS010281: Started default-host/cluster cache from web container
21:18:28,157 INFO  [org.jboss.as.clustering.infinispan] (CacheService lifecycle - 1) JBAS010281: Started repl cache from ejb container
21:18:28,165 INFO  [org.jboss.as.clustering] (MSC service thread 1-13) JBAS010238: Number of cluster members: 2
21:18:28,165 INFO  [org.jboss.as.clustering] (MSC service thread 1-3) JBAS010238: Number of cluster members: 2

Wenn wir jetzt die Ausgabe von Knoten jb1 betrachten, sehen wir, dass jb2 dem Cluster erfolgreich hinzugefügt wurde!

21:18:27,844 INFO  [org.jboss.as.clustering] (Incoming-1,null) JBAS010225: New cluster view for partition ejb (id: 1, delta: 1, merge: false) : [jb1/ejb, jb2/ejb]
21:18:27,844 INFO  [org.infinispan.remoting.transport.jgroups.JGroupsTransport] (Incoming-1,null) ISPN000094: Received new cluster view: [jb1/ejb|1] [jb1/ejb, jb/ejb]
21:18:27,849 INFO  [org.jboss.as.clustering] (Incoming-3,null) JBAS010225: New cluster view for partition web (id: 1, delta: 1, merge: false) : [jb1/web, jb2/web]
21:18:27,849 INFO  [org.infinispan.remoting.transport.jgroups.JGroupsTransport] (Incoming-3,null) ISPN000094: Received new cluster view: [jb1/web|1] [jb1/web, jb2/web]

Konfiguration von Apache httpd und mod_cluster

Was wir auch noch benötigen, ist ein Lastverteiler der auch das Failover regeln kann, wenn ein Clusterknoten ausfällt. mod_cluster ist ein solcher Lastverteiler. Zunächst muss ein Apache httpd-Server installiert werden, dann werden die mod_cluster-Module installiert und konfiguriert.

Wenn bereits eine aktuelle Version des httpd-Servers installiert ist, muss nur noch das mod_cluster dynamic libraries-Bundle heruntergeladen werden. Diese müssen in das Verzeichnis für httpd-Module kopiert werden. Alternativ kann auch eine vorkonfigurierte Version des httpd-Servers mit mod_cluster für Ihre Umgebung von der mod_cluster Download-Seite heruntergeladen werden.

Um den Apache httpd-Server zu konfigurieren, muss die primäre Konfigurationsdatei geöffnet werden (z. B. /opt/jboss/httpd/httpd.conf) und der folgende Code an das Ende der httpd.conf-Datei angehängt werden. Vergessen Sie nich, den Pfad zu den Modulen Ihrem System entsprechend anzupassen.

LoadModule proxy_module /opt/jboss/httpd/lib/httpd/modules/mod_proxy.so
LoadModule proxy_ajp_module /opt/jboss/httpd/lib/httpd/modules/mod_proxy_ajp.so
 
# load the mod_cluster modules
LoadModule slotmem_module /opt/jboss/httpd/lib/httpd/modules/mod_slotmem.so
LoadModule manager_module /opt/jboss/httpd/lib/httpd/modules/mod_manager.so
LoadModule proxy_cluster_module /opt/jboss/httpd/lib/httpd/modules/mod_proxy_cluster.so
LoadModule advertise_module /opt/jboss/httpd/lib/httpd/modules/mod_advertise.so
 
# MOD_CLUSTER_ADDS
# Adjust to you hostname and subnet.
 
<IfModule manager_module>
  Listen 127.0.0.1:6666
  ManagerBalancerName mycluster
 
  <VirtualHost 127.0.0.1:6666>
    <Location />
      Order deny,allow
      Deny from all
      Allow from 127.0.0
    </Location>
 
    KeepAliveTimeout 300
    MaxKeepAliveRequests 0
    #ServerAdvertise on http://@IP@:6666
    AdvertiseFrequency 5
    #AdvertiseSecurityKey secret  
    #AdvertiseGroup @ADVIP@:23364
    EnableMCPMReceive
 
    <Location /mod_cluster_manager>
      SetHandler mod_cluster-manager
      Order deny,allow
      Deny from all
      Allow from 127.0.0
    </Location>
  </VirtualHost>
</IfModule>

Jetzt muss der Apache httpd mit dem folgenden Kommando gestartet warden: ./opt/jboss/httpd/sbin/apachectl start

Auf den beiden Konsolen bzw. Logdateien der Clusterknoten ist dann bald eine interessante Ausgabe zu sehen:

[org.jboss.modcluster.ModClusterService] (ContainerBackgroundProcessor[StandardEngine[jboss.web]]) Engine [jboss.web] will use jvmRoute: 4e6189af-0502-3305-8ff3-fad7fee8b516

Das bedeutet, dass das ModCluster-Subsystem auf JBoss-Seite sich erfolgreich mit dem mod_cluster auf der Apache-Seite registriert hat. Wenn Sie http://127.0.0.1:6666/mod_cluster-manager in Ihrem Browser öffnen, sehen Sie einige Informationen über die Clusterknoten.

Nun können Sie mit dem Cluster, den Sie gerade gebaut haben etwas herumspielen. Auf die Beispiel-Anwendung kann unter http://127.0.0.1/cluster zugegriffen werden.

Man sieht, welcher Knoten die Anfragen bearbeitet – beenden diesen Knoten (ctrl-c im Konsolenfenster dieses Knotens) warten Sie einen Moment und laden Sie die Seite im Browser neu. Die Anfrage sollte jetzt vom anderen Knoten bearbeitet werden und noch interessanter: Es wird weder eine neue Session vergeben noch eine neue Stateful-Session-Bean. Es gibt keine sichtbaren Änderungen, außer, dass die Anfrage von einem anderen Clusterknoten bearbeitet wird. Versuchen Sie etwas mit Ihrem Cluster herumzuspielen.

Zusammenfassung und Aussicht

In diesem Post haben wir die EAP 6 Version verwendet, die völlig stabil läuft und vollständig für Clustering verwendet werden kann. Mit dem aktuellen Release 7.1.1.Final sind wir auf mehrere bekannte Bugs gestoßen. Diese wurden alle in der Version 7.1.2.Final gefixt. Für Produktivumgebungen empfehlen wir aber die unterstützte und qualitätsgesicherte EAP-Version.

Wie wir gezeigt haben, ist es sehr leicht, eine einfache Java EE-Anwendung in einer geclusterten Umgebung laufen zu lassen. In diesem Post haben wir aber weder Message-Driven-Beans noch einen Second-Level-Cache für JPA-Entitäten abgedeckt, das und noch viele weitere wichtige Themen zum Clustering sind aber als Themen für spätere Posts geplant.

Der nächste Post deckt das Management einer geclusterten Umgebung mit dem neuen Domain-Mode des JBoss AS 7 ab.

Wir stehen gerne für Fragen oder Feedback bereit:

  • immanuel.sims (at) akquinet.de
  • heinz.wilming (at) akquinet.de

2 Gedanken zu “Clustering im JBoss AS 7 und EAP 6

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden / Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden / Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden / Ändern )

Google+ Foto

Du kommentierst mit Deinem Google+-Konto. Abmelden / Ändern )

Verbinde mit %s