AEM 5.6.1 im Clusterbetrieb – Notwendige Sourcecodeanpassungen

Ausgangslage

In einem unserer Kundenprojekte, das mit AEM 5.6.1 umgesetzt wurde, kam ein Cluster von AEM-Autoreninstanzen zum Einsatz. Dadurch sollte vor allen Dingen eine hohe Ausfallsicherheit erreicht werden.

Wir haben geprüft, welche Anpassungen wir an unseren Sourcecode vornehmen mussten, um ein fehlerfreies Funktionieren im Cluster zu garantieren. Wir wollten auf jeden Fall vermeiden, dass zur selben Zeit die gleichen Schreibzugriffe auf die Contentrepositories der einzelnen Clusterknoten erfolgen, z.B. durch einen zeitgesteuerten Service. Durch die anschliessende Replikation der Änderungen käme es zu redundanten Schreiboperationen und somit zu einem unnötigen Resourcenverbrauch.

Die grundsätzliche Funktionsweise eines AEM-Clusters ist folgende:

  • In einem AEM-Autorencluster finden alle Schreibzugriffe lediglich auf einer Instanz, dem Master, statt.
  • Alle Änderungen werden auf die übrigen Clusterknoten repliziert.
  • Dadurch werden alle Clusterknoten synchron gehalten.
  • Ein Clusterknoten ist stets der Master.
  • Wird der Master heruntergefahren, so wird automatisch einer der anderen Slave-Clusterknoten zum Master.

Clusterarten

Bei dem Cluster in unserem Projekt handelte es sich um einen active/active-Cluster, d.h., die Autoren greifen über einen Loadbalancer auf die Instanzen zu. Alternativ wäre auch denkbar, einen der Clusterknoten im Hot-Standby zu betreiben. Die Autoren arbeiten in dieser Variante (active/passiv-Cluster) nur auf einem Clusterknoten.

authorcluster

Es ist grundsätzlich möglich, den Cluster so zu konfigurieren, dass sich die Knoten einen gemeinsamen Datastore teilen. Bei unserem Projekt war allerdings Datenredundanz gefordert, so dass wir auf einen geteilten Datastore verzichteten. Jede Instanz verfügt über einen eigenen Datastore.

Notwendige Sourcecodeanpassungen

Um herauszufinden, welche Sourcecodeanpassungen wir für den Clusterbetrieb vornehmen müssen, haben wir folgende Kandidaten untersucht:

  • Scheduled Services.
  • Workflow Prozesse.
  • Eventhandler.
  • Sonstige Services.

1.) Scheduled Services

Hierbei handelt es sich um OSGI-Services, die zeitgesteuert ablaufen, entweder zu bestimmten Zeitpunkten, die anhand einer CRON-Expression festgelegt werden, oder in bestimmten Zeitabständen (Aktualisierungsintervall). Würden die Services auf allen Clusterinstanzen gleichzeitig ausgeführt werden, so käme es zu redundanten Schreibzugriffen, zu unnötigen Replizierungen zwischen den Clusterknoten und damit zu unnötigem Resourcenverbrauch. Viel besser wäre es, wenn solch ein Service nur auf einer Instanz laufen würde. Alle Änderungen würden dann auf die anderen Knoten repliziert werden.

Um dieses sicherzustellen, stellt die SLING-API eine einfache Möglichkeit zur Verfügung. Voraussetzung hierfür ist, dass die scheduled Services mit dem Whiteboard-Pattern umgesetzt werden, und nicht direkt mit der Scheduling-API. Beim Whiteboard-Pattern wird die gesamte Scheduler-Funktionalität ausschließlich über Annotationen gesteuert:

@Service
@Properties(
{
@Property(name = "scheduler.expression", value = "1 1 1 ? * 1"),
@Property(name = "scheduler.concurrent", boolValue = false) }

)
public class MyScheduledServiceImpl implements Runnable {
...
}

Die Property scheduler.expression enthält die CRON-Expression, scheduler.concurent gibt an, ob der Job starten soll, wenn ein voriger Lauf des Jobs noch nicht beendet ist (true), oder ob die Ausführung erst dannn beginnen soll, wenn ein vorhergehender Lauf abgeschlossen ist (false). Der Defaultwert für diese Property ist false.
Statt scheduler.expression kann mit der Property scheduler.period ein Ausführungsintervall in Sekunden angegeben werden.

Durch Hinzufügen einer einzelnen Property kann festgelegt werden, dass der Job ausschliesslich auf der Master-Instanz ausgeführt wird:

...
@Property(name = "scheduler.runOn", value = "LEADER")
...

