本文章是由機器翻譯。

預測:雲端

同步處理 SQL Azure 與分支節點,第 2 部:服務型同步處理

Joseph Fultz

本文討論 Sync Framework 4.0 的預發佈版本;所有資訊均有可能發生變更。

上個月,我重點介紹了使用 SQL Azure 和各種目標節點同步企業資料庫的常規體系結構和設計。我談到了如何通過篩選或使用地理分佈(或結合使用這兩種策略)優化總體資料分佈和集合網路。

本月,我將引入 Windows Azure 來承載一項同步服務,重點討論如何通過雲中的服務介面進行同步。這將提供一種擴展同步機制的途徑,以便處理比資料庫直接同步方法更多的終端節點。我將使用 2010 年 10 月的 Microsoft Sync Framework 4.0 社區技術預覽 (CTP) 版 (bit.ly/dpyMP8),此版本是在 1 月文章所用的 2.1 框架上構建的。

在 2.1 版上可以直接構建同步服務,在 bit.ly/bibIdlbit.ly/epyImQ 上可以找到很好的相關示例和演練。但是,有了 4.0 CTP 及該版本中與 Internet 相關的元素,我們有很好的理由通過它實現 Windows Azure 同步服務。我們仍需編寫一定量的代碼才能得到一個實用的解決方案,但最後我們得到的同步服務可供使用 OData 的任何設備使用。

Internet 規模的同步

上個月,我提到過一些有關如何擴展資料庫直接同步的想法。但是,在一些為數不多的情況下,很多原因使得解決擴展問題不那麼容易。大概考慮一下以前所述的方法不能實現的情況,我們就可以得出以下結論:

  1. 由於資料之間存在關係,不能輕易拆分。
  2. 無法進行合理的資料分段,任何拆分都很牽強,這很可能在解決方案的不同分區中造成無法預料的熱點。
  3. 如果需要複製的資料只能存在於多處,這些資料量會導致成本效益的喪失。
  4. 在同步高峰時段,例如在日末處理成百上千零售位置的資料時,無論如何進行分區,都會導致爭用。

這顯然不是捨棄直接使用 SQL Azure 進行同步的設計的全部理由,但也已經足夠引入此話題以討論解決之道了。像電腦科學領域的大多數問題一樣,我將嘗試通過插入一個中間層來解決上面的問題。在這裡,這個中間層將是 Windows Azure Web 角色中承載的一個服務層,該服務層作為一個同步點,而不是直接與 SQL Azure 實例進行同步。我更新了上月的最終狀態圖,在其中為 Windows Azure 所承載的同步服務添加了一個位置,得到如圖 1 所示的邏輯設計。

圖 1 典型公司體系結構

入門

Sync Framework 4.0 特別有助於解決此問題。但是,與在資料庫之間直接同步的簡單模型相比,這需要多做一些工作。4.0 CTP 的説明檔中帶有很好的示例和演練,標題為“在 Windows Azure 中創建同步服務”。我將以此為基礎討論同步服務的實現。用戶端代碼要難一些,因為在 4.0 版中沒有用戶端運行時庫的説明,這是因為創建了抽象來打開到將使用 OData 的任意平臺的同步。但是,在 4.0 示例中有一個 Windows Mobile 6.5 示例使用了 SQL Server CE。我選用了所需代碼並加以修改,使其可用於標準 SQL Server。首先,2010 年 10 月版 4.0 CTP 使用一組特定的物件執行同步活動,先熟悉一下這些物件會有所説明。用戶端應用程式使用一個 CacheController,它負責使用 OData 與同步服務通信。在本地端,CacheController 使用 OfflineSyncProvider,這是應用程式和資料之間特定于資料存儲(可能是每個目標平臺)的介面(請參見圖 2)。此實現以該示例為基礎,其中使用一個 StorageHandler 物件處理本地資料訪問。OfflineSyncProvider 是已知類型,由 CacheController 使用,但 StorageHandler 是為處理所有後端存儲交互而編寫的自訂代碼。可將 OfflineSyncProvider 作為資料訪問庫上方的智慧層,將 StorageHandler 視為資料訪問庫。值得注意的是,4.0 CTP 只帶有一個用於 Silverlight 用戶端中的獨立存儲的內置 CacheController,這樣,我就必須做一些工作才能使用標準 SQL Server。物件佈局和交互邊界在圖 2 中以較高的層次表示。

