高效同步资源

本指南介绍如何实现日历数据的“增量同步”。使用此方法,您可以在节省带宽的同时使所有日历集合的数据保持同步。

目录

概览

增量同步包含两个阶段:

  1. 初始完全同步从一开始执行一次,以使客户端的状态与服务器的状态完全同步。客户端将获取需要持久保留的同步令牌。

  2. 增量同步会重复执行,并使用自上次同步以来发生的所有更改更新客户端。每次,客户端都会提供从服务器获取的上一个同步令牌,并存储响应中的新同步令牌。

首次完全同步

初始完全同步是对您要同步的集合的所有资源的原始请求。如果您只想同步特定资源子集,可以选择使用请求参数限制列表请求。

在对列出操作的响应中,您会看到一个名为 nextSyncToken 的字段,该字段表示同步令牌。您需要存储 nextSyncToken 的值。如果结果集过大,并且响应被分页,则 nextSyncToken 字段仅出现在最后一页。

增量同步

借助增量同步,您可以检索自上次同步请求以来发生了修改的所有资源。为此,您需要执行一个列表请求,并在 syncToken 字段中指定您最近的同步令牌。请记住,结果将始终包含已删除的条目,以便客户端有机会从存储空间中移除这些条目。

如果自上次增量同步请求以来大量资源发生了更改,您可能会在列表结果中看到 pageToken,而不是 syncToken。在这些情况下,您需要执行与在增量同步中检索第一页时所用的完全相同的列表查询(具有完全相同的 syncToken),将 pageToken 附加到其中,并对后续所有请求进行分页,直到在最后一页上找到另一个 syncToken。请务必存储此 syncToken,以用于将来的同步请求。

以下示例查询适用于需要增量分页同步的情况:

原始查询

GET /calendars/primary/events?maxResults=10&singleEvents=true&syncToken=CPDAlvWDx70CEPDAlvWDx

// Result contains the following

"nextPageToken":"CiAKGjBpNDd2Nmp2Zml2cXRwYjBpOXA",

检索下一页

GET /calendars/primary/events?maxResults=10&singleEvents=true&syncToken=CPDAlvWDx70CEPDAlvWDx&pageToken=CiAKGjBpNDd2Nmp2Zml2cXRwYjBpOXA

服务器要求完全同步

有时,服务器会由于各种原因(包括令牌过期或相关 ACL 发生更改)导致同步令牌失效。在这种情况下,服务器将使用响应代码 410 来响应增量请求。这应该会触发对客户端存储区执行一次完全擦除操作,并触发新的完全同步。

示例代码

以下示例代码段演示了如何将同步令牌与 Java 客户端库结合使用。首次调用运行方法时,它会执行完全同步并存储同步令牌。后续每次执行时,系统都会加载已保存的同步令牌并执行增量同步。

  private static void run() throws IOException {
    // Construct the {@link Calendar.Events.List} request, but don't execute it yet.
    Calendar.Events.List request = client.events().list("primary");

    // Load the sync token stored from the last execution, if any.
    String syncToken = syncSettingsDataStore.get(SYNC_TOKEN_KEY);
    if (syncToken == null) {
      System.out.println("Performing full sync.");

      // Set the filters you want to use during the full sync. Sync tokens aren't compatible with
      // most filters, but you may want to limit your full sync to only a certain date range.
      // In this example we are only syncing events up to a year old.
      Date oneYearAgo = Utils.getRelativeDate(java.util.Calendar.YEAR, -1);
      request.setTimeMin(new DateTime(oneYearAgo, TimeZone.getTimeZone("UTC")));
    } else {
      System.out.println("Performing incremental sync.");
      request.setSyncToken(syncToken);
    }

    // Retrieve the events, one page at a time.
    String pageToken = null;
    Events events = null;
    do {
      request.setPageToken(pageToken);

      try {
        events = request.execute();
      } catch (GoogleJsonResponseException e) {
        if (e.getStatusCode() == 410) {
          // A 410 status code, "Gone", indicates that the sync token is invalid.
          System.out.println("Invalid sync token, clearing event store and re-syncing.");
          syncSettingsDataStore.delete(SYNC_TOKEN_KEY);
          eventDataStore.clear();
          run();
        } else {
          throw e;
        }
      }

      List<Event> items = events.getItems();
      if (items.size() == 0) {
        System.out.println("No new events to sync.");
      } else {
        for (Event event : items) {
          syncEvent(event);
        }
      }

      pageToken = events.getNextPageToken();
    } while (pageToken != null);

    // Store the sync token from the last request to be used during the next execution.
    syncSettingsDataStore.set(SYNC_TOKEN_KEY, events.getNextSyncToken());

    System.out.println("Sync complete.");
  }

旧版同步

对于事件集合,您仍然可以通过传统方式执行同步,方法是保留事件列表请求中已更新字段的值,然后使用 modifiedSince 字段检索更新后的事件。我们不再推荐使用此方法,因为它更容易错过更新(例如,如果不强制执行查询限制)。此外,它仅可用于活动。