Ist abzusehen, dass in einem Projekt irgendwann ein Cluster eingesetzt werden soll, so empfiehlt es sich, die Property scheduler.runOn bereits in die scheduled Services eingetragen. Im Non-Cluster-Betrieb macht das keinen Unterschied. Eine Einzelinstanz betrachtet AEM als Master.

Näheres zum Whiteboard-Pattern finden Sie hier: Sling Scheduled Services

2.) Workflow Prozesse

Bei Workflow-Prozessen sind keine Anpassungen notwendig. Das AEM Job-Offloading sorgt dafür, dass Workflow-Prozesse nach dem Roundrobin-Prinzip verteilt werden und nur auf einer Clusterinstanz laufen. Dies gilt sowohl für die bereits von Adobe mitgelieferten Workflows als auch für Workflow-Prozesse, die projektspezifisch erstellt wurden.

Joboffloading

3.) Eventhandler

Wenn ein Service das Interface org.osgi.service.event.EventHandler implementiert, wird bei jedem Repository-Event die Methode handleEvent(Event event) aufgerufen.

public class MyEventListener implements EventHandler {

@Override
public void handleEvent(final Event event) {
...
}

}

Dies geschieht in einem Cluster nur auf der Instanz, auf der der Event ausgelöst wurde. Daher sind spezifische Anpassungen bei EventHandlern nicht erforderlich.

4.) Sonstige Services

Bei der Implementierung von Services sollte immer geprüft werden, ob eine explizite Festlegung des ausführenden Clusterknotens notwendig ist ( z.B., wenn schreibende Zugriffe in der activate-Methode eines Services ausgeführt werden sollen).

Um dies programmatisch sicherzustellen, gibt es zwei Möglichkeiten:

  • Via TopologyEventListener
  • Via Repository.getDescriptor()

TopologyEventListener

Implementiert ein Service das Interface org.apache.sling.discovery.TopologyEventListener, so wird bei jeder Änderung der Clustertopologie (z.B. Hochfahren des Clusters, Herunterfahren eines Clusterknotens) die Methode handleTopologyEvent(TopologyEvent) ausgelöst. Anhand des TopologyEvents kann jetzt ermittelt werden, ob der Service auf der Masterinstanz läuft. In diesem Fall kann man ein Flag (z.B. isRunningOnMaster) setzen. Mit Hilfe dieses Flags kann man bestimmen, ob Code auf dieser Instanz ausgeführt werden soll.

@Component(metatype = true)
@Service(value = { TopologyEventListener.class })

public class MyClusterAwareService implements TopologyEventListener {

private Boolean isRunningOnMaster= Boolean.FALSE;

public void handleTopologyEvent(final TopologyEvent event) {
if ( event.getType() == TopologyEvent.Type.TOPOLOGY_CHANGED || event.getType() == TopologyEvent.Type.TOPOLOGY_INIT) {
this.isRunningOnMaster= event.getNewView().getLocalInstance().isLeader();
}
}
...

if (this.isRunningOnMaster) {
// code that shall only be executed on cluster leader.
}

...

}

Da hier bei jeder Prüfung lediglich eine Flag-Variable abgefragt wird, ist dies die bevorzugte Methode. Sie wird deswegen auch von ADOBE empfohlen.

Repository.getDescriptor()

Alternativ ist es auch möglich, direkt am Repository zu ermitteln, ob der Code auf der aktuellen Masterinstanz läuft. Hierzu muss man eine Referenz zum SlingRepository hinzufügen.

@Reference
private SlingRepository repository;

private boolean isRunningOnMaster= Boolean.FALSE;

...

String strIsMaster = this.repository.getDescriptor("crx.cluster.master");
if (!StringUtils.isEmpty(strIsMaster)) {
isRunningOnMaster = Boolean.parseBoolean(strIsMaster);
}

...

Zusammenfassung

Durch die Möglichkeiten, die AEM bietet (Whiteboard Pattern, TopologyEventListener) war es mit vertretbaren Aufwand möglich, unseren Sourcecode an den Clusterbetrieb anzupassen. Wird der angepasste Code auf einer Einzelinstanz ausgeführt und nicht auf einem Cluster, so funktioniert er genauso wie vor der Anpassung. Das liegt daran, dass AEM eine Einzelinstanz wie einen Cluster mit nur einer Instanz betrachtet, die gleichzeitig der Master ist. Aus diesem Grunde sollte man überlegen, ob man nicht von vornherein bei der Entwicklung die Clusterfähigkeit berücksichtigt. In diesem Falle wäre eine nachträgliche Umstellung nicht mehr erforderlich.

Hinterlasse eine Antwort

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind markiert *

*

*

Du kannst folgende HTML-Tags benutzen: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>