Abfragewarteschlangen für Entitäten

Abfragewarteschlangen ermöglichen Anwendungen, eine durch Abfrage im serverseitigen oder lokalen eXtreme Scale über eine Entität qualifizierte Warteschlange zu erstellen. Entitäten aus dem Abfrageergebnis werden in dieser Warteschlange gespeichert. Derzeit werden Abfragewarteschlangen nur in Maps unterstützt, die die pessimistische Sperrstrategie verwenden.

Eine Abfragewarteschlange wird von mehreren Transaktionen und Clients gemeinsam genutzt. Wenn die Abfragewarteschlange leer ist, wird die Entitätenabfrage, die dieser Warteschlange zugeordnet ist, erneut ausgeführt, und die neuen Ergebnisse werden der Warteschlange hinzugefügt. Eine Abfragewarteschlange wird über die Entitätsabfragezeichenfolge und -parameter eindeutig identifiziert. Es gibt nur eine einzige Instanz jeder eindeutigen Abfragewarteschlange in einer ObjektGrid-Instanz. Weitere Informationen finden Sie in der API-Dokumentation zu EntityManager.

Beispiel für Abfragewarteschlange

Das folgende Beispiel veranschaulicht, wie eine Abfragewarteschlange verwendet werden kann.

/**
 * Nicht zugordnete Task vom Typ "Frage" abrufen
 */
