Прогноз: облачно

Синхронизация между подразделениями и узлами с помощью SQL Azure. Часть 2: синхронизация на основе сервисов

Джозеф Фулц

В этой статье рассматривается предварительная версия Sync Framework 4.0.Любая изложенная здесь информация может быть изменена.

image: Joseph Fultz В прошлый раз я сосредоточился на общей архитектуре и проектировании синхронизации корпоративных баз данных, используя SQL Azure и различные узлы назначения (destination nodes). Мы говорили об оптимизации с помощью фильтрации, а также применяли территориальное распределение (или использовали эти две стратегии совместно) для оптимизации сети распределения и сбора данных в целом.

На этот раз я собираюсь разместить в Windows Azure сервис синхронизации и сосредоточиться на синхронизации через интерфейс сервиса в облаке. Благодаря этому можно будет масштабировать механизм синхронизации для обработки гораздо большего количества конечных узлов, чем в случае синхронизации напрямую между базами данных. С этой целью я воспользуюсь CTP-версией Microsoft Sync Framework 4.0 за октябрь 2010 г.(bit.ly/dpyMP8), которая опирается на инфраструктуру версии 2.1, примененную в прошлой статье.

Сервис синхронизации можно создать прямо на основе версии 2.1, и детальный пример этого можно найти по ссылкам bit.ly/bibIdl и bit.ly/epyImQ. Однако с появлением CTP-версии инфраструктуры Sync Framework 4.0 и повышенным вниманием в ней к элементам, относящимся к Интернету, имеет смысл задействовать именно ее для создания сервиса синхронизации в Windows Azure. Для получения функционального решения по-прежнему потребуется писать приличное количество кода, но в конечном счете мы получим сервис синхронизации, который смогут использовать любые устройства с поддержкой OData.

Синхронизация в масштабах Интернета

В прошлом месяце я осветил некоторые идеи насчет масштабирования синхронизации напрямую с базой данных (direct-to-database sync). Однако в некоторых (если не во многих) случаях добиться такого масштабирования по ряду причин не так-то просто. Даже если поверхностно поразмыслить о проблемах, которые мы не будем решать с помощью ранее описанных подходов, любой может легко прийти к следующим выводам.

  1. Из-за взаимосвязанности данных их не так-то легко разделять.
  2. Нет логичной сегментации, и любое разделение данных было бы произвольным, что скорее всего привело бы к появлению непредвиденных «горячих точек» в различных разделах решения.
  3. Объемы данных, подлежащих репликации, настолько велики, что, если бы они должны были находиться во множестве мест, решение оказалось бы крайне неэкономичным.
  4. Пиковые всплески синхронизации; например, обработка в конце рабочего дня информации от сотен или тысяч точек розничной торговли привела бы к конкуренции несмотря на разбиение на разделы.

Этим списком возможные причины, из-за которых требуется проект, отличный от прямой синхронизации с SQL Azure, отнюдь не исчерпываются, но и его достаточно, чтобы задуматься о другом подходе к решению задачи синхронизации. Как и в большинстве других задач компьютерной науки, я попытаюсь решить проблемы добавлением уровня абстракции. В данном случае это будет уровень сервисов, размещаемый в веб-роли Windows Azure, которая используется как точка синхронизации; это избавит нас от прямой синхронизации с экземпляром SQL Azure. Я внес соответствующие изменения в заключительную схему из прошлой статьи, добавив место для сервиса синхронизации, размещаемой в Windows Azure, и это привело меня к логической архитектуре, показанной на рис. 1.

image: Typical Corporate Architecture

Рис. 1. Типичная корпоративная архитектура

Приступаем