圖 2 Sync Framework 4.0 用戶端同步物件

開發雲同步服務

總有人告訴我要先說壞消息再說好消息。這樣,對話或參與對話的人的精神最後總是積極的。不過,這一次我要反其道而行之,先從簡單的方面推銷一下這個解決方案。大部分工作都在用戶端進行,但該框架會為伺服器端的工作提供很多説明。在我主持的一次設計討論會上,有一個殯葬服務(提供葬禮、墓地、棺材等服務)銷售商曾告訴我,如果他們以“這是什麼”而不是“這有什麼作用”為重點,那麼他們一單買賣也做不成;就殯葬服務來說,真正購買的商品是內心的平靜,而不是棺材和墓穴。Sync Framework 也是同理。Sync Framework 2.1 為開發人員考慮了很多,但對於基於服務的同步而言則稍顯缺乏目標。它根本沒有考慮到的是,有太多的設備和平臺都要與通過面向 Internet 的同步服務所提供的資料進行同步。隨著現在流行的所謂 IT 消費化 的深入,我的客戶發現他們必須處理公司所有層面人員的眾多設備。Sync Framework 4.0 CTP 的目標就是説明應對這類挑戰,特別是在設備資料同步方面。

此解決方案在伺服器端的準備工作和操作非常簡單。基本上包括以下步驟:

  1. 定義資料庫
  2. 為數據庫創建設定檔
  3. 使用 SyncServiceUtil 通過設定檔配置資料庫
  4. 使用 SyncServiceUtil 生成同步服務所需的類
  5. 創建一個基於 Windows Azure 的 Web 角色以承載該服務
  6. 部署

讀到這個步驟摘要時,您可能會像我一樣在想“什麼設定檔?”。若如此,您可以在 位置的 MSDN 庫中找到該檔的架構 bit.ly/h2FJod.使用此架構,並參考 4.0 示例附帶的 ListDB 資料庫及其相關設定檔,就可以生成一個自訂設定檔,該檔能以盡可能清晰的方式表示資料庫。有了這個檔,創建基於 Windows Azure 的服務就很簡單了。首先,需要在 Windows Azure 中創建目標資料庫(在這裡為 4.0 SDK 中的 ListDB 示例)。完成此工作後,可以使用新的 SyncServiceUtil 通過類似下麵的命令配置資料庫:

SyncSvcUtil /mode:provision 
/scopeconfig:listdbconfig.xml

設定檔中唯一一個必需設置是到 SQL Azure 資料庫的連接。設定檔末尾附近有一個 <TargetDatabase />元素,需要為雲正確配置此元素:

<Databases>
  <TargetDatabase Name="listdb" DbServer="[URI for the SQL Azure DB 
   Instance]" DbName="listdb" UserName="[username]" Password="[password]" 
   UseIntegratedAuth="false" /> 
</Databases>

運行該實用工具將生成兩個檔:DefaultScopeEntities.cs 和 DefaultScopeSyncServices.svc。 名稱中的“DefaultScope”部分來自設定檔中的 <SyncScope />元素:

<SyncScope Name="DefaultScope" IsTemplateScope="true">

實體檔差不多就是這樣,但 DefaultScopeSyncServices.svc 檔要複雜一些,因為它要生成用以截獲服務調用及添加自訂邏輯(4.0 中的新功能)的分部類。基礎同步邏輯全部在基物件中提供。圖 3 顯示了 DefaultScopeSyncService 類及相關實體類作為範本類 SyncService 的範本類型。

圖 3 SyncServices 生成的代碼的物件流覽器視圖

請注意,圖 3 右側為進行同步而公開的服務介面清單有所減小(與直接使用 Sync Framework 2.1 所需公開的服務介面清單相比)。如果要向同步過程添加任何自訂邏輯,只需打開 DefaultScopeSyncServices.svc 檔,選取方法偵聽器並根據需要編寫即可。要使用剛創建的服務介面實現基礎同步,只需將包含這些檔的服務/Web 專案與一個 Web 角色關聯,並在 WebRole:OnStart 方法中添加一行以創建啟動上下文:

