Übersicht
Im kürzlich erschienenen Blog-Post Clustering im JBoss AS 7/EAP 6 haben wir gezeigt, wie in der neuen EAP 6 und im JBoss AS 7 einfaches Clustering verwendet werden kann. Die EAP 6 ist im Grunde genommen ein AS 7 mit offiziellem RedHat-Support. Der Cluster, den wir in diesem Post verwendet haben, war relativ klein und schlicht. Dieser Post behandelt deutlich komplexere Cluster-Strukturen, wie man diese baut und wie man den neuen Domain-Mode für Cluster verwendet. Es gibt viele Wege größere JBoss Cluster-Umgebungen zu bauen und zu verwalten. Wir werden zwei Möglichkeiten zeigen, um das zu erreichen: Die eine verwendet Separierungstechniken, die auch für ältere JBoss-Versionen verwendet werden können und die andere nutzt ein Infinispan-Feature, das Distribution genannt wird.
Skalierbarkeit vs. Verfügbarkeit
Die größte Schwierigkeit beim Bauen eines Clusters ist es, sowohl hohe Verfügbarkeit als auch Skalierbarkeit zu erreichen.
Verfügbarkeit für einen Cluster bedeutet: Wenn ein Knoten ausfällt werden alle Sessions dieses Knotens nahtlos von einem anderen Knoten übernommen. Dies kann durch Session-Replikation erreicht werden. Session-Replikation ist vorkonfiguriert und aktiviert im ha Profil in der Datei domain.xml. Flache Replikation bedeutet, dass alle Sessions auf alle anderen Knoten kopiert werden: Bei vier Knoten mit je 1 GB Speicher kann der Cluster nur 1 GB Speicher nutzen, weil alle Knoten jeweils Kopien der anderen Knoten speichern. Der Cluster wird also nicht 4 * 1 GB = 4 GB Speicher haben. Wenn man weitere Knoten hinzufügt erhält man nicht mehr Speicher, man verliert sogar Speicher durch den anfallenden Overhead der Replikation. Allerdings erhält man eine höhere Verfügbarkeit und noch wichtiger: mehr Netzwerkverkehr ebenfalls verursacht durch den Overhead der Replikation (alle Änderungen müssen auf alle anderen Knoten verteilt werden). Diese Cluster-Topologie nennen wir vollständige Replikation.
Skalierbarkeit bedeutet, wenn dem Cluster mehr Knoten hinzugefügt werden, liefert dieser mehr Rechenleistung. Mit Rechenleistung ist beides gemeint: CPU-Leistung und Speicher. Hätte man einen Cluster mit einigen Knoten, die identisch sind, aber nichts voneinander wissen, würde ein Lastverteiler dafür sorgen, dass jeder Knoten mit Arbeit versorgt wird. Dieses Konzept skaliert sehr gut, aber wenn ein Knoten ausfällt, sind die Daten verloren – Pech für den Nutzer, der gerade seinen virtuellen Einkaufswagen randvoll geladen hat. Das Cluster-Konzept hat aber einen weiteren Vorteil. Man könnte einem Knoten alle Sessions wegnehmen, die Anwendung, den JBoss oder das Betriebssystem aktualisieren, dann wieder hochfahren und mit einem anderen Knoten weitermachen. Das würde mit einem ha-Cluster nicht funktionieren, wegen der serialversionUIDs und einigen weiteren möglichen Kompatibilitätsproblemen. Zugegeben, für komplexere Live-Updates, die das Datenbankschema betreffen oder Ähnliches ist das nicht so einfach. Es gibt aber eine Tendenz, dass Updates in dieser Cluster-Topologie einfacher sind, als mit der zuerst vorgestellten Topologie. Diese Topologie nennen wir keine Replikation.
Wie die Namen vollständige Replikation und keine Replikation bereits verraten, sind beide Cluster-Topologien Extrema. Wir lernen aber etwas: Steigende Verfügbarkeit erhöht nicht die Rechenleistung eines Clusters, jedenfalls nicht in Bezug auf Speicher. Und, nur die Rechenleistung zu steigern führt nicht zu einer höheren Verfügbarkeit des Clusters. Die Dimensionen Skalierbarkeit (Rechenleistung) und Verfügbarkeit sind also in gewisser Weise orthogonal. Beide Parameter gleichzeitig zu steigern, ist weitaus komplexer und wird in den nächsten Abschnitten erklärt.
Skalierbare Hochverfügbarkeitscluster
Wie bereits erwähnt, präsentieren wir zwei Arten, um ein Cluster zu bauen, das sowohl hochverfügbar als auch skalierbar ist. Die erste Möglichkeit nutzt ein Konzept, das wir Sub-Cluster nennen und die Zweite nutzt ein Feature des Infinispan-Caches, das Distribution genannt wird.
Sub-Cluster verwenden
Topologie-Konzept
Dieser Cluster wird ein skalierbarer Cluster, der aus mehreren Sub-Clustern aufgebaut ist. Diese Sub-Cluster sind jeweils hochverfügbar. Das heißt, die Knoten eines Sub-Clusters replizieren sich gegenseitig. Die Knoten verschiedener Sub-Cluster replizieren sich dagegen nicht. Der komplette Cluster wird hochskaliert, indem zusätzliche Sub-Cluster hinzugefügt werden.
Wie viele Sub-Cluster verwendet werden und wie viele Knoten die Sub-Cluster enthalten hängt von der Anwendung ab. Zunächst ist klar, dass die Größe der Sub-Cluster nach unten hin durch Anforderungen an die Verfügbarkeit begrenzt ist. Die Anzahl der Sub-Cluster ist nach unten durch die Anforderungen an die Rechenleistung begrenzt. Die verschiedenen Sub-Cluster können über das Internet verteilt sein. Vielleicht sind einige Sub-Cluster lokal am Firmenstandort und einige weitere bei verschiedenen Cloud-Providern oder anderen Server-Farmen. Ein Sub-Cluster, der sich über größere Infrastrukturgrenzen erstreckt ist hingegen keine gute Idee, da die Performance dann stark sinken würde.
Wir empfehlen, den Domain-Mode zu verwenden. Die Sub-Cluster entsprechen dann je einer Server-Gruppen.
Aufsetzen eines Beispiel-Clusters
Für dieses Beispiel verwenden wir zwei Sub-Cluster mit zwei Knoten in jedem Sub-Cluster. Zunächst wird eine Domäne mit fünf Servern aufgesetzt. Der erste Server wird als Domain-Controller aufgesetzt und die vier anderen als normale Hosts. Im letzten Blogpost dieser Serie wurde genauer beschrieben, wie das gemacht wird. Jeder Sub-Cluster wird von einer Server-Gruppe repräsentiert. Wir erstellen zwei Server-Gruppen: subcluster1 und subcluster2.
Jetzt wird die Datei host.xml auf den Knoten editiert und die Server werden der Gruppe hinzugefügt. Wenn man jetzt die Server startet, fällt etwas auf: Die vier Server replizieren sich gegenseitig – dein Cluster ist nicht skalierbar aber viel verfügbarer als geplant.
Unkontrollierte Replikation verhindern
Standardmäßig finden sich geclusterte JBoss-Server im selben Netzwerk gegenseitig und replizieren alle Sessions von allen Anwendungen, die sie gemeinsam haben. Wenn wir mehrere Sub-Cluster erstellen wollen, müssen wir dieses Verhalten verhindern. Wir möchten, dass nur spezifische Server sich gegenseitig replizieren. JBoss-Server kleben so aneinander, weil sie dieselben Multicastgruppen nutzen. Also müssen wir nur diese ändern. Das ist die Standard-Socket-Binding-Gruppe von ha-socket in der Datei domain.xml:
[code language=”xml” gutter=”false”]
<socket-binding-group name="ha-sockets" default-interface="public">
<!– Needed for server groups using the ‘ha’ profile –>
<socket-binding name="ajp" port="8009"/>
<socket-binding name="http" port="8080"/>
<socket-binding name="https" port="8443"/>
<socket-binding name="jgroups-diagnostics" port="0" multicast-address="224.0.75.75" multicast-port="7500"/>
<socket-binding name="jgroups-mping" port="0" multicast-address="${jboss.default.multicast.address:230.0.0.4}" multicast-port="45700"/>
<socket-binding name="jgroups-tcp" port="7600"/>
<socket-binding name="jgroups-tcp-fd" port="57600"/>
<socket-binding name="jgroups-udp" port="55200" multicast-address="${jboss.default.multicast.address:230.0.0.4}" multicast-port="45688"/>
<socket-binding name="jgroups-udp-fd" port="54200"/>
<socket-binding name="modcluster" port="0" multicast-address="224.0.1.105" multicast-port="23364"/>
<socket-binding name="osgi-http" interface="management" port="8090"/>
<socket-binding name="remoting" port="4447"/>
<socket-binding name="txn-recovery-environment" port="4712"/>
<socket-binding name="txn-status-manager" port="4713"/>
<outbound-socket-binding name="mail-smtp">
<remote-destination host="localhost" port="25"/>
</outbound-socket-binding>
</socket-binding-group>
[/code]
Man sieht, dass vier Multicasts konfiguriert sind: jgroups-diagnostics, jgroups-mping, jgroups-udp und modcluster. jgroups-diagnostics und modcluster sollten ihre Werte für alle Sub-Cluster behalten. jgroups-mping und jgroups-udp dagegen brauchen für jeden Sub-Cluster andere Werte. Wie man sieht, ist ihre Multicast-Adresse auf den Wert der Property jboss.default.multicast.address gesetzt. Wir werden später unterschiedliche Werte für diese Property in jeder Server-Gruppe (also Sub-Cluster) setzen.
Mod-Cluster
Wenn der modcluster-Multicast auf dem Standardwert belassen wird, werden alle Knoten von allen Sub-Clustern von der Apache-Seite von mod_cluster gesehen. Also betreibt mod_cluster Lastverteilung durch und sieht viele Knoten, die alle dieselbe Anwendung deployt haben. Es nimmt an, dass ein Fail-Over und Ähnliches durchgeführt werden kann, und zwar auf allen Knoten von allen Sub-Clustern. Wir müssen das modcluster-Subsystem des JBoss-Servers konfigurieren, sodass jeder Sub-Cluster einer anderen Lastverteilungsgruppe zugeordnet wird. Das können wir in der Datei domain.xml im betreffenden Profil wie folgt einstellen:
[code language=”xml” gutter=”false”]
<subsystem xmlns="urn:jboss:domain:modcluster:1.1">
<mod-cluster-config advertise-socket="modcluster" connector="ajp" load-balancing-group="${mycluster.modcluster.lbgroup:StdLBGroup}">
<!– some more stuff–>
</mod-cluster-config>
</subsystem>
[/code]
Setzen der Properties
Wir nutzen eine Property als Wert für die Lastverteilungsgruppe, wie wir es für die Multicasts bereits getan haben. Dadurch können wir dasselbe Profil oder dieselbe Socket-Binding-Gruppe in verschiedenen Versionen verwenden. Das heißt, dass wir nicht für jeden Sub-Cluster ein neues Profil oder eine neue Socket-Binding-Gruppe erstellen müssen, was ja sehr nervenaufreibend wäre. Properties für eine Server-Gruppe setzen ist ziemlich einfach und selbsterklärend:
[code language=”xml” gutter=”false”]
<server-groups>
<server-group name="subcluster1" profile="ha">
<system-properties>
<property name="jboss.default.multicast.address" value="230.0.1.1"/>
<property name="mycluster.modcluster.lbgroup" value="LBGroup1"/>
</system-properties>
<socket-binding-group ref="ha-sockets"/>
</server-group>
<server-group name="subcluster2" profile="ha">
<system-properties>
<property name="jboss.default.multicast.address" value="230.0.1.2"/>
<property name="mycluster.modcluster.lbgroup" value="LBGroup2"/>
</system-properties>
<socket-binding-group ref="ha-sockets"/>
</server-group>
</server-groups>
[/code]
Anmerkung: Man kann Properties an unterschiedlichen Orten der Konfiguration setzen. Mehr dazu hier.
Wie man einen Cluster skaliert
Zunächst das Hochskalieren: Falls das noch nicht erledigt wurde, brauchen wir eine neue Server-Gruppe auf dem Domain-Controller. Im letzten Blog-Post haben wir gezeigt, wie eine Anwendung mit dem Kommandozeileninterface (CLI) deployt wird. Jetzt verwenden wir das CLI, um eine neue Sever-Gruppe auf dem laufenden Domain-Controller zu erstellen. Man verbindet das CLI mit dem Domain-Controller und führe die folgenden drei Befehle aus:
[code gutter=”true”]
/server-group=server-group3:add(profile=ha,socket-binding-group=ha-sockets)
/server-group=server-group3/system-property=jboss.default.multicast.address:add(value=230.0.1.3)
/server-group=server-group3/system-property=mycluster.modcluster.lbgroup:add(value=230.0.1.3)
[/code]
Diese Befehle erstellen eine neue Server-Gruppe namens server-group3, die von außen genau wie server-group2 oder server-group1 aussieht.
Jetzt müssen noch zwei Dinge erledigt werden:
- Die Mitglieder des Sub-Clusters erstellen und konfigurieren. Dabei sollte man nicht vergessen, die Server in die neue Server-Gruppe zu konfigurieren.
- Die benötigten Deployments der neuen Server-Gruppe hinzufügen.
- (Die Server starten.)
Das war‘s! Damit ist der Cluster hochskaliert, durch Hinzufügen eines neuen Sub-Clusters.
Beim Herunterskalieren des Clusters muss besonders auf bereits existierende Sessions geachtet werden. Aus diesem Grund können nicht einfach alle Server dieses Sub-Clusters gestoppt werden. Der erste Schritt ist es sicherzustellen, dass alle Sub-Cluster, die wir entfernen wollen keine neuen Sessions bekommen. Wenn mod_cluster verwendet wird, ist das ziemlich einfach: Es gibt die verschiedensten Disable-Buttons. Der richtige ist der große Disable-Nodes-Button, der auf dem folgenden Screenshot markiert ist. Wenn dieser Button angeklickt wird, wird mod_cluster keine neuen Sessions in den entsprechenden Sub-Cluster weiterleiten. Allerdings werden existierende Sessions immer noch von unserem Sub-Cluster bearbeitet. Jetzt müssen wir warten bis all diese Sessions verschwinden. Dann können die Server mit dem CLI oder der Web-Console angehalten werden. Die noch existierende, aber ungenutzte Server-Gruppe stört nicht weiter und kann eventuell wiederverwendet werden, wenn der Cluster zu einem späteren Zeitpunkt wieder hochskaliert wird, aber sie kann auch gelöscht werden.
Ein anderer Ansatz: Infinispan-Distribution
Infinispan ist ein verteilter Cache und spielt eine große Rolle beim Clustering. Wie im ersten Post dieser Reihe erklärt wurde, nutzen das Standard-ha-Profil oder standalone-ha.xml Infinispan zu vier Dingen:
- Verteilung und Caching von Web-Sessions auf den Cluster (Container: web)
- Verteilung und Caching von Stateful-Session-Beans auf den Cluster (Container: ejb)
- 2nd-Level Cache für Hibernate (Container: hibernate)
- Verteilung einiger allgemeiner Objekte auf den Cluster (Container: cluster)
Infinispan hat viele unterschiedliche Betriebsmodi. Für diesen Post gibt es zwei relevante Modi: Replikation zum einen ist der Standard-Modus, das bedeutet, dass ein im Cache-Container geändertes Objekt auf alle Cluster-Nodes weiterverteilt wird. Jeder Clusterknoten hat die gleichen Daten. In diesem Modus skaliert der Speicher des Clusters nicht mit der Anzahl der Clusterknoten. Der zweite Modus zum anderen wird Distribution genannt. Dieser Modus skaliert hoch: Man kann definieren, wie viele Kopien eines Objekts der Cluster halten soll. Diese Zahl ist konstant und steigt daher nicht mit der Anzahl der Clusterknoten. Also skaliert der Speicher des Clusters mit der Anzahl seiner Knoten.
Infinispan ist ein Key-Value-basierter Cache und im Distributed-Modus nutzt er Consistent-Hashing, um zu bestimmen, auf welchen Clusterknoten sich die konstante Anzahl an Kopien, num_copies, befinden werden. Wenn ein Objekt O in den verteilten Cache auf dem Knoten K gelegt wurde, wird es nicht zwangsweise auch in den lokalen Cache von K gelegt. Consistent-Hashing stellt im Allgemeinen nicht sicher, dass ein Objekt in den lokalen Cache gelegt wird. Es stellt nur sicher, dass es in num_copies Caches getan wird. Auf der anderen Seite kann ein 1st-Level-Cache für den verteilten Cache aktiviert werden. Dieser 1st-Level-Cache ist dafür zuständig Objekte des Clusters für einen bestimmten, konfigurierbaren Zeitraum zu cachen (l1-lifespan).
Topologie-Konzept
Dieser Cluster wird keine sehr komplexe Topologie haben. Zunächst müssen zwei unabhängige Domänen aufgesetzt werden, damit Live-Updates durchgeführt werden können. Jede Domäne hat eine produktive Server-Gruppe, die alle Knoten enthält. Bei den Knoten wird Infinispan-Distribution für die Cluster des Cache-Containers, cluster, web und ejb, aktiviert Das war’s. Der Cluster wird hochskaliert, indem weitere Knoten zu der Server-Gruppe hinzugefügt werden.
Es gibt zwei wichtige Tuning-Parameter für diesen Cluster: num_copies und die Anzahl der Cluster-Nodes. Der Parameter num_copies wird nach unten durch die Anforderungen an die Verfügbarkeit begrenzt. Das kommt daher, dass bei steigendem Wert für num_copies mehr Knoten ausfallen können, ohne, dass Daten verlorengehen. Die Anzahl der Clusterknoten ist allerdings nach unten begrenzt durch num_copies, weil nicht weniger Kopien als num_copies existieren können, wenn nicht mindestens so viele Clusterknoten existieren. Die Anzahl der Clusterknoten ist natürlich auch nach unten begrenzt durch die Anforderungen an die Rechenleistung des Clusters.
Infinispan-Distribution konfigurieren
Dieser Ansatz ist theoretisch verhältnismäßig einfach zu konfigurieren. Man öffnet die Datei domain.xml, lokalisiert das Infinispan-Subsystem im relevanten Profil und
- für den Container cluster muss der replizierte Cache in einen Distributed-Cache konvertiert werden
- für den Container web muss der Default-Cache auf dist geändert werden
- für den Container ejb muss der Default-Cache auf dist geändert werden
Die Anzahl der Kopien eines gecachten Objekts kann mit dem Attribut owners des distributed-cache Tags kontrolliert werden. Der Standardwert dieses Attributs ist „2“.
Mit dem AS 7 funktioniert dies momentan nicht. Wir sind auf folgenden Bug gestoßen: AS7-4881. Während dieser Post geschrieben wurde, wurde der Bug im Trunk gefixt. Der Source-Code kann von github heruntergeladen und der AS 7 kompiliert werden. Andernfalls kann ein nightly Build hier heruntergeladen werden.
Zusammenfassung
Das Clustering einer größeren Umgebung erfordert detaillierte Konfigurationen. Wir haben unterschiedliche Ansätze behandelt, wie man einen komplexeren Cluster erstellt. Eine Sache sollte man jedoch nicht vergessen: Einfach mehr Knoten hinzufügen macht ein Cluster nicht immer leistungsstärker. Weiterhin ist es wichtig Systeme Dritter, wie Datenbanken, nicht unbeachtet zu lassen. Wenn man nur einen Datenbank-Server benutzt, wird der Cluster irgendwann nicht mehr hochskalieren, weil der Datenbank-Server nicht mehr Requests verarbeiten kann. Behalte diese Abhängigkeiten im Hinterkopf, sogar ein Mail-Server könnte irgendwann zum Problem werden. Zwei wichtige Dinge wurden bis jetzt noch nicht abgedeckt: Messaging und ejb-Calls von einem Java-Client (also von einer Web-Anwendung durch modcluster). Der nächste Post deckt Load-Balancing und Fail-Over von Standalone-Remote-EJB-Clients ab.
Fragen oder Feedback? Schreiben Sie einfach einen Kommentar oder eine E-Mail an:
- heinz.wilming (at) akquinet.de
- immanuel.sims (at) akquinet.de