建立資料結構以達成強式一致性

Cloud Datastore 能透過許多機器發佈資料,還能針對地理範圍廣泛的區域使用免主機的同步備用資源,因此能發揮高度的可用性、擴充性以及耐用性。不過,這種設計有得有失,其缺點在於任何一個實體群組的寫入總處理量一律只能大約每秒提交一次,若要跨多個實體群組進行查詢或交易,也會受到限制。本頁詳細說明相關限制,同時以不犧牲應用程式寫入總處理量需求為前提,探討建立資料以利強式一致性的最佳做法。

同步一致性讀取功能一定能夠傳回最新的資料,此外,若是在交易當中執行,則會呈現來源是單一且一致的數據匯報。不過,查詢必須指定祖系篩選條件,才能保持十分一致或參與交易,且交易最多只能與 25 個實體群組相關。能發揮最終一致性的讀取功能沒有以上限制,適用於許多情況。若使用能發揮最終一致性的讀取功能,可以在為數更多的實體群組之間發佈資料,從而在不同的實體群組同時執行提交,以利提高寫入總處理量。但是,要判斷您的應用程式究竟是否適合使用能發揮最終一致性的讀取功能,就必須瞭解這類功能的特性:

  • 這類讀取功能的結果不見得能反映出最新交易,這類讀取功能之所以可能發生這種情形,原因在於這類功能不見得會以最新的備用資源執行,而是會在執行查詢時使用備用資源中任何可用的資料。備用資源延遲通常不超過幾秒鐘。
  • 在多個實體提交的交易可能看似只套用於其中部分實體,而未套用於其他實體。然而請注意,在單一實體中,絕對不會出現看似部分套用交易的情形。
  • 查詢結果可能會包含依據篩選準則不應包含的實體,也可能會排除應包含的實體。索引讀取的版本可能會不同於實體本身所讀取的版本,因此會發生這種情形。

若要瞭解如何建構資料以實現強式一致性,請為簡單的留言板應用比較兩種不同的方法。第一種方法是為每個已建立的實體建立一個新的根實體。

Java 8

protected Entity createGreeting(
    DatastoreService datastore, User user, Date date, String content) {
  // No parent key specified, so Greeting is a root entity.
  Entity greeting = new Entity("Greeting");
  greeting.setProperty("user", user);
  greeting.setProperty("date", date);
  greeting.setProperty("content", content);

  datastore.put(greeting);
  return greeting;
}

Java 7

protected Entity createGreeting(
    DatastoreService datastore, User user, Date date, String content) {
  // No parent key specified, so Greeting is a root entity.
  Entity greeting = new Entity("Greeting");
  greeting.setProperty("user", user);
  greeting.setProperty("date", date);
  greeting.setProperty("content", content);

  datastore.put(greeting);
  return greeting;
}

接下來會查詢最近十句問候語的實體種類 Greeting

Java 8

protected List<Entity> listGreetingEntities(DatastoreService datastore) {
  Query query = new Query("Greeting").addSort("date", Query.SortDirection.DESCENDING);
  return datastore.prepare(query).asList(FetchOptions.Builder.withLimit(10));
}

Java 7

protected List<Entity> listGreetingEntities(DatastoreService datastore) {
  Query query =
      new Query("Greeting")
          .addSort("date", Query.SortDirection.DESCENDING);
  return datastore.prepare(query)
      .asList(FetchOptions.Builder.withLimit(10));
}

不過,由於您使用的是非祖系查詢,因此在執行查詢時,這項配置中用於執行查詢的備用資源可能尚未發現新的問候語。儘管如此,幾乎所有寫入均可在提交後幾秒內用於非祖系查詢。就許多應用程式而言,只要能在目前使用者自己所做的變更中提供非祖系查詢結果,即使出現備用資源延遲現象,通常也在可以接受的範圍。

若您的應用程式非常需要發揮強大的一致性,可以考慮另一種方法,就是使用祖系路徑寫入實體,且所用的祖系路徑要能在所有實體中識別同一個根實體 (所有實體必須利用一次同步一致性祖系查詢讀取):

Java 8

protected Entity createGreeting(
    DatastoreService datastore, User user, Date date, String content) {
  // String guestbookName = "my guestbook"; -- Set elsewhere (injected to the constructor).
  Key guestbookKey = KeyFactory.createKey("Guestbook", guestbookName);

  // Place greeting in the same entity group as guestbook.
  Entity greeting = new Entity("Greeting", guestbookKey);
  greeting.setProperty("user", user);
  greeting.setProperty("date", date);
  greeting.setProperty("content", content);

  datastore.put(greeting);
  return greeting;
}

Java 7

protected Entity createGreeting(
    DatastoreService datastore, User user, Date date, String content) {
  // String guestbookName = "my guestbook"; -- Set elsewhere (injected to the constructor).
  Key guestbookKey = KeyFactory.createKey("Guestbook", guestbookName);

  // Place greeting in the same entity group as guestbook.
  Entity greeting = new Entity("Greeting", guestbookKey);
  greeting.setProperty("user", user);
  greeting.setProperty("date", date);
  greeting.setProperty("content", content);

  datastore.put(greeting);
  return greeting;
}

之後,您就能夠在共同根實體所識別到的實體群組中執行強式一致性祖系查詢:

Java 8

protected List<Entity> listGreetingEntities(DatastoreService datastore) {
  Key guestbookKey = KeyFactory.createKey("Guestbook", guestbookName);
  Query query =
      new Query("Greeting", guestbookKey)
          .setAncestor(guestbookKey)
          .addSort("date", Query.SortDirection.DESCENDING);
  return datastore.prepare(query).asList(FetchOptions.Builder.withLimit(10));
}

Java 7

protected List<Entity> listGreetingEntities(DatastoreService datastore) {
  Key guestbookKey = KeyFactory.createKey("Guestbook", guestbookName);
  Query query =
      new Query("Greeting", guestbookKey)
          .setAncestor(guestbookKey)
          .addSort("date", Query.SortDirection.DESCENDING);
  return datastore.prepare(query)
      .asList(FetchOptions.Builder.withLimit(10));
}

這個方法能依留言板寫入單一實體群組,因此效能十分一致,不過,留言板變更也會因此受到限制,每秒最多只能寫入 1 次 (支援範圍內的實體群組限制)。若您的應用程式可能會出現較頻繁的寫入情形,可能需要考慮使用其他方法:例如可以為近期的 memcache 貼文設定到期日,並且混合顯示 memcache 和 Cloud Datastore 的近期貼文,也可以使用 Cookie 快取這些貼文,將部分狀態或其他完整資料放入網址中。這麼做的目的在於找到一個快取解決方案,以利目前使用者在您的應用程式中貼文的期間內提供資料。請記住,若在交易中執行 get 操作、祖系查詢或任何操作,一定會顯示最近寫入的資料。

本頁內容對您是否有任何幫助?請提供意見:

傳送您對下列選項的寶貴意見...

這個網頁
Java 8 適用的 App Engine 標準環境