预测:多云

使用 SQL Azure 实现分支节点同步,第 2 部分:基于服务的同步

Joseph Fultz

本文讨论 Sync Framework 4.0 的预发布版本;所有信息均有可能发生变更。

image: Joseph Fultz
上个月,我重点介绍了使用 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. 部署

读到这个步骤摘要时,您可能会像我一样在想“什么配置文件?”。若如此,您可以在 bit.ly/h2FJod 位置的 MSDN 库中找到该文件的架构。使用此架构,并参考 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 项目中,该项目中的代码全部借用自该示例。

image: Files Used from the 6.5 Sample

图 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