public override bool OnStart()
{
  DiagnosticMonitor.Start("DiagnosticsConnectionString");

  // For information on handling
  // configuration changes, see the MSDN topic at 
  // go.microsoft.com/fwlink/?LinkId=166357
  RoleEnvironment.Changing += RoleEnvironmentChanging;
  Microsoft.Samples.Synchronization.ActivationContext.
CreateActivationContext();
  return base.OnStart();
}

然後,更改幾項配置以確保將 Sync Framework 二進位檔案設置為 CopyAlways。 為使新的服務介面很好地工作,我要確保引用 4.0 Microsoft.Synchronization.dll 並將其設置為隨套裝軟體一起發佈。 然後,將其發佈到我的 Web 角色,這樣我就做好了準備。 我可以簡單測試一下,方法是在我的流覽器中輸入一個請求,比如 jofultz.cloudapp. net/defaultscopeSyncService.svc/$syncscopes,以請求當前可用的同步範圍。 我得到以下回應,這樣我就對該服務的正常工作有些信心了:

- <service xml:base="http://rd00155d3a1a55:20000/defaultscopesyncservice.svc/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns="http://www.w3.org/2007/app">
- <workspace>
  <atom:title>SyncScopes</atom:title> 
- <collection href="defaultscope">
  <atom:title>defaultscope</atom:title> 
  </collection>
  </workspace>
  </service>

我也可以請求其他資料,如果有更改,預設以 OData 形式獲取這些更改。可以在流覽器中或通過工具實現此目的。我使用 CodePlex 上的 OData Viewer 工具 (dataservicestool.codeplex.com/releases/view/52805) 發出下載更改的請求:jofultz.cloudapp.net/defaultscopeSyncService.svc/DefaultScope/DownloadChanges?userid=BA9152CC-4280-4DAC-B32D-1782E2E8C3D3。然後,得到如圖 4 所示的結果。

圖 4 OData Viewer 工具 DownloadChanges 結果

這裡有一個好消息,就是 Sync Framework 4.0 CTP 中的新增功能為這些簡化的同步介面提供了能以 OData ATOM 和 OData JSON 檢索的結果。這使從用戶端角度同步到其他平臺成為可能,並且將專用資料格式降級為舊式格式,而我需要做的全部工作就是運行一個實用工具,配置一個專案並添加一行代碼。

實現用戶端同步

這是實現中需要認真做重點研究的一部分工作。雲服務基本上屬於配置問題,只是如果從頭開始的話,用戶端的工作要多一些。由於 Sync Framework 4.0 CTP 附帶一個用於獨立存儲的 CacheController,如果 Silverlight 是目標用戶端平臺,那麼用戶端實現將像雲服務實現一樣簡單。不過,我的目標是運行 SQL Server Standard/Express 的 Windows 用戶端,這就需要做一些工作了。SyncServiceUtil 仍然可以通過生成需要的實體提供説明,但必須創建自訂的 CacheController 和 OfflineSyncProvider。更重要的是,為了便於進行更改跟蹤,需要修改資料存儲。可以通過使用 2.1 配置的資料庫或用於更改跟蹤的自訂架構實現此目的。由於資料庫實現和基本代碼都更加複雜,這樣的實現會顯著增加整個應用程式的工作量和複雜性。但是,為了利用框架的其餘部分,我們需要完成這些工作。向別人介紹這一點時,有人問我“為什麼不幹脆自己做這一切?”,答案很簡單:這樣做可以減少工作量,並引入其他 2.1 和 4.0 框架同步用戶端/代理(包括非 Windows 平臺)的同步實現。

讓我們看看圖 5 所示的分工來瞭解所討論的用戶端和服務部分。可以看到,使用該框架減少了約 60% 甚至更多的工作量,具體取決於目標用戶端平臺。

圖 5 用戶端和服務的分工

工作單元 工作方式
同步資料庫架構(伺服器) 配置
服務實現 生成的代碼 + 1 行代碼
自訂同步中的驗證掛接 生成的掛接;僅須編寫增值代碼
同步資料庫架構(用戶端) 雲使用 2.1 配置或自訂
非 Silverlight 的同步實現 自訂
同步 Silverlight 用戶端 配置 + 生成

