Maps als FIFO-Warteschlangen

Mit WebSphere eXtreme Scale können Sie eine Funktion für alle Maps bereitstellen, die einer FIFO-Warteschlange (First In/First Out) gleicht. WebSphere eXtreme Scale überwacht die Einfügereihenfolge für alle Maps. Ein Client kann eine Map nach dem nächsten nicht gesperrten Eintrag in der Einfügereihenfolge abfragen und den Eintrag sperren. Dieser Prozess ermöglicht mehreren Clients, Einträge effizient aus der Map zu konsumieren.

FIFO-Beispiel

Das folgende Code-Snippet zeigt einen Client, der in eine Schleife eintritt, um Einträge aus der Map zu verarbeiten, bis die Map abgearbeitet ist. Die Schleife startet eine Transaktion und ruft dann die Methode "ObjectMap.getNextKey(5000)" auf. Diese Methode gibt den Schlüssel des nächsten verfügbaren, nicht gesperrten Eintrags zurück und sperrt den Eintrag. Wenn die Transaktion länger als 5000 Millisekunden blockiert, gibt die Methode null zurück.
Session session = ...;
ObjectMap map = session.getMap("xxx");
// Zum Stoppen der Schleife muss folgende Zeile irgendwo definiert werden.
boolean timeToStop = false;

while(!timeToStop)
{
  session.begin();
  Object msgKey = map.getNextKey(5000);
  if(msgKey == null)
  {
    // Aktuelle Partition ist abgearbeitet. Erneut in einer neuen
    // Transaktion aufrufen, um mit der nächsten Partition fortzufahren.
    session.rollback();
    continue;
  }
  Message m = (Message)map.get(msgKey);
  // Jetzt die Nachricht konsumieren.
  ...
  // Eintrag entfernen
  map.remove(msgKey);
  session.commit();
}

Lokaler Modus versus Clientmodus

Wenn die Anwendung einen lokalen Kern verwendet, d. h., wenn sie kein Client ist, funktioniert der Mechanismus wie zuvor beschrieben.

Wenn die Java Virtual Machine (JVM) ein Client ist, stellt der Client zunächst eine Verbindung zu einem zufällig ausgewählten primären Shard der Partition her. Wenn keine Arbeit in dieser Partition vorhanden ist, sucht der Client in der nächsten Partition nach Arbeit. Entweder findet der Client eine Partition mit Einträgen, oder er kehrt wieder zur ersten zufällig ausgewählten Partition zurück. Wenn der Client wieder zur ersten Partition zurückkehrt, gibt er einen Nullwert an die Anwendung zurück. Findet der Client eine Map, die Einträge enthält, konsumiert er bis zum Ablauf des Zeitlimits Einträge aus dieser Map, bis keine Einträge mehr verfügbar sind. Nach Ablauf des Zeitlimits wird null zurückgegeben. Wenn null zurückgegeben und eine partitionierte Map verwendet wird, bedeutet dies, dass Sie eine neue Transaktion starten und den Empfangsvorgang fortsetzen müssen. Das vorherige Codebeispielfragment hat dieses Verhalten.

Beispiel

Wenn Sie einen Client ausführen und ein Schlüssel zurückgegeben wird, ist diese Transaktion an die Partition gebunden, die den Eintrag für diesen Schlüssel enthält. Wenn Sie in dieser Transaktion keine weiteren Maps aktualisieren möchten, ist kein Problem vorhanden. Wenn Sie weitere Maps aktualisieren möchten, können Sie nur Maps aktualisieren, die zu derselben Partition gehören wie die Map, aus der Sie den Schlüssel abgerufen haben. Der Eintrag, der von der Methode "getNextKey" zurückgegeben wird, muss der Anwendung die Möglichkeit bieten, relevante Daten in dieser Partition zu finden. Sie haben beispielsweise zwei Maps: eine für Ereignisse und eine andere für Jobs, auf die sich die Ereignisse auswirken. Sie definieren die beiden Maps mit den folgenden Entitäten:
Job.java
package tutorial.fifo;

import com.ibm.websphere.projector.annotations.Entity;
import com.ibm.websphere.projector.annotations.Id;

@Entity
public class Job
{
	@Id String jobId;