Sync Framework 4.0 великолепно подходит для решения этой задачи. Однако она потребует проделать несколько больше работы, чем в случае простой модели прямой синхронизации между базами данных. Ее CTP-версия поставлялась с хорошим примером и пошаговой инструкцией в справочном файле «Creating a Sync Service in Windows Azure». Я возьму этот пример за основу при обсуждении реализации Sync Service (сервиса синхронизации). Клиентский код будет потруднее, так как в версии 4.0 нет библиотеки исполняющей среды на клиентской стороне из-за включения абстракции, позволяющей запускать синхронизацию на любой платформе, способной работать с OData. Однако есть один пример для Windows Mobile 6.5 в сочетании с SQL Server CE. Я позаимствовал оттуда необходимый код и изменил его так, чтобы он работал со стандартной СУБД SQL Server. CTP-версия инфраструктуры 4.0 за октябрь 2010 г. использует определенный набор объектов для выполнения операций синхронизации, и вам будет легче, если вы знакомы с ними. Клиентское приложение использует CacheController, который отвечает за взаимодействие с Sync Service по OData. На локальной стороне CacheController обращается к OfflineSyncProvider, который является специфичным для конкретного хранилища данных — и скорее всего для целевой платформы — интерфейсом между приложением и данными (рис. 2). В этой реализации, основанной на упомянутом примере, объект StorageHandler обслуживает доступ к локальным данным. OfflineSyncProvider — известный тип, используемый CacheController, но для StorageHandler нужно писать собственный код для обработки всех взаимодействий с серверным хранилищем. Рассматривайте OfflineSyncProvider как дополнительную «интеллектуальную» логику поверх библиотеки для доступа к данным, а StorageHandler — как эту самую библиотеку. Между прочим, CTP-версия инфраструктуры 4.0 поставляется только со встроенным CacheController for Isolated Storage в клиенте Silverlight, что требует от меня проделать некоторую работу для использования стандартной SQL Server. Структура объектов и границы взаимодействия на высоком уровне представлены на рис. 2.

image: Sync Framework 4.0 Client Synchronization Objects

Рис. 2. Клиентские объекты синхронизации в Sync Framework 4.0

Разработка облачного сервиса синхронизации

Мне всегда говорили, что плохие новости надо выкладывать первыми, а хорошие — оставлять напоследок. Тогда разговор закончится на позитивной ноте (и, возможно, настроение его участников не будет безнадежно испорчено). Но в данном случае я поступлю прямо наоборот в надежде, что само решение покажется вам не столь сложным. Основной объем работы на клиентской стороне вам придется выполнять самостоятельно, а на серверной — во многом поможет инфраструктура. Как-то раз мне сказали, что в сфере ритуальных услуг никто не сумел бы ничего продать, если бы зациклился на том, «что это такое».Вместо этого нужно уделять основное внимание тому, «что это дает».В случае ритуальных услуг реальным товаром является покой души, а не гроб и яма в земле. Точно такая же ситуация и с Sync Framework. Sync Framework 2.1 очень много делала за разработчика, но не совсем оправдывала ожидания, когда дело доходило до синхронизации на основе сервиса. Она вовсе не поддерживала то изобилие устройств и платформ, с данными на которых вам могла понадобиться синхронизация и которые делались доступными через сервис синхронизации в Интернете. При нынешней ориентации информационных технологий (ИТ) на потребителя мои заказчики вынуждены иметь дело со множеством устройств на руках у сотрудников на всех организационных уровнях своих предприятий. CTP-версия Sync Framework 4.0 рассчитана на то, чтобы помочь в решение задач этого типа, особенно с учетом синхронизации данных на упомянутых устройствах.

Подготовить серверную часть этого решения довольно просто. По сути, все сводится к следующему.

  1. Определение базы данных.
  2. Создание для нее конфигурационного файла.
  3. Обеспечение доступа к базе данных с помощью SyncServiceUtil на основе конфигурационного файла.
  4. Генерация с помощью SyncServiceUtil классов, необходимых сервису синхронизации.
  5. Создание веб-роли Windows Azure для размещения сервиса.
  6. Развертывание.

Наверняка вы подумали: какой конфигурационный файл? Схему для этого файла можно найти в MSDN Library по ссылке bit.ly/h2FJod. Используя ее, а также базу данных ListDB и соответствующий конфигурационный файл, поставляемые с примерами для инфраструктуры версии 4.0, вы можете с минимальными усилиями собрать собственный конфигурационный файл, представляющий вашу базу данных. Как только у вас появляется этот файл, создание сервисов на основе Windows Azure становится проще пареной репы. Сначала вы должны создать в Windows Azure целевую базу данных — в нашем случае пример ListDB из SDK для версии 4.0. После этого, используя новый 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.

image: Object Browser View of SyncServices Generated Code

Рис. 3. Представление сгенерированного кода SyncServices в Object Browser