Mobile 6.5 和 SQL CE 示例提供了為實現用戶端同步需要進行的資料庫處理的示例實現;請注意圖 6 中所示的 IsDirty、IsTombstone 和 Metadata 欄位。

圖 6 支援自訂同步實現的列

架構就位後,我還需要做另外幾件事:

  1. 前面所提到的 CacheController 實現
    1. 本機存放區交互
    2. 服務交互
    3. 同步衝突處理常式
    4. 同步錯誤處理常式
  2. 生成和使用 OData 的一些代碼
  3. 所同步實體的代碼定義
  4. 本地 SQL Server 資料庫的 OfflineSyncProvider

對於專案 1 和 2,我使用為 6.5 示例提供的代碼(請參見圖 7),並將其放入我自己的 CacheController 專案中,該專案中的代碼全部借用自該示例。

圖 7 從 6.5 示例借用的檔

我使用 SyncServiceUtil 生成實體,這些實體使用與前面相同的設定檔,並使用“/mode:codegen”和“/target:client”標記。這會生成一個 DefaultScopeEntities.cs 檔,其中包含我的用戶端物件。因為我要從 6.5 借用,所以將 settings.cs、utility.cs、SqlCeOfflineSyncProvider.cs、DataStoreHelper.cs 和 SqlCeStorageHandler.cs 複製到我的 Windows 表單專案中。為儘量減少編碼工作,我進行了如圖 8 所示的更改。

圖 8 為儘量減少編碼工作進行的示例代碼更改

檔/專案 更改
DefaultScopeEntities.cs 將該類重命名為 SqlCeOfflineEntity 以便與借用的檔中需要的類型名稱相符。
 

[Microsoft.Samples.Synchronization.ClientServices.KeyAttribute]

添加到存在

[System.ComponentModel.DataAnnotations.KeyAttribute()]

的每個位置,因為它在 CacheController 實現內部使用

我的新的 CacheController 專案

將所有命名空間替換為

namespace Microsoft.Samples.Synchronization.ClientServices.Custom

SqlCeOfflineSyncProvider.cs

using Microsoft.Samples.Synchronization.ClientServices;

替換為

using Microsoft.Samples.Synchronization.ClientServices.Custom;

以引用我的自訂 CacheController 實現

SqlCeStorageHandler.cs 從檔中注釋掉所有 [連接].[事務命令]:操作 SQL Server 所需的實現與操作 SQL CE 有所不同,對於真實的實現,需要重新正確添加這些內容
DataStoreHelper.cs 將連接字串更改為指向我的本地 SQL Server 實例
Settings.cs 將我的 Windows Azure 同步服務 URI 分配給 SyncServiceUrl (http://jofultz.cloudapp.net/DefaultScopeSyncService.svc/)
Utility.cs

using Microsoft.Samples.Synchronization.ClientServices;

替換為

using Microsoft.Samples.Synchronization.ClientServices.Custom;

以引用我的自訂 CacheController 實現

通過利用示例代碼並進行這些更改,我就能編寫一個小型主控台應用程式來調用 Utility.Sync 函數,該函數又可以產生實體 OfflineSyncProvider 和 CacheController 以執行同步:

var localProvider = new   
  SqlCeOfflineSyncProvider();
var controller = new CacheController(new 
  Uri(Settings.SyncServiceUrl), Settings.
SyncScope, localProvider);

有人可能會問,執行像從本機存放區中提取更改記錄這樣工作的代碼在哪裡? 所有這些都在 StorageHandler 實現中。 請看圖 9,裡面有其中的部分內容。

圖 9 本機存放區資料命令

internal class SqlCeStorageHandler : IDisposable
  {
    #region SQL CE Commands

    private const string GET_ALL_PRIORITY = "SELECT [ID], [Name], [_
      MetadataID] FROM [Priority] WHERE [IsTombstone] = 0";

    private const string GET_ALL_STATUS = "SELECT [ID], [Name], [_
      MetadataID] FROM [Status] WHERE [IsTombstone] = 0";

    private const string GET_ALL_TAGS = "SELECT [ID], [Name], [_
      MetadataID] FROM [Tag] WHERE [IsTombstone] = 0";

    private const string GET_ALL_LISTS =
      "SELECT [ID], [Name], [Description], [UserID], [CreatedDate], 
      [IsTombstone], [_MetadataID] FROM [List] WHERE [IsTombstone] = 0";

    private const string GET_ALL_ITEMS =
      "SELECT ID, ListID, UserID, Name, Description, Priority, Status, 
      StartDate, EndDate, IsTombstone, [_MetadataID] FROM [Item] WHERE 
      [IsTombstone]=0 AND [ListID]=@ListID";

    private const string SELECT_ITEM_CHANGES =
      "SELECT ID, ListID, UserID, Name, Description, Priority, Status, 
      StartDate, EndDate, IsTombstone, [_MetadataID] FROM [Item] WHERE 
      IsDirty = 1";

    private const string SELECT_LIST_CHANGES =
      "SELECT ID, Name, Description, UserID, CreatedDate, IsTombstone, 
      [_MetadataID] FROM [List] WHERE IsDirty = 1";

    private const string SELECT_TAGITEMMAPPING_CHANGES =
      "SELECT TagID, ItemID, UserID, IsTombstone, [_MetadataID] FROM 
      [TagItemMapping] WHERE IsDirty = 1";

