Mit der Async Datastore API können Sie parallele, nicht blockierende Aufrufe an den Datenspeicher ausführen und die Ergebnisse später bei der Verarbeitung der Anfrage abrufen. In dieser Dokumentation werden die folgenden Aspekte der Async Datastore API erläutert:
Mit dem Async Datastore-Dienst arbeiten
Mit der Async Datastore API führen Sie Datastore-Aufrufe mit den Methoden der Schnittstelle AsyncDatastoreService durch. Sie erhalten dieses Objekt, wenn Sie die Klassenmethode getAsyncDatastoreService() der Klasse DatastoreServiceFactory aufrufen:
import com.google.appengine.api.datastore.AsyncDatastoreService; import com.google.appengine.api.datastore.DatastoreServiceFactory; // ... AsyncDatastoreService datastore = DatastoreServiceFactory.getAsyncDatastoreService();
AsyncDatastoreService
unterstützt dieselben Vorgänge wie DatastoreService, mit dem Unterschied, dass die meisten Methoden sofort ein Future zurückgeben, dessen Ergebnis Sie später blockieren können. DatastoreService.get() gibt beispielsweise eine Entität zurück. Bei AsyncDatastoreService.get() ist es ein Future<Entity>
:
// ... Key key = KeyFactory.createKey("Employee", "Max"); // Async call returns immediately Future<Entity> entityFuture = datastore.get(key); // Do other stuff while the get operation runs in the background... // Blocks if the get operation has not finished, otherwise returns instantly Entity entity = entityFuture.get();
Hinweis: Ausnahmen werden erst ausgelöst, wenn Sie die get()-Methode aufrufen. Durch das Aufrufen dieser Methode können Sie überprüfen, ob der asynchrone Vorgang erfolgreich war.
Wenn Sie einen AsyncDatastoreService
haben, aber einen Vorgang synchron ausführen möchten, rufen Sie die geeignete AsyncDatastoreService
-Methode auf und blockieren anschließend sofort das Ergebnis:
// ... Entity entity = new Employee("Employee", "Alfred"); // ... populate entity properties // Make a sync call via the async interface Key key = datastore.put(key).get();
Mit asynchronen Transaktionen arbeiten
Aufrufe der Async Datastore API können genau wie synchrone Aufrufe an Transaktionen beteiligt sein. Bei dieser Funktion wird im Rahmen einer einzelnen Transaktion das Gehalt von Employee
angepasst und eine zusätzliche Entität SalaryAdjustment
in dieselbe Entitätengruppe wie Employee
geschrieben:
void giveRaise(AsyncDatastoreService datastore, Key employeeKey, long raiseAmount) throws Exception { Future<Transaction> txn = datastore.beginTransaction(); // Async call to lookup the Employee entity Future<Entity> employeeEntityFuture = datastore.get(employeeKey); // Create and put a SalaryAdjustment entity in parallel with the lookup Entity adjustmentEntity = new Entity("SalaryAdjustment", employeeKey); adjustmentEntity.setProperty("adjustment", raiseAmount); adjustmentEntity.setProperty("adjustmentDate", new Date()); datastore.put(adjustmentEntity); // Fetch the result of our lookup to make the salary adjustment Entity employeeEntity = employeeEntityFuture.get(); long salary = (Long) employeeEntity.getProperty("salary"); employeeEntity.setProperty("salary", salary + raiseAmount); // Re-put the Employee entity with the adjusted salary. datastore.put(employeeEntity); txn.get().commit(); // could also call txn.get().commitAsync() here }
Dieses Beispiel veranschaulicht einen wichtigen Unterschied zwischen asynchronen Aufrufen ohne Transaktionen und asynchronen Aufrufen mit Transaktionen. Wenn Sie keine Transaktion verwenden, kann nur sichergestellt werden, dass ein einzelner asynchroner Aufruf abgeschlossen wurde, indem der Rückgabewert des Future
abgerufen wird, das bei der Durchführung des Aufrufs zurückgegeben wurde. Wenn Sie eine Transaktion verwenden, werden durch das Aufrufen von Transaction.commit() vor dem Commit die Ergebnisse aller asynchronen Aufrufe blockiert, die seit dem Start der Transaktion erfolgt sind.
Für unser obiges Beispiel bedeutet dies: Auch wenn unser asynchroner Aufruf zur Einfügung der SalaryAdjustment
-Entität vielleicht noch aussteht, wenn wir commit()
aufrufen, wird der Commit-Vorgang erst nach Abschluss der Einfügung durchgeführt. Ähnliches gilt, wenn Sie commitAsync()
statt commit()
aufrufen. Dann wird der Aufruf von get()
für das Future
, das von commitAsync()
zurückgegeben wurde, blockiert, bis alle ausstehenden asynchronen Aufrufe abgeschlossen sind.
Hinweis: Transaktionen werden einem bestimmten Thread zugeordnet, nicht einer bestimmten Instanz von DatastoreService
oder AsyncDatastoreService
. Wenn Sie also eine Transaktion mit einem DatastoreService
initiieren und einen asynchronen Aufruf mit einem AsyncDatastoreService
durchführen, nimmt der asynchrone Aufruf an der Transaktion teil. Oder einfacher gesagt: DatastoreService.getCurrentTransaction()
und AsyncDatastoreService.getCurrentTransaction()
geben immer dieselbe Transaction
zurück.
Mit Futures arbeiten
Im Future-Javadoc erfahren Sie, wie Sie mit einem Future
arbeiten, das von der Async Datastore API zurückgegeben wird. Außerdem gilt es, diese Aspekte von App Engine zu beachten:
- Wenn Sie Future.get(long timeout, TimeUnit unit) aufrufen, ist das Zeitlimit unabhängig von RPC-Zeitlimits, die Sie beim Erstellen des
AsyncDatastoreService
festgelegt haben. Weitere Informationen finden Sie unter Datenkonsistenz in Cloud Datastore-Abfragen. - Wenn Sie Future.cancel(boolean mayInterruptIfRunning) aufrufen und daraufhin
true
zurückgegeben wird, heißt dies nicht unbedingt, dass der Status Ihres Datenspeichers unverändert ist. Mit anderen Worten: EinFuture
abzubrechen, ist nicht dasselbe, wie eine Transaktion rückgängig zu machen.
Asynchrone Abfragen
Wir bieten derzeit keine explizit asynchrone API für Abfragen. Wenn Sie jedoch PreparedQuery.asIterable(), PreparedQuery.asIterator() oder PreparedQuery.asList(FetchOptions fetchOptions) aufrufen, antworten sowohl DatastoreService als auch AsyncDatastoreService sofort und rufen asynchron Ergebnisse ab. Dadurch kann Ihre Anwendung Aufgaben parallel abarbeiten, während die Abfrageergebnisse abgerufen werden:
// ... Query q1 = new Query("Salesperson"); q1.setFilter(new FilterPredicate("dateOfHire", FilterOperator.LESS_THAN, oneMonthAgo)); // Returns instantly, query is executing in the background. Iterable<Entity> recentHires = datastore.prepare(q1).asIterable(); Query q2 = new Query("Customer"); q2.setFilter(new FilterPredicate("lastContact", FilterOperator.GREATER_THAN, oneYearAgo)); // Also returns instantly, query is executing in the background. Iterable<Entity> needsFollowup = datastore.prepare(q2).asIterable(); schedulePhoneCall(recentHires, needsFollowUp);
Einsatzmöglichkeiten für Async Datastore-Aufrufe
Die von der DatastoreServiceSchnittstelle bereitgestellten Vorgänge sind synchron. Wenn Sie beispielsweise DatastoreService.get()
aufrufen, wird Ihr Code blockiert, bis der Aufruf beim Datenspeicher abgeschlossen ist. Wenn Ihre Anwendung das Ergebnis von get()
einfach nur in HTML ausgeben muss, ist eine Blockierung, bis der Aufruf abgeschlossen ist, eine sinnvolle Vorgehensweise. Falls Ihre Anwendung jedoch sowohl das Ergebnis von get()
als auch das Ergebnis einer Query
benötigt, um die Antwort auszugeben, und wenn zwischen get()
und der Query
keine Datenabhängigkeiten bestehen, ist es nicht sinnvoll, auf den Abschluss von get()
zu warten, um die Query
zu starten. Hier sehen Sie ein Beispiel für Code, der durch die Verwendung der Async API verbessert werden kann:
DatastoreService datastore = DatastoreServiceFactory.getDatastoreService(); Key empKey = KeyFactory.createKey("Employee", "Max"); // Read employee data from the Datastore Entity employee = datastore.get(empKey); // Blocking for no good reason! // Fetch payment history Query query = new Query("PaymentHistory"); PreparedQuery pq = datastore.prepare(query); List<Entity> result = pq.asList(FetchOptions.Builder.withLimit(10)); renderHtml(employee, result);
Sie müssen nicht darauf warten, dass get()
abgeschlossen wird. Führen Sie stattdessen mit dem AsyncDatastoreService einen asynchronen Aufruf aus:
AsyncDatastoreService datastore = DatastoreServiceFactory.getAsyncDatastoreService(); Key empKey = KeyFactory.createKey("Employee", "Max"); // Read employee data from the Datastore Future<Entity> employeeFuture = datastore.get(empKey); // Returns immediately! // Fetch payment history for the employee Query query = new Query("PaymentHistory", empKey); PreparedQuery pq = datastore.prepare(query); // Run the query while the employee is being fetched List<Entity> result = pq.asList(FetchOptions.Builder.withLimit(10)); // Implicitly performs query asynchronously Entity employee = employeeFuture.get(); // Blocking! renderHtml(employee, result);
Die synchrone und die asynchrone Version dieses Codes weisen einen ähnlichen CPU-Verbrauch auf, sie führen ja schließlich jeweils denselben Arbeitsumfang aus. Aber da die beiden Datenspeichervorgänge bei der asynchronen Version parallel ausgeführt werden können, weist die asynchrone Version eine geringere Latenz auf. Allgemein gilt: Wenn Sie mehrere Datenspeichervorgänge ohne Datenabhängigkeiten durchführen müssen, kann der AsyncDatastoreService
die Latenz erheblich verbessern.