private void getUnassignedQuestionTask() throws Exception {
    EntityManager em = og.getSession().getEntityManager();
    EntityTransaction tran = em.getTransaction();

    QueryQueue queue = em.createQueryQueue("SELECT t FROM Task t
				WHERE t.type=?1 AND t.status=?2", Task.class);
    queue.setParameter(1, new Integer(Task.TYPE_QUESTION));
    queue.setParameter(2, new Integer(Task.STATUS_UNASSIGNED));

    tran.begin();
    Task nextTask = (Task) queue.getNextEntity(10000);
    System.out.println("next task is " + nextTask);
    if (nextTask != null) {
        assignTask(em, nextTask);
    }
    tran.commit();
}

Der vorherige Beispielcode erstellt zunächst eine Abfragewarteschlange (QueryQueue) mit der Entitätsabfragezeichenfolge "SELECT t FROM Task t WHERE t.type=?1 AND t.status=?2". Anschließend setzt er die Parameter des QueryQueue-Objekts. Diese Abfragewarteschlange stellt alle "nicht zugeordneten" Tasks des Typs "Frage" dar. Das QueryQueue-Objekt ist dem Query-Objekt einer Entität sehr ähnlich.

Nach dem Erstellen des QueryQueue-Objekts wird eine Entitätstransaktion gestartet und die Methode "getNextEntity" aufgerufen, die die nächste verfügbare Entität mit einem definierten Zeitlimit von 10 Sekunden aufruft. Nachdem die Entität abgerufen wurde, wird sie in der Methode "assignTask" verarbeitet. Die Methode "assignTask" modifiziert die Taskentitätsinstanz und ändert den Status in "zugeordnet"(assigned), woraufhin die Instanz aus der Warteschlange entfernt wird, weil sie dem Filter von QueryQueue nicht mehr entspricht. Nach der Zuordnung wird die Transaktion festgeschrieben.

Anhand dieses einfachen Beispiels lässt sich erkennen, dass eine Abfragewarteschlange einer Entitätsabfrage gleicht. Sie unterscheiden sich jedoch in folgender Hinsicht:
  1. Entitäten in der Abfragewarteschlange können iterativ abgerufen werden. Die Benutzeranwendung legt die Anzahl der abzurufenden Entitäten fest. Wenn beispielsweise "QueryQueue.getNextEntity(timeout)" verwendet wird, wird nur eine einzige Entität abgerufen, wird "QueryQueue.getNextEntities(5, timeout)" verwendet, werden 5 Entitäten abgerufen. In einer verteilten Umgebung entscheidet die Anzahl der Entitäten direkt über die Anzahl der Bytes, die vom Server an den Client übertragen werden.
  2. Beim Abruf einer Entität aus der Abfragewarteschlange wird eine U-Sperre für die Entität gesetzt, so dass keine anderen Transaktionen auf die Entität zugreifen können.

Entitäten in einer Schleife abrufen

Sie können Entitäten in einer Schleife abrufen. Im Folgenden sehen Sie ein Beispiel, das veranschaulicht, wie alle nicht zugeordneten Tasks vom Typ "Frage" abgerufen werden:

/**
 * Alle nicht zugeordneten Tasks vom Typ "Frage" abrufen
 */
private void getAllUnassignedQuestionTask() throws Exception {
    EntityManager em = og.getSession().getEntityManager();
    EntityTransaction tran = em.getTransaction();

    QueryQueue queue = em.createQueryQueue("SELECT t FROM Task t WHERE 
			t.type=?1 AND t.status=?2", Task.class);
    queue.setParameter(1, new Integer(Task.TYPE_QUESTION));
    queue.setParameter(2, new Integer(Task.STATUS_UNASSIGNED));

    Task nextTask = null;

    do {
        tran.begin();
        nextTask = (Task) queue.getNextEntity(10000);
        if (nextTask != null) {
            System.out.println("next task is " + nextTask);
        }
        tran.commit();
    } while (nextTask != null);
}

Wenn es 10 nicht zugeordnete Tasks vom Typ "Frage" in der Entitäts-Map gibt, erwarten Sie wahrscheinlich, dass 10 Entitäten in der Konsole ausgegeben werden. Wenn Sie jedoch dieses Beispiel ausführen, werden Sie feststellen, dass das Programm entgegen Ihren Erwartungen nie beendet wird.

Wenn eine Abfragewarteschlange erstellt und die Methode "getNextEntity" aufgerufen wird, wird die Entitätsabfrage ausgeführt, die der Warteschlange zugeordnet ist, und die 10 Ergebnisse werden in die Warteschlange eingetragen. Beim Aufruf von "getNextEntity" wird der Warteschlange eine Entität entnommen. Nach der Ausführung von zehn getNextEntity-Aufrufen ist die Warteschlange leer. Die Entitätsabfrage wird automatisch erneut ausgeführt. Da diese 10 Entitäten immer noch vorhanden sind und den Filterkriterien der Abfragewarteschlange entsprechen, werden sie erneut in die Warteschlange eingetragen.

Wenn die folgende Zeile hinter der Anweisung "println()" hinzugefügt wird, werden nur 10 Entitäten ausgegeben:

em.remove(nextTask);

Informationen zur Verwendung von SessionHandle mit QueryQueue in einer containerbezogenen Verteilungsimplementierung finden Sie unter SessionHandle-Integration.

In allen Partitionen implementierte Abfragewarteschlangen

In einer verteilten eXtreme-Scale-Umgebung kann eine Abfragewarteschlange für eine einzige oder für alle Partitionen erstellt werden. Wenn eine Abfragewarteschlange für alle Partitionen erstellt wird, gibt es in jeder Partition eine einzige Instanz der Abfragewarteschlange.

Wenn ein Client versucht, die nächste Entität mit der Methode QueryQueue.getNextEntity oder QueryQueue.getNextEntities abzurufen, sendet der Client eine Anforderung an eine der Partitionen. Ein Client sendet so genannte Peek- und Pin-Anforderungen an den Server:

  • Bei einer Peek-Anforderung sendet der Client eine Anforderung an eine einzige Partition, und der Server gibt das Ergebnis sofort zurück. Ist eine Entität in der Warteschlange vorhanden, sendet der Server eine Antwort mit der Entität, wenn nicht, sendet der Server eine Antwort ohne Entität. In beiden Fällen gibt der Server sofort ein Ergebnis zurück.
  • Bei einer Pin-Anforderung sendet der Client eine Anforderung an eine einzige Partition, und der Server wartet, bis eine Entität verfügbar ist. Ist eine Entität in der Warteschlange enthalten, sendet der Server unverzüglich eine Antwort mit der Entität, wenn nicht, wartet der Server in der Warteschlange, bis eine Entität verfügbar ist oder bis das zulässige Anforderungszeitlimit abläuft.

Im Folgenden sehen Sie ein Beispiel, in dem veranschaulicht wird, wie eine Entität für eine Abfragewarteschlange abgerufen wird, die in allen Partitionen (n) implementiert ist:

  1. Beim Aufruf einer Methode "QueryQueue.getNextEntity" oder "QueryQueue.getNextEntities" verwendet der Client wahlfrei eine Partitionsnummer zwischen 0 und n-1.
  2. Der Client sendet eine Peek-Anforderung an die wahlfreie Partition.
    • Ist eine Entität verfügbar, wird die Methode "QueryQueue.getNextEntity" bzw. "QueryQueue.getNextEntities" durch Rückgabe der Entität beendet.
    • Ist keine Entität verfügbar und handelt es sich nicht um die letzte noch nicht besuchte Partition, sendet der Client eine Peek-Anforderung an die nächste Partition.
    • Ist keine Entität verfügbar und handelt es sich um die letzte noch nicht besuchte Partition, sendet der Client stattdessen eine Pin-Anforderung.
    • Wenn das zulässige Zeitlimit für die Pin-Anforderung an die letzte Partition abläuft und noch immer keine Daten verfügbar sind, unternimmt der Client einen letzten Versuch, indem er noch einmal nacheinander eine Peek-Anforderung an alle Partitionen sendet. Deshalb ist der Client in der Lage, eine mittlerweile in einer der früheren Partitionen verfügbare Entität abzurufen.

Unterstützung von Teilentitäten und keinen Entitäten

Im Folgenden wird die Methode zum Erstellen eines QueryQueue-Objekt im EntityManager veranschaulicht:

public QueryQueue createQueryQueue(String qlString, Class entityClass);

Das Ergebnis in der Abfragewarteschlange muss an das Objekt weitergegeben werden, das mit dem zweiten Parameter der Methode definiert ist, Class entityClass.

Wenn dieser Parameter angegeben ist, muss die Klasse denselben Entitätsnamen haben, der auch in der Abfragezeichenfolge enthalten ist. Dies ist hilfreich, wenn Sie eine Entität an eine Teilentität weitergeben möchten. Wird ein Nullwert als Entitätsklasse verwendet, wird das Ergebnis nicht weitergegeben. Der in der Map gespeicherte Wert hat das Entitätstupelformat.

Clientseitige Schlüsselkollision

In einer verteilten eXtreme-Scale-Umgebung werden Abfragewarteschlangen nur für eXtreme-Scale-Maps mit pessimistischem Sperrmodus unterstützt. Deshalb gibt es auf der Clientseite keinen nahen Cache. Ein Client kann jedoch Daten (Schlüssel und Wert) in einer Transaktions-Map verwalten. Dies könnte zu einer Schlüsselkollision führen, wenn eine vom Server abgerufene Entität denselben Schlüssel wie ein Eintrag verwendet, der bereits in der Transaktions-Map enthalten ist.

Bei einer Schlüsselkollision verwendet die Laufzeitumgebung des eXtreme-Scale-Clients die folgende Regel, um entweder eine Ausnahme auszulösen oder die Daten unbeaufsichtigt zu überschreiben.
  1. Wenn der von der Kollision betroffene Schlüssel der Schlüssel der Entität ist, die in der Entitätenabfrage für die Abfragewarteschlange angegeben ist, wird eine Ausnahme ausgelöst. In diesem Fall wird die Transaktion rückgängige macht und eine U-Sperre für diesen Entitätsschlüssel auf Serverseite freigegeben.
  2. Wenn der betroffene Schlüssel der Schlüssel der Entitätsassoziation ist, werden die Daten in der Transaktions-Map ohne Warnung überschrieben.

Die Schlüsselkollision kommt nur vor, wenn die Transaktions-Map Daten enthält. Anders ausgedrückt, es kommt nur dann zu einer Schlüsselkollision, wenn ein Aufruf der Methode "getNextEntity" oder "getNextEntities" in einer Transaktion vorgenommen wird, die bereits genutzt wurde (es wurden neue Daten eingefügt oder aktualisiert). Wenn eine Anwendung Schlüsselkollisionen vermeiden möchten, muss sie getNextEntity oder getNextEntities immer in einer noch nicht genutzten Transaktion aufrufen.

Clientfehler

Nachdem ein Client eine getNextEntity- oder getNextEntities-Anforderung an den Server gesendet hat, könnte einer der folgenden Fehler im Client auftreten:
  1. Der Client sendet eine Anforderung an den Server und stürzt dann ab.
  2. Der Client empfängt eine oder mehrere Entitäten vom Server und stürzt dann ab.

Im ersten Fall erkennt der Server, dass der Client nicht mehr verfügbar ist, wenn er versucht, die Antwort an den Client zurückzusenden. Im zweiten Fall wird eine X-Sperre für diese Entitäten gesetzt. Wenn der Client abstürzt, läuft irgendwann das Transaktionszeitlimit ab, und die X-Sperre wird freigegeben.

Abfrage mit der Klausel ORDER BY

Im Allgemeinen wird die Klausel ORDER BY von Abfragewarteschlangen nicht berücksichtigt. Wenn Sie getNextEntity oder getNextEntities über die Abfragewarteschlange aufrufen, besteht keine Garantie, dass die Entitäten in der richtigen Reihenfolge zurückgegeben werden. Der Grund hierfür ist, dass die Entitäten in den Partitionen nicht sortiert werden können. Wenn die Abfragewarteschlange in allen Partitionen implementiert ist und ein getNextEntity- oder getNextEntities-Abruf ausgeführt wird, wird eine Partition für die Verarbeitung der Anforderung zufällig ausgewählt. Deshalb ist die Reihenfolge nicht garantiert.

ORDER BY wird berücksichtigt, wenn eine Abfragewarteschlange nur in einer einzigen Partition implementiert ist.

Weitere Informationen finden Sie im Abschnitt EntityManager-API "Query".

Ein Aufruf pro Transaktion

Jeder Aufruf von QueryQueue.getNextEntity oder QueryQueue.getNextEntities ruft übereinstimmende Entitäten aus einer wahlfreien Partition ab. Anwendungen müssen einen einzigen Aufruf von QueryQueue.getNextEntity oder QueryQueue.getNextEntities in einer Transaktion absetzen. Andernfalls könnte eXtreme Scale Entitäten aus mehreren Partitionen abrufen, was dazu führt, dass beim Festschreiben eine Ausnahme ausgelöst wird.