Async Datastore API

Async Datastore API 可讓您以平行、無阻斷的方式呼叫資料儲存庫,並在稍後處理要求時擷取這些呼叫的結果。本說明文件提供 Async Datastore API 下列方面的說明:

使用非同步資料儲存庫服務

您可以透過 Async Datastore API 使用 AsyncDatastoreService 介面的方法呼叫資料儲存庫。您可以呼叫 DatastoreServiceFactory 類別的 getAsyncDatastoreService() 類別方法來取得這個物件。

import com.google.appengine.api.datastore.AsyncDatastoreService;
import com.google.appengine.api.datastore.DatastoreServiceFactory;

// ...
AsyncDatastoreService datastore = DatastoreServiceFactory.getAsyncDatastoreService();

除了大多數方法會立即傳回您稍後可封鎖其結果的 Future 以外,AsyncDatastoreService 支援的作業與 DatastoreService 相同。舉例來說,DatastoreService.get() 會傳回實體,但 AsyncDatastoreService.get() 會傳回 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();

注意事項:在您呼叫 get() 方法之前,系統不會傳回例外狀況。呼叫這個方法之後,您就能確認非同步作業是否完成。

如果您使用 AsyncDatastoreService 時必須同步執行某項作業,請叫用適當的 AsyncDatastoreService 方法,然後立即封鎖結果:

// ...

Entity entity = new Employee("Employee", "Alfred");
// ... populate entity properties

// Make a sync call via the async interface
Key key = datastore.put(key).get();

使用非同步交易

與同步呼叫相同,Async Datastore API 呼叫也能用於交易。以下函式的作用是調整 Employee 的薪資,並在含有 Employee 的同一個實體群組中寫入額外的 SalaryAdjustment 實體,而且這些作業都在一次交易中進行。

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
}

上方範例顯示了「不使用交易的非同步呼叫」與「使用交易的非同步呼叫」之間的重要差異。如果您未使用交易,唯一可以確認個別非同步呼叫是否完成的方法是擷取呼叫執行時傳回的 Future 值。如果您使用了交易,呼叫 Transaction.commit() 即會封鎖自交易開始至您修訂交易前發出的所有非同步呼叫產出結果。

因此在上方範例中,即便插入 SalaryAdjustment 實體的非同步呼叫在我們呼叫 commit() 時仍未完成,但除非插入作業完成,否則系統不會進行修訂。同理,如果您選擇呼叫 commitAsync() 而非 commit(),系統會在 commitAsync() 傳回的 Future 中叫用 get(),直到未解決的所有非同步呼叫均已完成。

注意事項:交易與特定執行緒相關聯,而非 DatastoreServiceAsyncDatastoreService 的特定執行個體。也就是說,如果您展開含有 DatastoreService 的交易,並執行含有 AsyncDatastoreService 的非同步呼叫,交易中就會包含這項非同步呼叫。簡言之,DatastoreService.getCurrentTransaction()AsyncDatastoreService.getCurrentTransaction() 一律會傳回相同的 Transaction

使用 Futures

Future Javadoc 針對順利使用 Async Datastore API 傳回的 Future 說明了您必須瞭解的大部分事項,但您仍須注意以下幾項 App Engine 關事宜:

非同步查詢

我們目前不開放針對明確的非同步 API 進行查詢。不過,在您叫用 PreparedQuery.asIterable()PreparedQuery.asIterator()PreparedQuery.asList(FetchOptions fetchOptions) 時,DatastoreService 和 AsyncDatastoreService 都會立即傳回並非同步預先擷取結果。這樣一來,應用程式就能在擷取查詢結果時平行執行工作。

// ...

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);

非同步資料儲存庫呼叫的使用時機

DatastoreService 介面中顯示的這些項目為同步作業。舉例來說,在您呼叫 DatastoreService.get() 時,系統會暫時封鎖程式碼,直到資料儲存庫呼叫完成為止。如果應用程式僅須將 get() 的結果轉譯為 HTML,在呼叫完成前封鎖程式碼是相當合理的做法。不過,如果應用程式需要 get()Query 的結果才能轉譯回應,且 get()Query 之間不具有任何資料相依性,那麼等到 get() 完成後才展開 Query 會相當浪費時間。下列提供可透過非同步 API 提升效能的程式碼範例:

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);

您可以改用 AsyncDatastoreService 以非同步的方式執行呼叫,這樣就不必等待 get() 完成:

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); 

這個程式碼的同步版和非同步版 CPU 用量相差無幾 (因為這兩個版本執行的工作量相同),但非同步版的程式碼允許並行執行兩項資料儲存庫作業,因此非同步版的延遲時間較短。一般來說,如果您必須執行多項不具資料相依性的資料儲存庫作業,使用 AsyncDatastoreService 可以大幅改善延遲情況。