	int jobState;
}
JobEvent.java
package tutorial.fifo;

import com.ibm.websphere.projector.annotations.Entity;
import com.ibm.websphere.projector.annotations.Id;
import com.ibm.websphere.projector.annotations.OneToOne;

@Entity
public class JobEvent
{
	@Id String eventId;
	@OneToOne Job job;
}
Der Job hat eine ID und einen Status (eine ganze Zahl). Angenommen, Sie möchten den Statuswert um jeweils eins erhöhen, wenn ein Ereignis eintritt. Die Ereignisse werden in der Map "JobEvent" gespeichert. Jeder Eintrag hat eine Referenz auf den Job, den das Ereignis betrifft. Der Code für den entsprechenden Listener gleicht dem folgenden Beispiel:
JobEventListener.java
package tutorial.fifo;

import com.ibm.websphere.objectgrid.ObjectGridException;
import com.ibm.websphere.objectgrid.ObjectMap;
import com.ibm.websphere.objectgrid.Session;
import com.ibm.websphere.objectgrid.em.EntityManager;

public class JobEventListener
{
	boolean stopListening;

	public synchronized void stopListening()
	{
		stopListening = true;
	}

	synchronized boolean isStopped()
	{
		return stopListening;
	}

	public void processJobEvents(Session session)
		throws ObjectGridException
	{
EntityManager em = session.getEntityManager();
		ObjectMap jobEvents = session.getMap("JobEvent");
		while(!isStopped())
		{
em.getTransaction().begin();

			Object jobEventKey = jobEvents.getNextKey(5000);
			if(jobEventKey == null)
			{
				em.getTransaction().rollback();
				continue;
			}
			JobEvent event = (JobEvent)em.find(JobEvent.class, jobEventKey);
			// Ereignis verarbeiten. Hier wird nur der Jobstatus erhöht.
			event.job.jobState++;
em.getTransaction().commit();		}
	}
}

Der Listener wird von der Anwendung in einem Thread gestartet. Der Listener wird ausgeführt, bis die Methode "stopListening" aufgerufen wird. Die Methode "processJobEvents" wird im Thread ausgeführt, bis die Methode "stopListening" aufgerufen wird. Die Schleife blockiert beim Warten auf ein eventKey-Objekt aus der Map "JobEvent" und verwendet dann den EntityManager, um auf das Ereignisobjekt zuzugreifen, den Job zu dereferenzieren und den Status zu erhöhen.

Die API "EntityManager" hat keine Methode "getNextKey", wohl aber die API "ObjectMap". Deshalb verwendet der Code die API "ObjectMap" für JobEvent, um den Schlüssel abzurufen. Wenn eine Map mit Entitäten verwendet wird, werden keine Objekte mehr gespeichert. Stattdessen werden Tupel gespeichert: Ein Tupelobjekt für den Schlüssel und ein Tupelobjekt für den Wert. Die Methode "EntityManager.find" akzeptiert ein Tupel für den Schlüssel.

Der Code zum Erstellen eines Ereignisses gleicht dem folgenden Beispiel:
em.getTransaction().begin();
Job job = em.find(Job.class, "Job Key");
JobEvent event = new JobEvent();
event.id = Random.toString();
event.job = job;
em.persist(event); // Einfügen
em.getTransaction().commit();
Sie finden den Job für das Ereignis, erstellen ein Ereignis, zeigen auf den Job, fügen ihn in die Map "JobEvent" ein und schreiben dann die Transaktion fest.

Loader und FIFO-Maps

Wenn Sie eine Map, die als FIFO-Warteschlange verwendet wird, mit einem Loader stützen möchten, kann zusätzliche Arbeit Ihrerseits erforderlich sein. Wenn die Reihenfolge der Einträge in der Map keine Rolle spielt, haben Sie keine zusätzliche Arbeit. Wenn die Reihenfolge von Bedeutung ist, müssen Sie allen eingefügten Datensätzen eine Folgenummer hinzufügen, wenn Sie die Datensätze persistent im Back-End speichern. Der Preload-Mechanismus muss so geschrieben werden, dass die Datensätze beim Starten in dieser Reihenfolge eingefügt werden.