L'API Async Datastore ti consente di effettuare chiamate non bloccanti in parallelo al datastore e di recuperare i risultati di queste chiamate in un secondo momento durante la gestione della richiesta. Questa documentazione descrive i seguenti aspetti dell'API Async Datastore:
Utilizzo del servizio Async Datastore
Con l'API Datastore asincrona, puoi effettuare chiamate a Datastore utilizzando i metodi dell'interfaccia AsyncDatastoreService. Puoi ottenere questo oggetto chiamando il metodo di classe getAsyncDatastoreService() della classe DatastoreServiceFactory.
import com.google.appengine.api.datastore.AsyncDatastoreService; import com.google.appengine.api.datastore.DatastoreServiceFactory; // ... AsyncDatastoreService datastore = DatastoreServiceFactory.getAsyncDatastoreService();
AsyncDatastoreService
supporta le stesse operazioni di DatastoreService, tranne per il fatto che la maggior parte dei metodi restituisce immediatamente un Future sul cui risultato puoi bloccarti in un secondo momento. Ad esempio, DatastoreService.get() restituisce un'entità, ma AsyncDatastoreService.get() restituisce un 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();
Nota:le eccezioni non vengono lanciate finché non chiami il metodo get(). La chiamata di questo metodo ti consente di verificare che l'operazione asincrona sia riuscita.
Se hai un AsyncDatastoreService
, ma devi eseguire un'operazione in modo sincrono, invoca il metodo AsyncDatastoreService
appropriato e poi blocca immediatamente il risultato:
// ... Entity entity = new Employee("Employee", "Alfred"); // ... populate entity properties // Make a sync call via the async interface Key key = datastore.put(key).get();
Utilizzo delle transazioni asincrone
Le chiamate all'API Async Datastore possono partecipare alle transazioni come le chiamate sincrone. Di seguito è riportata una funzione che regola lo stipendio di un Employee
e scrive un'entità SalaryAdjustment
aggiuntiva nello stesso gruppo di entità dell'Employee
, il tutto all'interno di un'unica transazione.
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 }
Questo esempio illustra una differenza importante tra le chiamate asincrone senza transazioni e le chiamate asincrone con transazioni. Quando non utilizzi una transazione, l'unico modo per assicurarti che una singola chiamata asincrona sia stata completata è recuperare il valore restituito del Future
restituito al momento della chiamata. Quando utilizzi una transazione, la chiamata a Transaction.commit() si blocca sul risultato di tutte le chiamate asincrone effettuate dall'inizio della transazione prima di eseguirne l'commit.
Pertanto, nell'esempio riportato sopra, anche se la chiamata asincrona per inserire l'entità SalaryAdjustment
potrebbe essere ancora in sospeso quando chiamiamo commit()
, il commit non verrà eseguito finché l'inserimento non sarà completato. Analogamente, se scegli di chiamare commitAsync()
anziché commit()
, l'invocazione di get()
sull'oggetto Future
restituito da commitAsync()
viene bloccata fino al completamento di tutte le chiamate asincrone in sospeso.
Nota:le transazioni sono associate a un thread specifico, non a un'istanza specifica di DatastoreService
o AsyncDatastoreService
. Ciò significa che se avvii una transazione con un DatastoreService
ed esegui una chiamata asincrona con un AsyncDatastoreService
, la chiamata asincrona partecipa alla transazione. In altre parole, DatastoreService.getCurrentTransaction()
e AsyncDatastoreService.getCurrentTransaction()
restituiscono sempre lo stesso Transaction
.
Lavorare con i futures
La documentazione Javadoc futura spiega la maggior parte di ciò che devi sapere per utilizzare correttamente un Future
restituito dall'API Async Datastore, ma ci sono alcune informazioni specifiche di App Engine che devi conoscere:
- Quando chiami Future.get(long timeout, TimeUnit unit), il timeout è separato da qualsiasi scadenza RPC impostata quando hai creato
AsyncDatastoreService
. Per ulteriori informazioni, consulta Coerenza dei dati nelle query di Cloud Datastore. - Quando chiami Future.cancel(boolean mayInterruptIfRunning) e la chiamata restituisce
true
, non significa necessariamente che lo stato del tuo datastore sia invariato. In altre parole, l'annullamento di unFuture
non è uguale al rollback di una transazione.
Query asincrone
Al momento non esponiamo un'API esplicitamente asincrona per le query. Tuttavia, quando chiami PreparedQuery.asIterable(), PreparedQuery.asIterator() o PreparedQuery.asList(FetchOptions fetchOptions), sia DatastoreService sia AsyncDatastoreService restituiscono immediatamente i risultati e li precaricano in modo asincrono. In questo modo, l'applicazione può eseguire attività in parallelo durante il recupero dei risultati della query.
// ... 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);
Quando utilizzare le chiamate Async Datastore
Le operazioni esposte dall'interfaccia DatastoreService sono sincrone. Ad esempio, quando chiami DatastoreService.get()
, il codice si blocca fino al completamento della chiamata al datastore. Se l'unica cosa che la tua applicazione deve fare è eseguire il rendering del risultato di get()
in HTML, bloccare fino al completamento della chiamata è una cosa perfettamente ragionevole. Tuttavia, se la tua applicazione ha bisogno del risultato di get()
e del risultato di un Query
per eseguire il rendering della risposta e se get()
e Query
non hanno dipendenze di dati, attendere il completamento di get()
per avviare Query
è uno spreco di tempo. Ecco un esempio di codice che può essere migliorato utilizzando l'API asincrona:
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);
Anziché attendere il completamento di get()
, utilizza un'istanza di AsyncDatastoreService per eseguire la chiamata in modo asincrono:
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);
Le versioni sincrone e asincrone di questo codice utilizzano quantità simili di CPU (dopotutto, entrambe eseguono la stessa quantità di lavoro), ma poiché la versione asincrona consente l'esecuzione in parallelo delle due operazioni del datastore, ha una latenza inferiore. In generale, se devi eseguire più operazioni di Datastore che non hanno dipendenze dai dati, AsyncDatastoreService
può migliorare notevolmente la latenza.