因此,操作按以下順序繼續:

  1. 用戶端應用程式調用任意同步函數
  2. 同步函數
    1. 產生實體 OfflineSyncProvider
    2. 產生實體 CacheController(這是自訂的),產生實體時傳入服務 URI 和 OfflineSyncProvider
    3. 最後調用 CacheController.Refresh()
  3. CacheController 創建一個 CacheRequestHandler,後者將處理與 Windows Azure 中的同步服務的通信
  4. CachController 向 OfflineSyncProvider 請求本地變更集
  5. OfflineSyncProvider 使用 StorageHandler 從 SQL Server 檢索更改
  6. CacheController 使用變更集創建請求並將請求傳遞給 CacheRequestHandler
  7. CacheRequestHandler 使用合適的格式化程式(在本例中是 OData ATOM)創建合適的請求,並將其發送給同步服務 URI

當然,解包和將資料返回用戶端的操作基本上就是反向執行的相同操作。圖 4 所示為從服務返回的 OData 資料包。

結論

取消事務支援並為物件保留 SqlCe[尾碼] 之類的不當使用顯然不能用於真正的實現,但在這裡達到了不必編寫所有新代碼就能使用戶端版本工作的目的。任何人想要創建 SQL Server CacheController 雲,都可以從 6.5 示例輕鬆開始,進行重構和重命名,而主要工作在於處理 StorageHandler 內的命令,這些命令需要根據具體資料存儲加以調整。

我在這裡的主要目的是演示基於服務的同步體系結構。我有意忽略了進行擴展所需的緩存和其他優化,不過這些內容一般較容易理解。此外,我還想在説明讀者熟悉 Sync Framework 4.0 CTP 的同時,向讀者介紹它有什麼功能、沒什麼功能以及可能實現什麼功能。希望我達到了這些目的。

借助現行的 SQL Azure Data Sync CTP 2,我保證能夠通過配置和下載用戶端代理完成這些工作(包括用戶端工作)。當然,這是對基於 Windows 的電腦而言,但如果目標是範圍更廣的平臺,直接使用 Sync Framework 4.0 可能是更好的選擇。

我建議您下載最新的 Sync Framework SDK,至少按照教程所述使用 SQL Azure 資料庫在 Windows Azure 中設置同步服務,並按照 Silverlight 用戶端示例操作來找找感覺。如果您勇於探索,可以按前面所述取用 4.0 CTP 中的 Windows Mobile 6.5 示例中的檔(有兩個專案),使用它們創建您自己的基於 Windows 的同步用戶端。

Joseph Fultz 是達拉斯 Microsoft 技術中心的架構師,協助企業客戶和 ISV 設計和製作軟體解決方案以滿足商業和市場需求。他在 Tech·Ed 及類似的內部培訓活動中做過講座。

衷心感謝以下技術專家對本文的審閱: Ganeshan Iyer