Заметьте, что на правой стороне на рис. 3 приводится сокращенный список интерфейсов, предоставляемых для выполнения синхронизации (по сравнению с тем, что потребовалось бы предоставлять при прямом использовании Sync Framework 2.1). Если бы я хотел добавить дополнительную логику в процесс синхронизации, то просто открыл бы файл DefaultScopeSyncServices.svc, выбрал бы перехватчик метода и написал бы свою реализацию. Чтобы реализовать базовую синхронизацию через только что созданный интерфейс сервиса, мне нужно лишь сопоставить проект сервиса, содержащий эти файлы, с веб-ролью и добавить в метод 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. Для использования преимуществ нового интерфейса сервисов я убедился, что у меня есть ссылка на Microsoft.Synchronization.dll из инфраструктуры версии 4.0 и что эта библиотека настроена на публикацию вместе с пакетом. После ее публикации в веб-роли все готово. Можно провести небольшой тест, запросив доступные в настоящее время области синхронизации.Для этого в браузере надо ввести запрос, такой как 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 по умолчанию. Это можно сделать в браузере или с помощью какой-либо утилиты. Используя, например, OData Viewer Tool с сайта CodePlex (dataservicestool.codeplex.com/releases/view/52805), я выдаю запрос на скачивание изменений: jofultz.cloudapp.net/defaultscopeSyncService.svc/DefaultScope/DownloadChanges?userid=BA9152CC-4280-4DAC-B32D-1782E2E8C3D3 и получаю результаты, показанные на рис. 4.

image: OData Viewer Tool DownloadChanges Result

Рис. 4. Результат, полученный OData Viewer Tool

Хорошая новость в том, что дополнения в Sync Framework 4.0 CTP предоставляют более простые интерфейсы синхронизации, позволяющие получать результаты в форматах OData ATOM и OData JSON. Это открывает возможности синхронизации с другими платформами и низводит закрытые форматы просто до устаревших; все, что мне понадобилось для этого, — запустить утилиту, сконфигурировать проект и добавить строку кода.

Реализация синхронизации на клиентской стороне

В этом месте реализации включаются наушники и многозадачность уступает место. Если создание облачного сервиса заключалось главным образом в конфигурировании, то клиентская часть требует куда больше работы, если вы начинаете с нуля. Sync Framework 4.0 CTP поставляется с CacheController для изолированного хранилища, и, если целевой клиентской платформой является Silverlight, клиентская реализация будет такой же простой, как и реализация облачного сервиса. Однако моей целевой платформой является Windows-клиент, выполняющий SQL Server Standard/Express, и это потребует некоторых усилий. SyncServiceUtil все равно помогает, генерируя необходимые сущности, но придется создавать собственные CacheController и OfflineSyncProvider. Что важнее, понадобится модификация хранилища данных, чтобы можно было отслеживать изменения. Это можно было бы сделать с базой данных, подготовленной в версии 2.1, или использовать собственную схему для отслеживания изменений. Такая реализация могла бы добавить значительный объем работы и существенно усложнить базу данных и кодовую базу. Однако это нужно проделать, чтобы задействовать остальную часть инфраструктуры. Когда я все это рассказываю своим слушателям, меня спрашивают:«А почему бы не сделать все это самостоятельно?». Ответ прост: я делаю так, чтобы уменьшить объем работы и ввести поддержку синхронизации с другими клиентами/агентами (в том числе на платформах, отличных от Windows), на которых установлена инфраструктура синхронизации версий 2.1 и 4.0.

Взгляните на распределение работы (табл. 1) для клиента и сервиса. Как видите, использование инфраструктуры сокращает объем работ примерно на 60% и более в зависимости от целевой клиентской платформы.

Табл. 5. Распределение работы для клиента и сервиса

Единица работы Реальные усилия
Схема базы данных для синхронизации (сервер) Конфигурирование
Реализация сервиса Сгенерированный код + одна строка кода
Настройка точек подключения для проверки в процессе синхронизации Точки подключения генерируются; нужно написать лишь код, отвечающий за дополнительную логику (если она требуется)
Схема базы данных для синхронизации (клиент) Можно использовать базу данных, подготовленную в версии 2.1, или создать собственную схему
Реализация синхронизации для клиентов, отличных от Silverlight Целиком возлагается на вас
Клиент синхронизации для Silverlight Конфигурирование + генерация

Работа с примером для Windows Mobile 6.5 и SQL CE дала мне образец того, что можно было бы сделать с базой данных, чтобы реализовать клиентскую синхронизацию; обратите внимание на поля IsDirty, IsTombstone и Metadata, показанные на рис. 5.

image: Columns to Support Custom Synchronization Implementation

Рис. 6. Поля для поддержки собственной реализации синхронизации

Подготовив схему, нужно проделать и другую работу.

  1. Реализовать CacheController, как уже упоминалось:
    1. взаимодействие с локальным хранилищем;
    2. взаимодействие с сервисом;
    3. создать обработчик конфликтов при синхронизации;
    4. создать обработчик ошибок при синхронизации.
  2. Сгенерировать кое-какой код для использования OData.
  3. Определить в коде сущности, подлежащие синхронизации.
  4. Создать OfflineSyncProvider для локальной базы данных SQL Server.

В пп.1 и 2 я использую код из примера для Windows Mobile 6.5 и SQL CE (рис. 6) и помещаю его в свой проект CacheController, который полностью состоит из кода, позаимствованного из этого примера.

image: Files Used from the 6.5 Sample

Рис. 7. Файлы, используемые из примера для Windows Mobile 6.5 и SQL CE

Я использую SyncServiceUtil для генерации сущностей на основе того же конфигурационного файла, что и раньше, наряду с флагами «/mode:codegen» и «/target:client». Это приводит к созданию файла DefaultScopeEntities.cs, в котором находятся мои объекты для клиентской стороны. Поскольку я заимствую код из примера, я копирую в свой проект Windows Forms файлы settings.cs, utility.cs, SqlCeOfflineSyncProvider.cs, DataStoreHelper.cs и SqlCeStorageHandler.cs. Чтобы свести к минимуму кодирование, я внес изменения, показанные в табл. 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 Закомментируйте в этом файле все [connection].[transaction commands]: работа с SQL Server требует несколько иной реализации, чем в случае SQL CE
DataStoreHelper.cs Измените строку подключения, чтобы она указывала на локальный экземпляр SQL Server
Settings.cs Укажите URI моего сервиса синхронизации Windows Azure в 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. Instantiates 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, мог бы легко начать с примера для Windows Mobile 6.5 и SQL CE, провести рефакторинг кода и переименование; при этом основная часть работы выпала бы на команды внутри StorageHandler, которые должны быть специфичны для конкретного хранилища данных.

Моей основной целью было продемонстрировать архитектуру синхронизации на основе сервиса. Я намеренно пренебрег кешированием и другими оптимизациями, необходимыми для масштабирования, но эти части обычно и так хорошо понятны. Кроме того, я хотел показать, что есть, чего нет и что возможно в новой инфраструктуре Sync Framework 4.0 CTP. Надеюсь, мне это удалось.

В ближайшей перспективе ожидается появление SQL Azure Data Sync CTP 2, в которой должна быть введена поддержка настройки всех частей, в том числе клиентской, через конфигурацию и загрузку агента, размещаемого на клиентской стороне. Конечно, это предназначено для систем на основе Windows, но, если ваша цель — охват более широкого спектра платформ, прямое использование Sync Framework 4.0, возможно, будет лучшим выбором.

Советую скачать последнюю версию Sync Framework SDK и по крайней мере проработать руководство по созданию сервиса синхронизации в Windows Azure с применением базы данных SQL Azure, а также пример клиента Silverlight. Те, кто посмелее, могут взять файлы из примера для Windows Mobile 6.5 и задействовать их в Sync Framework 4.0 CTP (два проекта) для создания собственного Windows-клиента синхронизации.

Джозеф Фулц (Joseph Fultz) — архитектор в Microsoft Technology Center в Далласе, где он работает как с корпоративными заказчиками, так и с независимыми разработчиками ПО (ISV), проектируя и создавая прототипы программных решений, отвечающих потребностям бизнеса и рынка. Выступал на различных конференциях вроде Tech·Ed, а также на внутренних мероприятиях, направленных на повышение квалификации сотрудников.

Выражаю благодарность за рецензирование статьи эксперту Ганешану Айеру (Ganeshan Iyer)