Службы SSIS для Azure и гибридное перемещение данных

advertisement
Службы SSIS для Azure и гибридное
перемещение данных
Техническая статья по SQL Server
Авторы: Алексей Халяко (Alexei Khalyako), Карла Саббота (Carla Sabotta), Дэниел Сол
(Daniel Sol), Шредхар Пеллуру (Sreedhar Pelluru), Стив Говард (Steve Howard)
Технический рецензент: Дэвид Плесс (David Pless)
Опубликовано: декабрь 2012 г.
Область применения: SQL Server 2012
Сводка. Службы SQL Server Integration Services (SSIS) эффективны в качестве средства
перемещения данных в базы данных SQL Windows Azure (WA) и обратно в рамках общего
решения для извлечения, преобразования и загрузки данных (ETL) и решения переноса
данных. Службы SSIS могут быть эффективно задействованы для перемещения данных
между источниками и получателями данных в облаке, а также в гибридных сценариях,
включающих облачные и локальные базы данных. В этом документе приводятся общие
рекомендации по использованию служб SSIS, касающиеся применения служб SSIS для
облачных источников и получателей, обсуждается планирование проектов служб SSIS
для перемещения данных в Azure и гибридных средах, а также дается пошаговое
описание примера максимального повышения производительности работы
гибридной схемы за счет масштабирования переноса данных.
Авторские права
Документ предоставляется «как есть». Отраженные в нем сведения и мнения, включая URL-адреса
и другие ссылки на веб-сайты, могут измениться без уведомления. Вы принимаете на себя риск,
связанный с пользованием этим документом.
Некоторые примеры, содержащиеся в этом документе, вымышлены и приводятся исключительно
в демонстрационных целях. Любое сходство с реальными ситуациями случайно.
Настоящий документ не предоставляет пользователям права на интеллектуальную собственность
продуктов Майкрософт. Разрешается копирование и использование документа в справочных
целях, для внутреннего использования.
© Корпорация Майкрософт (Microsoft Corporation), 2011. Все права защищены.
2
Оглавление
Введение ...................................................................................................................................................... 5
Структура проекта ....................................................................................................................................... 5
Рекомендации и варианты для Azure ....................................................................................................... 5
Источники и получатели ......................................................................................................................... 6
ODBC ..................................................................................................................................................... 6
ADO.NET................................................................................................................................................ 8
Сегментирование и федерации ............................................................................................................. 8
Импорт в федерации......................................................................................................................... 10
Процессы служб SSIS в Windows Azure ................................................................................................ 20
Жизненный цикл виртуальных машин SSIS .................................................................................... 21
Расположение процессов служб SSIS .................................................................................................. 22
Перемещение данных из Azure в Azure .......................................................................................... 22
Гибридное перемещение данных ................................................................................................... 23
Работа в случае ограниченной пропускной способности сети ......................................................... 23
Сжатие данных .................................................................................................................................. 23
Двухэтапная передача ...................................................................................................................... 24
Применение принципов масштабирования в WA ............................................................................. 24
Несколько постоянных виртуальных машин или серверов извлечения служб SSIS ................... 24
Очереди.............................................................................................................................................. 25
Хранилище больших двоичных объектов ....................................................................................... 25
Проектирование логики повтора без ручного вмешательства ............................................................. 26
Включение в пакет логики повторов ................................................................................................... 26
Включение логики повторов в пользовательские компоненты ....................................................... 31
Повторы перемещения данных при программном построении или выполнении......................... 31
Гибридное перемещение данных при работе SQL Server на виртуальных машинах WA .................. 31
Стратегии................................................................................................................................................ 32
Загрузка данных напрямую на виртуальную машину WA............................................................. 34
Использование передачи файлов для перемещения данных между локальным сервером и
виртуальной машиной WA ............................................................................................................... 34
Рекомендации по настройке локального и гибридного перемещения данных ............................. 44
Сеть ..................................................................................................................................................... 44
3
Поток данных пакета служб SSIS ...................................................................................................... 45
Драйверы соединения ...................................................................................................................... 45
База данных SQL Server (как получатель) ........................................................................................ 45
Решение: масштабирование и гибридное перемещение данных ....................................................... 46
Требования решения ............................................................................................................................ 46
Структура решения................................................................................................................................ 46
Архитектура ....................................................................................................................................... 47
Вопросы проектирования ................................................................................................................. 51
Реализация решения ............................................................................................................................ 51
Требования к оборудованию и программному обеспечению ..................................................... 51
Установка служб SSIS на локальном компьютере и облачных виртуальных машинах............... 51
Установка пользовательских компонентов сжатых файлов .......................................................... 51
Настройка очереди экспорта и импорта SQL Azure........................................................................ 51
Настройка хранилища больших двоичных объектов SQL Azure ................................................... 52
Создание приложения-координатора ............................................................................................ 52
Создайте приложение экспорта. ..................................................................................................... 52
Создайте приложение для формирования пакетов в облаке ....................................................... 52
Сегментирование данных с помощью преобразования «Условное разбиение» ....................... 53
Заключение ................................................................................................................................................ 53
Приложение A. Пример процесса координатора .................................................................................. 54
Приложение B. Пример процесса извлечения ....................................................................................... 65
Приложение C. Пример процесса импорта ............................................................................................ 71
Приложение D. Образец пакета шаблона............................................................................................... 83
4
Введение
Службы SQL Server Integration Services (SSIS) эффективны в качестве средства
перемещения данных в базы данных SQL Windows Azure и обратно в рамках общего
решения для извлечения, преобразования и загрузки данных (ETL) и решения переноса
данных. Службы SSIS могут быть эффективно задействованы для перемещения данных
между источниками и получателями данных в облаке, а также в гибридных сценариях,
включающих облачные и локальные базы данных. В этом документе приводятся общие
рекомендации по использованию служб SSIS, касающиеся применения служб SSIS для
облачных источников и получателей, обсуждается планирование проектов служб SSIS
для перемещения данных в Azure и гибридных средах, а также дается пошаговое
описание примера максимального повышения производительности работы гибридной
схемы за счет масштабирования переноса данных.
Структура проекта
Проекты по переносу данных между облачными и локальными хранилищами данных
могут включать в себя различные процессы, работающие в рамках различных решений.
Часто решение состоит из нескольких частей, начиная от заполнения получателя, данные
для которого могут браться из другой системы или с другой платформы, и заканчивая
выполнением таких операций обслуживания, как повторная балансировка наборов
данных между изменяющимся числом секций или сегментов и, возможно, периодическое
выполнение массового копирования или обновления. Структура и базовые принципы
проекта для работы с облачными данными часто отличаются от тех, на основе которых
строятся традиционные решения для перемещения данных в полностью локальных
средах. Накопленные знания, опыт и практика тоже пригодятся, но необходимо учитывать
и некоторые особенности, например тот факт, что после перехода к пулу общих ресурсов
среда больше не будет самодостаточной и подконтрольной только вам. Эти различия
требуют более сбалансированного и масштабируемого подхода.
Полное описание структуры проекта см. в документе «Руководство по эксплуатации
и настройке служб SQL Server 2012 SSIS» в библиотеке MSDN в разделе
Технические документы Microsoft для SQL Server 2012
(http://msdn.microsoft.com/ru-ru/library/hh403491.aspx).
Рекомендации и варианты для Azure
Платформа Windows Azure (WA) предоставляет определенные возможности, но вместе с тем
создает и некоторые проблемы, связанные со службами SSIS. В этом разделе обсуждаются
некоторые из них — как возможности, так и варианты решения распространенных проблем.
Кроме того, здесь рассматриваются основные понятия масштабирования передачи данных
в качестве облачного решения для WA.
5
Источники и получатели
Источники и получатели данных, расположенные на постоянных виртуальных машинах
WA, могут использоваться точно так же, как и в локальных системах. Но не все локальные
решения будут поддерживаться для баз данных SQL Windows Azure. Например, база
данных SQL Windows Azure не поддерживает OLE-DB, поэтому при передаче данных
на платформу Azure или обратно нужно пользоваться соединениями ODBC или ADO.NET.
ODBC
ODBC можно использовать вместе с драйверами SQL Server 2012 для подключения к базе
данных SQL Windows Azure. При создании диспетчера соединений для подключения к SQL
Azure выполните следующие действия.
1. Выберите Создать в диалоговом окне Настройка диспетчера соединений ODBC, чтобы
открыть диалоговое окно Диспетчер соединений.
2. Выберите вариант Использовать строку подключения:.
3. Создайте строку подключения в следующем формате.
Driver={SQL Server Native Client
11.0};Server=tcp:myservername.database.windows.net;Database=mydbname;Uid=mylogin;Pwd
=myPassword;
Замените «myservername», «mydbname», «mylogin» и «myPassword» соответствующими
значениями.
4. Нажмите кнопку Построить. Поля Имя пользователя и Пароль заполнятся значениями,
указанными в строке подключения.
5. Нажмите кнопку Проверить соединение, чтобы подтвердить возможность установления
соединения.
6
Настройка диспетчера соединений служб SSIS с помощью параметра «Использовать строку подключения».
ПРИМЕЧАНИЕ. Убедитесь, что правила брандмауэра базы данных SQL Windows Azure
позволяют принимать соединения с вашего IP-адреса. При отсутствии правила
брандмауэра, позволяющего принимать соединения с вашего IP-адреса, вы не сможете
установить соединение. Сведения о настройке брандмауэра базы данных Windows Azure
SQL и инструкции по заданию правил брандмауэра см. в разделе Брандмауэр базы данных
SQL Windows Azure (http://msdn.microsoft.com/ru-ru/library/windowsazure/ee621782.aspx).
ПРИМЕЧАНИЕ. При создании строки подключения обязательно пользуйтесь атрибутом
Database. Если не указать базу данных, будет установлено соединение с базой данных
Master. Команда USE для смены контекста базы данных в SQL Azure не поддерживается.
7
ADO.NET
ADO.NET может быть использован в качестве источника или получателя для SQL Azure. При
настройке диспетчера соединений ADO.NET для использования с SQL Azure в диалоговом
окне Диспетчер соединений выберите Использовать проверку подлинности SQL Server.
Сегментирование и федерации
Поскольку во многих локальных решениях большие объемы данных могут размещаться
в одной базе данных, ограничения, связанные с размером и управляемостью, а также
необходимость масштабирования источников данных приводят к созданию архитектур
с сегментированием или федерациями в базах данных SQL Windows Azure.
Функциональное или вертикальное сегментирование выполняется путем размещения
различных групп связанных таблиц в различные базы данных. Горизонтальное
сегментирование выполняется путем размещения различных строк одной таблицы
в различные базы данных в соответствии с ключом сегментирования. Дополнительные
сведения о сегментировании см. в разделе Как выполнять сегментирование в базе данных
SQL Windows Azure (http://social.technet.microsoft.com/wiki/contents/articles/1926.how-to-shardwith-windows-azure-sql-database.aspx).
Вертикальное сегментирование в службах SSIS выполняется простой настройкой
получателей, указывающих на нужную базу данных. Горизонтальное сегментирование
в службах SSIS выполняется одним сквозным проходом по источнику данных
с использованием условного разбиения, направляющего данные в соответствующий
сегмент, а также настройкой диспетчера соединений для каждой целевой базы данных.
Для горизонтального сегментирования обычно используется два типа логики.


Единичное значение указывает сегмент, к которому относится строка.
В каждом сегменте хранится диапазон значений ключа.
Единичное значение может существовать в данных, получаемых службами SSIS, либо ключ
сегментирования может вычисляться внутри потока данных служб SSIS. Использование
диапазона значений обычно подразумевает, что значение берется из данных источника.
При сегментировании с использованием как диапазона значений, так и единичного значения
для направления строки в соответствующий получатель применяются выражения в условном
разбиении в потоке данных.
В качестве примера использования единичного значения для сегментирования можно
привести применение функции HASHVALUE в SQL Server или коде .NET с целью
указания GUID в качестве первичного ключа. Для такого перемещения необходимо
использовать команду SQL для доступа к данным в компоненте источника ADO.NET
или ODBC служб SSIS и ввести запрос. Это может быть, например, такой запрос:
SELECT CAST(ABS(HASHBYTES(N'SHA1', CAST(USERID AS BINARY(16))) % 25)AS SMALLINT) as
rshard, * FROM SourceTable
8
В этом примере данные будут распределяться между 25 сегментами, так как для этого
используется операция получения остатка от деления «%25». Переменная rshard в этом
запросе может принимать значения от 0 до 24. Для распределения данных в потоке
необходимо настроить условное разбиение, как показано на следующем рисунке.
Каждый из приведенных в этом примере 25 выходов передает данные отдельному получателю.
Каждый получатель использует отдельный диспетчер соединений, определяющий базу данных,
в которой размещен рассматриваемый сегмент. Например, выход условного разбиения RShard1
будет соединен с получателем RShard1, для которого настроено использование диспетчера
соединений RShard1. Диспетчер соединений указывает на базу данных RShard1, которая содержит
только записи со значением ключа сегментирования 0. Обратите внимание, что в данном случае не
нужно хранить ключ сегментирования, так как приложение может вычислить его перед обращением.
9
Преимущество сегментирования по единственному значению заключается в простоте импорта
или вычисления этого значения в приложении. Недостаток же состоит в том, что при изменении
числа сегментов (если используется остаток от деления) либо усложняется вычисление, либо
возникает необходимость перераспределения данных между сегментами. Не существует простого
способа разделения одного сегмента. Чтобы упростить разделение отдельных сегментов при
масштабировании, пользуйтесь сегментированием по диапазону. В качестве примера
сегментирования по диапазону можно привести реализацию федераций в базах данных
SQL Windows Azure.
Импорт в федерации
Федерации сегментируют данные путем определения границ диапазонов. Приложения работают
с федерациями путем соединения с корневой базой данных и выполнения команды USE FEDERATION
(http://msdn.microsoft.com/ru-ru/library/windowsazure/hh597457.aspx). Службы SSIS могут соединиться
с базой данных ROOT, но, прежде чем выполнять какие бы то ни было операции массовой вставки,
необходимо установить соединение с соответствующим членом федерации. Члены федерации —
это отдельные базы данных, которые создаются при определении границ федерации. Большинство
приложений не используют имена баз данных-членов федерации, так как для направления запроса
соответствующему члену федерации они следуют парадигме соединения с корневой базой данных
и выполнения команды USE FEDERATION. В отношении служб SSIS для соединения с федерацией и
работы с ней полезным может оказаться использование немного иного метода. При этом повторное
использование пакета и минимальный объем обновления при переходе из одной среды в другую
останутся неизменными.
Как правило, для импорта в федерацию баз данных SQL Windows Azure с помощью служб SSIS
необходимо выполнить следующие действия.
1. Определите параметр пакета, где будет храниться имя корневой базы данных федерации.
2. Определите параметры пакета для хранения следующих значений.
a. Сведения о соединении и имени входа, а также об источнике данных.
b. Используемое имя федерации.
ПРИМЕЧАНИЕ. Установите свойство Sensitive в значение True для параметров, содержащих
пароли или другие конфиденциальные сведения о соединении. Это позволит предотвратить
несанкционированный просмотр конфиденциальных сведений о соединении.
10
После этого параметры должны выглядеть следующим образом.
Задайте параметрам значения, необходимые для подключения к своему серверу,
или присвойте эти значения в процессе выполнения, чтобы обеспечить соединение
со своей системой.
3. Определите строчные переменные для следующих элементов.
a. Для каждого члена федерации, для которого можно выполнить импорт данных.
Пронумеруйте их последовательно для простоты последующего доступа. Например,
используйте такие соглашения об именах, как «FedMember0», «FedMember1»,
«FedMember2» и так далее до самого последнего используемого элемента.
ПРИМЕЧАНИЕ. Эти переменные необходимо будет заполнить значениями для баз
данных, содержащих схему целевой таблицы. Во время работы конструктору служб
SSIS потребуется соединение с базой данных для получения метаданных. Можно
заполнить все переменные одним и тем же значением — в процессе отправки данных
нужным членам федерации они будут изменены задачей «Скрипт».
b. Для минимальной границы диапазона каждой из федераций.
Эти переменные можно создать из соглашений об именах элементов, о которых
говорилось выше, например «FedMember0MinRange», «FedMember1MinRange»,
«FedMember2MinRange» и так далее до максимального элемента в
последовательности.
11
Если следовать вышеприведенному образцу, то при использовании федерации с 6 членами
получатся следующие переменные.
4. Создайте один диспетчер соединений с корневой базой данных федерации, один для базы
данных-источника и по одному для каждой базы данных-члена федерации. С помощью
выражений присвойте отдельные свойства, необходимые для входа и соединения
с отдельными членами федерации, либо с помощью выражения определите полную строку
подключения для соединения с этими членами федерации. Используя строчные переменные,
созданные на шаге 3, определите параметр «Initial Catalog» в строке подключения или имя
базы данных в свойствах соединения.
ПРИМЕЧАНИЕ. Дополнительные сведения о выражениях свойств и о том, как их задавать,
см. в разделе Использование выражений свойств в пакетах
(http://technet.microsoft.com/ru-ru/library/ms141214.aspx).
12
В следующем примере показано, как с помощью выражений задать свойства соединения
для корневой базы данных федерации с использованием переменных пакета,
определенных на шаге 2.
В следующем примере то же самое для базы данных-члена федерации.
ПРИМЕЧАНИЕ. Для свойства InitialCatalog вместо параметра пакета используется
переменная пакета, поскольку во время создания пакета определить имя базы данныхчлена федерации не представляется возможным. Использование переменной пакета
позволяет задать значение этого свойства динамически во время выполнения.
13
5. Хотя заполнение переменных можно выполнить несколькими разными способами,
в данном примере будет использоваться задача «Скрипт». Поэтому все необходимые
значения можно задать в одной задаче. Чтобы воспользоваться задачей «Скрипт»,
выполните следующие действия.
a. Перетащите задачу «Скрипт» в поток управления. Задайте ей имя и дважды
щелкните задачу, чтобы открыть редактор задачи «Скрипт».
b. В поле ReadOnlyVariables нажмите кнопку с многоточием и выберите параметры
пакета, заданные для сервера назначения: имя целевого корневого члена
федерации, имя федерации, идентификатор и пароль пользователя федерации.
После того как будет выбран параметр и нажата кнопка ОК в диалоговом окне
Выбор переменных, в поле ReadOnlyVariables появится следующая строка.
$Package::DestinationServerName,$Package::DestinationUserID,$Package::DestinationUserPwd
,$Package::FederationName,$Package::FederationRootName
c. В поле ReadWriteVariables нажмите кнопку с многоточием и выберите
пользовательские переменные для имен членов федерации и минимальных
значений диапазонов федерации. После того как будет выбрана переменная
и нажата кнопка ОК в диалоговом окне Выбор переменных, в поле
ReadWriteVariables появится следующая строка.
User::FedMember0,User::FedMember0MinRange,User::FedMember1,User::FedMember1MinRa
nge,User::FedMember2,User::FedMember2MinRange,User::FedMember3,User::FedMember3M
inRange,User::FedMember4,User::FedMember4MinRange,User::FedMember5,User::FedMembe
r5MinRange
d. Нажмите Изменить скрипт.
e. При открытии VstaProjects нажмите кнопку «+» вверху слева от слов «Пространства
имен», чтобы развернуть область кода. Добавьте в область следующую строку:
using System.Data.SqlClient;
f.
В этом примере с указанными переменными функция Main должна выглядеть
следующим образом.
public void Main()
{
// соединение с корнем федерации и начало построения сопоставления федерации:
// сначала зададим соединение и команду для получения информации от корневого
члена:
SqlConnection conn = new SqlConnection();
conn.ConnectionString =
String.Format("Data Source={0};Initial Catalog={1};User Id={2};Password={3};"
, Dts.Variables["$Package::DestinationServerName"].Value
, Dts.Variables["$Package::FederationRootName"].Value
, Dts.Variables["$Package::DestinationUserID"].Value
, Dts.Variables["$Package::DestinationUserPwd"
].GetSensitiveValue().ToString());
14
SqlCommand cmd = new SqlCommand();
cmd.Connection = conn;
cmd.CommandText = "select f.federation_id, m.member_id, d.distribution_name,"
+ " fd.range_low from sys.federations f\n"
+ "
join sys.federation_members m on f.federation_id = m.federation_id\n"
+ "
join sys.federation_distributions d on f.federation_id = d.federation_id\n"
+ "
join sys.federation_member_distributions fd"
+ " on f.federation_id = fd.federation_id\n"
+ "
and m.member_id = fd.member_id\n"
+ "
and d.distribution_name = fd.distribution_name\n"
+ "where f.name = @FedName\n"
+ "ORDER BY fd.range_low desc; ";
cmd.Parameters.AddWithValue("@FedName",
Dts.Variables["$Package::FederationName"].Value.ToString());
// потребуется второе соединение для подключения к каждому из членов федерации
// для получения имени базы данных:
SqlConnection conn1 = new SqlConnection();
conn1.ConnectionString = conn.ConnectionString;
SqlCommand fedNameCmd = new SqlCommand();
fedNameCmd.Connection = conn1;
// поскольку сама команда «USE FEDERATION» должна быть в пакете
// и процесс для получения номера федерации
// имя базы данных будет включать 2 пакета,
// пока нельзя ни подготовить инструкцию,
// ни создать отдельное выполнение. Поэтому отложим
установку CommandText в fedNameCmd
try
{
conn.Open();
}
catch (SqlException e)
{
Dts.Events.FireError(0,
"Задание сведений о соединении для федерации, соединение conn с корнем
федерации",
e.Message, String.Empty, 0);
Dts.TaskResult = (int)ScriptResults.Failure;
return;
}
try
{
conn1.Open();
}
catch (SqlException e)
{
Dts.Events.FireError(0,
"Задание сведений о соединении с федерацией, соединение conn1 с корнем
федерации",
15
e.Message, String.Empty, 0);
Dts.TaskResult = (int)ScriptResults.Failure;
return;
}
// если вы оказались в этой точке, то оба соединения открыты.
// можно безопасно выполнить команду
try
{
SqlDataReader reader =
cmd.ExecuteReader();
// переменные для хранения результатов запроса:
Int32 federation_id;
Int32 member_id;
String distribution_name;
Int32 range_low;
// выбор соответствующего типа данных.
// в нашем случае ключ диапазона имеет тип int
// переменная для отслеживания члена, с которым мы работаем
// ее значение используется для создания имени переменной пакета служб DTS
Int32 federationMember = 0;
while (reader.Read())
{
federation_id = reader.GetInt32(0);
member_id = reader.GetInt32(1);
distribution_name = reader.GetString(2);
range_low = reader.GetInt32(3);
/*
*
* Обратите внимание,
что в этом примере извлекаются federation_id и member_id.
* Они необходимы только в том случае, если нужно выполнять запросы
к представлениям каталога в
* члене федерации, чтобы проверить, на самом ли деле таблица
* находится в федерации.
* */
// сначала зададим rangeLow для этого члена федерации:
Dts.Variables[String.Format("User::FedMember{0}MinRange",
federationMember)].Value = range_low;
// затем зададим имя базы данных, чтобы поток данных
// мог соединиться с нужной базой данных. Обратите внимание, что при
создании
// текста команды используется range_low. В диапазонах, определенных в
// представлении каталога sys.federation_member_distributions,
значение range_low
// включается в диапазон, а range_high не включается, поэтому
используемое в
// range_low значение приведет нас к данному члену федерации.
16
// дополнительные сведения см.
// в http://msdn.microsoft.com/ru-ru/library/windowsazure/hh597453
try
{
String commandText = String.Format(
"USE FEDERATION {0} ({1} = {2}) WITH RESET;",
Dts.Variables["$Package::FederationName"].Value
, distribution_name, range_low);
fedNameCmd.CommandText = commandText;
fedNameCmd.ExecuteNonQuery();
// теперь fedNameCmd соединен с членом федерации.
// запросим имя:
fedNameCmd.CommandText = "SELECT db_name()";
// присвоим переменной служб SSIS имя базы данных:
Dts.Variables[String.Format("User::FedMember{0}",
federationMember++)].Value = (String)fedNameCmd.ExecuteScalar();
}
catch (SqlException e)
{
Dts.Events.FireError(0,
"Задание сведений о соединении для федерации," +
"Получение имен членов федерации",
e.Message, String.Empty, 0);
Dts.TaskResult = (int)ScriptResults.Failure;
return;
}
}
}
catch (SqlException e)
{
Dts.Events.FireError(0,
"Задание сведений о соединении для федерации, обработка сведений о корне
федерации",
e.Message, String.Empty, 0);
Dts.TaskResult = (int)ScriptResults.Failure;
return;
}
Dts.TaskResult = (int)ScriptResults.Success;
}
17
6.
Создайте компоненты и свою задачу потока данных, включая логику повторов и перезапуска
для потока данных. Обратите внимание, что если для компонента предусматривается логика
перезапуска, то для задачи «Скрипт» необходимо задать контрольные точки и установить
свойство FailPackageOnFailure в значение true, чтобы значения переменных сохранялись
для перезапуска любого пакета.
Дополнительные сведения об использовании контрольных точек для сохранения состояния
потока управления при перезапуске пакета см. в разделе Перезапуск пакетов с
использованием контрольных точек (http://msdn.microsoft.com/ru-ru/library/ms140226.aspx).
Дополнительные сведения о проектировании конвейера для перезапуска см. в разделе
Проектирование перезапуска без потери хода выполнения конвейера документа
«Руководство по эксплуатации и настройке служб SQL Server 2012 SSIS». Это руководство
находится в библиотеке MSDN в узле Технические документы Майкрософт
(http://msdn.microsoft.com/ru-ru/library/hh403491.aspx) для SQL Server 2012.
7. Соедините задачи «Скрипт» и «Поток данных» с управлением очередностью, чтобы задача
«Поток данных» запускалась только после выполнения задачи «Скрипт».
8. В потоке данных следует использовать условное разбиение для перенаправления строк
к соответствующему члену федерации. Необходимо создать один выход для каждого
члена федерации.
Поскольку запрос для получения членов федерации возвратил строки, отсортированные
по range_low в убывающем порядке, а члены федерации в службах SSIS пронумерованы от
0, член FedMember0 будет содержать наибольшие значения ключа федерации. Поскольку
range_low включается в диапазон, выражение должно включать условие «<=».
Строки отправляются на первый вывод, где их состояние возвращает значение true.
С учетом того, что обработка начинается с диапазона наибольших значений и используется
условие «>=», нет необходимости проверять range_high на соответствие обоим условиям.
Прежде чем строки для определенного диапазона достигнут его конца, все строки со
значением больше текущего диапазона отправляются на соответствующие выводы.
В следующем примере показана конфигурация условного разбиения.
18
9. Соедините выводы преобразования «Условное разбиение» с целевыми компонентами
членов федерации.
При разработке пакета переменным FedMember0, FedMember1 и другим переменным
FedMember потребуется присвоить значение по умолчанию. Это значение должно
представлять имя базы данных, содержащей таблицу со схемой, совпадающей со
схемой получателя. Это позволит службам SSIS получить метаданные получателя
во время проектирования. При выполнении пакета задача «Скрипт» присвоит этим
переменным имена тех баз данных-членов федерации, в которые будут вставляться
данные. Поток данных из образца пакета будет выглядеть следующим образом.
19
СОВЕТ. Из-за того что конвейер потока данных является статическим, в нем нельзя динамически
создавать дополнительные получатели для разделения сегментов. Чтобы обеспечить возможность
динамического расширения федераций, необходимо программное построение пакетов. Можно
воспользоваться методом, показанным в задаче «Скрипт», для программного сопоставления
данных с членами федерации по мере построения пакетов.
МЕТОД. Можно создать статический пакет, предусмотрев возможность расширения за счет
создания большего числа получателей в потоке данных, чем количество членов федерации.
При использовании показанного метода динамического сопоставления данных с существующими
членами федерации данные отправляются только последним. В случае разбиения для создания
нового члена федерации при следующем выполнении пакета появится новый получатель,
а задача «Скрипт» с помощью метаданных федерации направит данные новому члену.
Процессы служб SSIS в Windows Azure
Благодаря новым постоянным виртуальным машинам WA в нем теперь поддерживается SQL Server.
Службы SSIS не поддерживаются в WA. Это позволяет размещать службы SSIS в тех же центрах
обработки данных, что и источники и получатели данных Azure. Таким образом, обеспечивается более
эффективное перемещение данных из Azure в Azure, чем это было возможно ранее. При этом также
20
имеется возможность масштабировать перемещение данных служб SSIS в Azure путем запуска пакетов
на нескольких постоянных виртуальных машинах. Кроме того, данные можно переместить в два этапа,
на первом из которых данные сжимаются и размещаются в промежуточном хранилище Azure, а на
втором сжатые данные забираются из хранилища и извлекаются в получателе, размещенном в WA.
Сжатие позволяет передавать гораздо большие объемы данных по каналам связи с ограниченной
пропускной способностью и высокими задержками, которые, возможно, имеются между локальной
инфраструктурой и центром обработки данных Azure.
Жизненный цикл виртуальных машин SSIS
Постоянные виртуальные машины WA с установленными компонентами служб SSIS способны
выполнять пакеты SSIS. Для выполнения пакетов SSIS SQL Server не обязательно должен работать
на том же компьютере. Варианты установки служб SSIS см. в разделе Установка служб Integration
Services электронной документации по SQL Server
(http://msdn.microsoft.com/ru-ru/library/ms143731.aspx).
Службы SSIS можно установить на виртуальную машину WA, либо они могут работать в Hyper-V
и передаваться в Azure. В качестве альтернативы можно создать новую виртуальную машину
на платформе WA, выбрав из коллекции образ Microsoft SQL Server 2012. Поскольку в этих
виртуальных машинах установлены служба SSIS, среда выполнения и SDK, пакеты служб SSIS на
них можно выполнять сразу после их перевода в режим «в сети». База данных и каталог SSISDB
не установлены в образах из коллекции Azure, поэтому для использования возможностей каталога
необходимо настроить их вместе с другими приложениями. Инструкции по созданию каталога см.
в разделе Создание каталога служб SSIS (http://msdn.microsoft.com/ru-ru/library/gg471509.aspx).
Дополнительные сведения о базе данных и каталоге SSISDB см. в разделе Каталог служб SSIS
(http://msdn.microsoft.com/ru-ru/library/hh479588.aspx).
Как при создании из коллекции, так и при загрузке подготовленного образа Hyper-V вы будете
оплачивать хранение любых сохраненных вами частных образов. Плата за вычислительные
мощности взимается сразу после создания виртуальной машины из образа. По завершении
работы виртуальной машины в Azure выдается предупреждение о том, что оплата за нее
будет взиматься до тех пор, пока машина не будет удалена.
Можно, например, создать выделенные виртуальные машины для перемещения данных и
запланировать запуск или запускать на них пакеты служб SSIS. Такой сценарий хорошо подходит
для устойчивого перемещения данных и ETL. Но в тех случаях, когда нужно быстро перемещать
большие объемы данных, либо при резком росте объемов перемещаемых данных может
потребоваться решение, развертывающее новые виртуальные машины для обработки
пиковой нагрузки, а затем удаляющее их по завершении перемещения данных.
Когда требуется развертывание виртуальных машин служб SSIS для обработки пиковой нагрузки,
необходимо установить все компоненты, приложения и расписания, выполнить всю настройку,
а затем сохранить образ подготовленной виртуальной машины в своей личной коллекции.
21
Расположение процессов служб SSIS
Все общие рекомендации относительно выполнения пакетов служб SSIS на компьютерах
с доступными ресурсами будут действовать и при выполнении этих пакетов в WA. Для
выполнения пакетов служб SSIS на том же компьютере или виртуальной машине, на которой
работает производственный SQL Server, может потребоваться больше памяти и ресурсов ЦП
для SQL Server, поэтому этот фактор может оказать влияние на производительность. При
проектировании решения для перемещения данных, предусматривающего передачу данных
по каналам связи с ограниченной припускной способностью или сетям с высоким уровнем
задержек, могут потребоваться дополнительные меры.
Перемещение данных из Azure в Azure
При создании решений для переноса данных, где как источник данных, так и получатель
находятся в WA (в SQL Server на виртуальной машине, в базе данных SQL Windows Azure либо
где-нибудь еще), наиболее эффективным подходом будет размещение процесса служб SSIS на
виртуальной машине в WA. Кроме того, нужно учитывать размещение источника и получателя
данных внутри Azure. Существуют следующие варианты.


22
Когда и источник и получатель находятся в одном и том же центре обработки данных,
размещайте виртуальную машину с процессом служб SSIS в том же центре.
Если же источник и получатель находятся в разных центрах обработки данных, то в
зависимости от объемов данных и требований к времени обработки можно выбрать один
из двух вариантов подхода.
o Для небольших объемов несжатых данных может оказаться более эффективным
запускать процесс служб SSIS в том же центре обработки данных, где находится
источник или получатель данных, не создавая промежуточные файлы.
o Если файлы данных сжаты, размещайте процесс служб SSIS в центре обработки
данных, в котором находится получатель. Сначала скопируйте или переместите
сжатые файлы в центр обработки данных, где расположены процесс служб SSIS
и получатель, а затем обработайте данные в этом центре обработки данных.
o Если источник данных не сжат, а получателем является сжатый файл, запускайте
процесс служб SSIS в центре обработки данных, в котором находится источник,
а затем отправляйте сжатый файл получателю.
o При обработке больших объемов несжатых данных спроектируйте для
перемещения данных такое решение, которое будет извлекать сжатые файлы,
перемещать их в центр обработки данных, где находится получатель, а затем
распаковывать данные и импортировать их в получатель с помощью отдельного
процесса служб SSIS, выполняемого в том же центре.
Гибридное перемещение данных
Гибридным называется перемещение данных между локальным и облачным хранилищами данных.
При таком перемещении всегда используется сеть с ограниченной пропускной способностью и
высоким уровнем задержек. В данном случае реализуются те же сценарии, что и при перемещении
данных между центрами обработки данных Azure. Описание этих сценариев см. в разделе
Перемещение данных из Azure в Azure этой статьи.




Для небольших объемов несжатых данных может оказаться более эффективным запускать
процесс служб SSIS в том же центре обработки данных, где находится источник или
получатель данных, не создавая промежуточные файлы.
Если файлы данных сжаты, размещайте процесс служб SSIS в центре обработки данных,
в котором находится получатель. Сначала скопируйте или переместите сжатые файлы
в центр обработки данных, где расположены процесс служб SSIS и получатель, а затем
обработайте данные в этом центре обработки данных.
Если источник данных не сжат, а получателем является сжатый файл, запускайте процесс
служб SSIS в центре обработки данных, в котором находится источник, а затем
отправляйте сжатый файл получателю.
При обработке больших объемов несжатых данных спроектируйте для перемещения данных
такое решение, которое будет извлекать сжатые файлы, перемещать их в центр обработки
данных, где находится получатель, а затем распаковывать данные и импортировать их в
получатель с помощью отдельного процесса служб SSIS, выполняемого в том же центре.
Работа в случае ограниченной пропускной способности сети
Если между источником и получателем находится сеть с низкой пропускной способностью и высоким
уровнем задержек, то при перемещении данных любого объема будет эффективнее сжать данные
перед отправкой их по сети и распаковать в центре обработки данных получателя. Этот сценарий
напоминает песочные часы, где посередине имеется узкое место (в виде сети). Если бы можно
было сжимать песок перед прохождением его через узкое горлышко и распаковывать после, то песок
мог бы проходить в нижнюю часть колбы гораздо быстрее. При гибридном перемещении данных
используется двухэтапный подход, который позволяет обеспечить в несколько раз более высокую
скорость передачи, чем при отправке несжатых данных напрямую получателю.
Сжатие данных
Сжатие данных перед их отправкой через сеть с ограниченной пропускной способностью
экономит емкость полосы пропускания и помогает снизить нагрузку на сеть. Перемещение
данных из Azure также позволяет минимизировать расходы на выгрузку. При использовании
облачного хранилища для промежуточного хранения это поможет свести к минимуму затрат
на хранение и транзакции.
Текстовые данные или XML-данные сжимаются очень хорошо. JPEG-файлы, хранящиеся в таблицах
SQL, сжатию практически не поддаются. Степень сжатия зависит от типа сжимаемых данных.
23
Для сжатия данных их можно собрать в файл данных (например, необработанный или
неструктурированный файл служб SSIS) либо воспользоваться пользовательскими компонентами,
которые извлекают данные напрямую в сжатый файл, выполняя извлечение и сжатие одновременно.
Компоненты для сжатия файлов можно загрузить на сайте CodePlex
(http://compfilecomponents.codeplex.com/).
Двухэтапная передача
При двухэтапной передаче данные сначала извлекаются в файл. Затем файл перемещается
из расположения источника в расположение получателя. Отдельный процесс в расположении
получателя забирает файл и импортирует его получателю. При гибридном перемещении
данных использование двухэтапной передачи, когда файл передается из центра обработки
данных источника в центр обработки данных получателя, может оказаться значительно
более эффективным, чем передача с помощью единственного процесса SSIS.
На платформе WA для извлечения данных из источника или их загрузки получателю службы SSIS
могут выполняться на постоянных виртуальных машинах. Для выполнения такой обработки
данных службы SSIS могут работать и на локальных компьютерах.
Пример гибридного масштабирования, приведенный в разделе Масштабирование и гибридное
перемещение данных данной статьи, представляет собой пример двухэтапного перемещения.
Применение принципов масштабирования в WA
Масштабирование выполнения приложений включает перенос частей решения на отдельные
серверы для обеспечения параллельного выполнения. Службы SSIS могут использоваться
в масштабируемых решениях перемещения данных путем разделения функций извлечения
и загрузки на две параллельно выполняемые службы. Такие источники данных, как SQL Server,
могут одновременно готовить данные для нескольких процессов извлечения, а такие получатели,
как SQL Server или базы данных SQL Windows Azure, позволяют обслуживать несколько процессов
импорта как для одной, так и для нескольких таблиц. Когда источники или получатели
сегментированы или представляют собой федерации, то для извлечения или импорта данных
в различные сегменты и члены федерации также можно использовать несколько процессов.
Несколько постоянных виртуальных машин или серверов извлечения служб SSIS
Масштабирование перемещения данных производится за счет параллельного выполнения на
разных компьютерах нескольких процессов извлечения, сегментирования и импорта данных.
Выполнение процессов на нескольких компьютерах позволит воспользоваться вычислительными
мощностями нескольких компьютеров меньшего размера. Асинхронная обработка позволяет
более эффективно использовать такие ограниченные ресурсы, как пропускная способность сети,
так как не все процессы будут пытаться передавать данные одновременно.
При проектировании масштабируемого приложения необходимо обеспечить возможность
распределения работы. Это можно сделать различными способами. Хранимые процедуры в базе
данных SSISDB позволяют выполнять пакеты на удаленных компьютерах с одной центральной точки.
Очереди позволяют приложениям получать единицы работы по мере готовности к их обработке.
24
Очереди
В отличие от выполнения каталога из центральной точки, очереди могут быть использованы для
выполнения процессов по собственному расписанию — получать записи только в момент
готовности к их обработке и не простаивать без дела, когда требуется выполнить другую работу.
Очереди распределяют единицы работы среди процессов, работающих асинхронно. Очереди WA
являются одним из механизмов организации очередей, которые могут быть использованы для
распространения такой работы.
Удаление сообщения из очереди WA производится в два этапа.
1. Сообщение выталкивается из очереди.
2. Сообщение удаляется.
Объект Timespan передается в очередь при выталкивании сообщения. Объект Timespan указывает
промежуток времени, в течение которого запись не будет видна всем остальным процессам,
опрашивающим очередь. Если процесс завершил обработку до истечения этого срока, то он
может удалить сообщение и оно не будет возвращено в очередь. Если же срок истек, а сообщение
не удалено, то оно возвращается в очередь, а его счетчик вывода увеличивается на единицу.
Чтобы обеспечить эффективность использования очередей, выполните следующие действия.



Не делайте слишком большими единицы работы, которые процесс будет выполнять
с сообщением. Это позволит вам выполнять работу в разумные сроки. В этом случае
единица работы определяется извлечением фрагмента данных.
Задавайте срок выполнения так, чтобы он был достаточным для осуществления работы,
но не слишком растянутым, чтобы по истечении этого срока работа могла быть передана
другому процессу.
Проверяйте счетчик вывода для сообщения, особенно если во время обработки возникает
исключение. Возможно, при обработке возникает ошибка, по причине которой единица
работы не может быть выполнена процессом. Если число вывода превысило разумное
число попыток, считайте эту запись дефектной и обрабатывайте ее соответствующим
образом. Это может означать вставку в какое-то место записи о том, что эту единицу
работы выполнить не удалось, поэтому эти данные требуют ручной обработки.
Два этапа, которые необходимы для удаления сообщения из очереди WA, требуют от приложения
реализации логики повтора. Если компьютеру не удалось выполнить единицу работы, то другой
процесс извлечет это сообщение по истечении времени ожидания и обработает его. Это
позволяет повысить надежность масштабируемого приложения.
Хранилище больших двоичных объектов
Хранилище больших двоичных объектов предназначено для хранения промежуточных файлов
данных или пакетов служб SSIS. Оно позволяет нескольким процессам, работающим на разных
компьютерах, при необходимости обращаться к этим файлам, даже если компьютеры не имеют
общих прав доступа с пользователями на других компьютерах. Для доступа к файлам процессу
необходим только URL-адрес и ключ доступа к учетной записи хранилища.
25
Проектирование логики повтора без ручного вмешательства
Компоненты источника и получателя служб SSIS не содержат логики повторов. Но при этом
разработчик пакета служб SSIS не беззащитен перед этой ситуацией. Разработка пакетов,
обеспечивающих перезапуск пакетов служб SSIS с точки сбоя в потоке данных, также дает
возможность автоматического повтора после сбоя с некоторыми оговорками. В тех случаях,
когда, например, производится перемещение данных в базы данных SQL Windows Azure или
обратно, может потребоваться автоматический повтор для обработки временных сбоев,
в частности при нехватке ресурсов или из-за регулирования.
В данной главе используются концепции, проиллюстрированные в предыдущем разделе,
на основе которых показан пример простой логики повтора для пакета. Кроме того, здесь
демонстрируется использование фрагментирования для обеспечения логики повторов
для каждого фрагмента данных, чтобы придать пакету надежность в ситуациях, когда
возникновение временных сбоев наиболее вероятно.
Включение в пакет логики повторов
При наличии одного пакета встроить логику повторов можно следующим образом.
1. Определение максимального числа повторов пакета перед выдачей ошибки. Для этого
примера определим, что компоненту разрешено выполнить не более 5 попыток, прежде
чем выдать ошибку. Создайте переменную пакета maxNumOfRetries с типом int и присвойте
ей значение 5. Она будет использоваться в выражениях в пакете. Чтобы изменить число
повторных попыток, можно будет присвоить этой переменной значение по умолчанию.
2. Настройте переменную для хранения успешного состояния. Создайте новую переменную
в пакете служб SSIS. Присвойте новой переменной имя attemptNumber.
3. Если поток данных выполнен неуспешно, то задайте повтор в цикле FOR до максимального
числа попыток.
26
4. Поместите задачу потока данных внутри цикла FOR.
5. Установите свойство MaximumErrorCount в цикле FOR в максимальное число повторов
потока данных. Это делается с помощью выражения, где используется переменная
maxNumOfRetries, заданная на шаге 1.
27
6. С помощью задачи «Выполнение SQL», показанной в предыдущем разделе, найдите
минимальное значение ключа в источнике и максимальное значение ключа в получателе.
7. Разместите задачи «Выполнение SQL» в цикле FOR.
Для простого повтора это можно сделать в цикле FOR. В более сложной логике повторов
эти задачи могут размещаться в потоке управления в других местах перед циклом FOR.
8. Если задачи «Выполнение SQL» использовались на шаге 6, то соедините ограничение
успеха из задачи «Выполнение SQL» с задачей «Поток данных».
9. Соедините управление очередностью «Успех» задачи «Поток данных» с задачей «Скрипт»,
которая будет присваивать переменной успешного состояния значение true, чтобы
завершить цикл FOR. Ниже приведен пример конфигурации задачи «Скрипт».
28
10. Соедините управление очередностью «Сбой» задачи «Поток данных» с другой задачей
«Скрипт», чтобы выполнить следующее.
a. Подождать 10 секунд, если задача работает в среде базы данных SQL Windows
Azure, где возможно регулирование ресурсов.
b. Присвойте переменной успеха значение false.
29
11. Для каждой задачи в цикле FOR установите свойство FailPackageOnFailure в значение false.
В такой конфигурации ошибка будет выдана только в том случае, если цикл FOR завершается
после заданного числа попыток повтора. Таким образом, пакет завершится сбоем.
12. Настройте задачи «Выполнение SQL» после сбоя для новой проверки минимальных
значений ключа в источнике, а затем проверьте максимальные значения ключа
в получателе, чтобы возобновить процесс без повторного выполнения работы. Задайте
настройки, описанные в разделе Проектирование для перезапуска без потери хода
выполнения конвейера документа «Руководство по эксплуатации и настройке служб
SQL Server 2012 SSIS». Это руководство находится в библиотеке MSDN в узле Технические
документы Майкрософт (http://msdn.microsoft.com/ru-ru/library/hh403491.aspx) для
SQL Server 2012.
30
Каждый раз, когда в потоке данных возникает ошибка, процесс, если количество попыток не
исчерпано, возвращается к точке нахождения максимального значения ключа в получателе и
обработка продолжается с точки, зафиксированной в получателе. Если в потоке данных имеется
несколько получателей, то начните с наименьшего значения ключа, поступившего в получатель,
и с помощью условного разбиения обрабатывайте строки, которые вызовут нарушение
первичного ключа.
Включение логики повторов в пользовательские компоненты
Логика повторов наиболее эффективна при ее размещении в источнике или получателе. Можно
разработать пользовательские компоненты источника или получателя для служб SSIS и включить
в них такую обработку временных ошибок, которая приведена в статье Платформа обработки
временных сбоев для SQL Azure, хранилища Windows Azure, Service Bus и Cache
(http://windowsazurecat.com/2011/02/transient-fault-handling-framework/).
Повторы перемещения данных при программном построении или
выполнении
Выше приведен пример помещения логики повторов в пакет служб SSIS. При программном
выполнении пакетов также имеется возможность добавления логики повторов. Достигается
это за счет проверки состояния после выполнения пакета и его повторного выполнения в случае
неуспешного завершения. Это решение показано в разделе Масштабирование и гибридное
перемещение данных ниже в этой статье. Пример такого решения приведен в приложении С.
Гибридное перемещение данных при работе SQL Server на
виртуальных машинах WA
Еще одна возможность перемещения данных между WA и локальным SQL Server заключается
в обмене данными между базами данных, находящимися на постоянных виртуальных машинах
WA. Многие из принципов перемещения данных из баз данных SQL Windows Azure и обратно
остаются неизменными, но есть и немаловажные различия.
Ниже приведены некоторые соображения.

31
Сжимайте данные перед их передачей через глобальные сети (WAN). При гибридном
перемещении данных сети с низкой пропускной способностью по-прежнему являются
потенциально узким местом. Независимо от метода, используемого для передачи данных
по этой сети, большие объемы данных эффективнее сжимать перед передачей по сетям
с ограниченной пропускной способностью, а затем распаковывать в месте назначения.
Сервер FTP можно настроить на передачу данных непосредственно на серверы служб SSIS,
кроме того, данные можно перенести в хранилище больших двоичных объектов WA,
которое будет использоваться в качестве промежуточного хранилища.




Используйте отдельный сервер служб SSIS. Чтобы добиться максимальной вычислительной
мощности и минимального воздействия на производительность SQL Server, работающего
на виртуальной машине WA, пользуйтесь отдельным сервером служб SSIS, а не запускайте
пакеты непосредственно на виртуальной машине с SQL Server.
Настройка пакетов. Настройка пакетов по-прежнему крайне важна для производительности.
Если возможно, проектируйте обработку в режиме минимального протоколирования.
Рекомендации, приведенные в документе Руководство по производительности загрузки
данных (http://msdn.microsoft.com/ru-ru/library/dd425070(v=sql.100).aspx), актуальны и
для загрузки данных в SQL Server, работающий на постоянной виртуальной машине WA.
Убедитесь, что SQL Server настроен для запуска на постоянной виртуальной машине WA.
Учитывайте следующие рекомендации.
o Перенесите базу данных tempdb из локального хранилища на диски данных.
o Поместите базы данных на диски данных WA.
o Разместите журнал транзакций на другом диске WA отдельно от файлов данных.
o Используйте несколько дисков данных WA, размещая файлы данных на
отдельных дисках.
o Настройте память SQL Server так, чтобы предотвратить число внешних конфликтов
между SQL Server и другими процессами, работающими на виртуальной машине.
Если выбран запуск пакетов служб SSIS на той же виртуальной машине, что и
экземпляр SQL Server, не забывайте о том, что пакет SSIS выполняет отдельный
процесс, который может конкурировать с экземпляром SQL Server за память,
вычислительные и дисковые ресурсы.
Дополнительные сведения о рекомендациях по настройке SQL Server для работы на виртуальной
машине WA см. в разделе Запуск SQL Server на виртуальных машинах Windows Azure —
рекомендации по производительности для предварительной версии
(http://sqlcat.com/sqlcat/b/technicalnotes/archive/2012/06/08/running-sql-server-in-windows-azurevirtual-machine-performance-guidelines-for-preview.aspx).
Стратегии
Стандартный поток данных в локальной конфигурации выглядит следующим образом.



32
Источники данных содержат информацию, которую необходимо загрузить в базу данных.
Роль источника данных может выполнять таблица в другой базе данных или
неструктурированный файл.
Службы SSIS выполняют пакет, который выбирает данные из источника и формирует из них
реляционную базу данных с дополнительными преобразованиями (или без них).
Чтобы добиться удовлетворительной скорости перемещения данных, можно подготовить
соглашение об уровне обслуживания пакета служб SSIS с использованием нескольких потоков
параллельной обработки данных в соответствии с рекомендациями по загрузке данных.
Первоначальная локальная конфигурация:
SSIS streams
SSIS streams
SSIS
SSIS Box
Box
SQL
SQL Data
Data Base
Base
SQL
SQL Data
Data Source
Source
Гибридное решение предполагает, что данные будут храниться не только на сервере локальной
инфраструктуры, но некоторые из них будут также доступны в WA. В этом случае первоначально
использованный поток ETL может потребовать дополнительного изменения для извлечения
данных в облачные базы данных.
Гибридная конфигурация:
SSIS streams
SQL
SQL Data
Data Base
Base
SSIS streams
SQL
SQL Data
Data Base
Base
SSIS
SSIS Box
Box
SQL
SQL Data
Data Source
Source
SQL
SQL Data
Data Base
Base
SQL
SQL Data
Data Base
Base
Рассмотрим подробнее возможные стратегии загрузки данных.
33
Загрузка данных напрямую на виртуальную машину WA
Простейший сценарий отправки данных в среду WA заключается в использовании баз данных
Azure в качестве получателя.
В этом случае, если мы рассмотрим пример пакета служб SSIS, он не будет особо отличаться
от пакета, разработанного для локального ETL.
Пример пакета служб SSIS 1
Использование передачи файлов для перемещения данных между локальным
сервером и виртуальной машиной WA
В качестве альтернативной стратегии загрузки данных с локального сервера на виртуальную машинуполучатель в WA можно использовать передачу файлов. В этом случае процесс загрузки данных будет
включать загрузку данных в неструктурированный файл, его передачу по сети в общую папку
виртуальной машины и последующую массовую вставку данных в таблицу-получатель.
34
Пример пакета служб SSIS 2
Но этот сценарий требует настройки общих папок, где будут создаваться и храниться временные
файлы данных. Чтобы сделать файлы доступными для виртуальной машины WA, можно
воспользоваться общей папкой. Это потребует присоединения виртуальной машины к домену,
где находится локальный сервер.
Одно из преимуществ использования такой конфигурации заключается в том, что службы SSIS будут
использовать компоненты неструктурированного файла и поэтому будут относительно просты.
Но в некоторых случаях присоединение виртуальной машины WA к домену может оказаться
проблематичным, главным образом из-за внутренних ограничений организации. В этом случае
некоторую гибкость может обеспечить использование службы FTP. Эту службу можно использовать
для передачи файлов через общедоступную сеть между локальным компьютером и виртуальной
машиной WA. Для этого потребуется настроить FTP-сервер на виртуальной машине WA.
Настройка FTP-сервера на виртуальной машине WA
1. Установите службы IIS с FTP-сервером через диспетчер серверов в операционной системе
виртуальной машины (Windows Server 2008/R2/2012).
2. Создайте стандартный FTP-сайт с предпочтительной конфигурацией и настройками
безопасности. Дополнительные сведения см. в разделе Настройка FTP в IIS 7
(http://technet.microsoft.com/ru-ru/library/cc771012(v=ws.10).aspx).
35
3. В консоли управления службами IIS на уровне сервера откройте параметры поддержки
брандмауэра FTP и определите единственный диапазон портов для канала данных
(например, 14147). Он будет использован FTP-сервером для установления обратного
канала данных к клиенту. Диапазон должен быть фиксированным, так как для обеспечения
работы потребуется определить конечную точку виртуальной машины. Это также означает,
что FTP-сервер будет доступен только через пассивное соединение. Активные соединения,
необходимые для обмена данными через динамический диапазон портов, невозможно
настроить при использовании конечных точек виртуальной машины WA.
36
4. В текстовом поле Внешний IP-адрес брандмауэра введите внешний IP-адрес виртуальной
машины. В противном случае некоторые FTP-клиенты, например задача «FTP» служб SSIS,
получат ошибку при попытке сервера обратиться по внутреннему IP-адресу клиентской
виртуальной машины.
37
5. При настройке службы FTP нужно создать соответствующие правила брандмауэра
(самое важное — порт 21 и пассивный диапазон портов).
6. Создайте две конечные точки для данных и управления FTP (порт 21 для одной конечной
точки, которая определена в предыдущем шаге) с помощью портала WA. Перезапустите
виртуальную машину.
38
7. Локально на сервере служб SSIS откройте порт для подключения к данным, который был
указан как порт соединения для передачи данных по FTP (например, 14147).
8. С помощью FTP-клиента, поддерживающего пассивные соединения (например, Smart FTP
или FTPZilla), проверьте, возможно ли установить соединение с FTP-сервером на виртуальной
машине WA. Убедитесь, что папка FTP доступна. Для установления подключения к данным
указывайте полное имя FTP-сервера на виртуальной машине вместе с номером порта
(например, 14147).
39
9. Создайте задачу «FTP», соответствующим образом настройте соединение, удостоверьтесь,
что указано полное имя сервера и установлен флажок Использовать пассивный режим.
40
Теперь можно создать пакеты служб SSIS и передавать или загружать данные непосредственно
с виртуальной машины SQL Azure, как показано в следующем примере.
Особый случай: использование сжатия для сокращения издержек передачи файлов
данных по сети.
В тех случаях, когда пропускная способность сети сильно ограниченна, сжатие файлов данных
перед их передачей по сети может оказаться очень полезным.
41
Компоненты служб SSIS не имеют готовых средств сжатия. Но это можно обойти за счет разработки
пользовательских компонентов служб SSIS, как описано в разделе Сжатие данных этой статьи. Еще
один вариант может оказаться предпочтительным для некоторых администраторов баз данных —
реализация для сжатия и распаковки данных процедуры CLR, которая вызывается из пакета
служб SSIS и выполняется как локально, так и на виртуальной машине WA.
Ниже приведен пример процедуры CLR, предназначенной для сжатия и распаковки данных.
using
using
using
using
using
using
using
System.Collections.Generic;
System.Linq;
System.Text;
Microsoft.SqlServer.Server;
System.Data.SqlTypes;
System.IO;
System.IO.Compression;
namespace zip
{
public class Program
{
public static void comp(string option, string inputpath, string outputpath)
{
// возможность сжатия
if (option == "comp")
{
SqlContext.Pipe.Send("Compression Selected");
FileInfo fileToCompress = new FileInfo(inputpath);
FileInfo compressedFile = new FileInfo(outputpath);
Compress(fileToCompress, compressedFile);
return;
}
42
// возможность распаковки
else if (option == "decomp")
{
SqlContext.Pipe.Send("Decompression Selected\n");
FileInfo fileToDecompress = new FileInfo(inputpath);
FileInfo decompressedFile = new FileInfo(outputpath);
Decompress(fileToDecompress, decompressedFile);
return;
}
// справка... потому что помощь нужна всем! :-)
else if (option == "help")
{
help();
return;
}
}
// код сжатия
public static void Compress(FileInfo fileToCompress, FileInfo compressedFile)
{
using (FileStream originalFileStream = fileToCompress.OpenRead())
{
if ((File.GetAttributes(fileToCompress.FullName) & FileAttributes.Hidden)
!= FileAttributes.Hidden & fileToCompress.Extension != ".gz")
{
using (FileStream compressedFileStream = File.Create(compressedFile +
".gz"))
{
using (GZipStream compressionStream = new
GZipStream(compressedFileStream, CompressionMode.Compress))
{
originalFileStream.CopyTo(compressionStream);
SqlContext.Pipe.Send("Сжатие завершено\n");
}
}
}
}
}
// код справки
public static void help()
{
SqlContext.Pipe.Send("Справка по процедуре сжатия файлов CLR\n");
SqlContext.Pipe.Send("Процедура ожидает 3 параметра (все в нижнем
регистре):\n");
SqlContext.Pipe.Send("Параметр 1: режим, 'comp' - сжатие, 'decomp' распаковка\n");
SqlContext.Pipe.Send("Параметр 2: входной путь<FileName>.<Extention>, то есть
входной файл для сжатия или распаковки\n");
SqlContext.Pipe.Send("Параметр 3: входной путь<FileName>.<Extention>, то есть
файл для сжатия или распаковки, в случае сжатия расширение файла не используется\n");
43
SqlContext.Pipe.Send("Параметры безопасности, параметр БД, TrustWorthy = ON,
безопасность сборки = EXTERNAL_ACCESS\n");
}
// код распаковки
public static void Decompress(FileInfo fileToDecompress, FileInfo
decompressedFile)
{
using (FileStream originalFileStream = fileToDecompress.OpenRead())
{
string currentFileName = fileToDecompress.FullName;
string newFileName = currentFileName.Remove(currentFileName.Length fileToDecompress.Extension.Length);
using (FileStream decompressedFileStream = File.Create(decompressedFile +
""))
{
using (GZipStream decompressionStream = new
GZipStream(originalFileStream, CompressionMode.Decompress))
{
decompressionStream.CopyTo(decompressedFileStream);
SqlContext.Pipe.Send("Распаковка завершена\n");
}
}
}
}
}
}
Рекомендации по настройке локального и гибридного
перемещения данных
На производительность перемещения данных могут повлиять следующие факторы.




Сеть
Поток данных пакета служб SSIS
Драйверы соединения
База данных SQL Server (как получатель)
Сеть
Как правило, значительный прирост производительности достигается настройкой крупных
фреймов в конфигурации сети, а также установкой размера пакетов в строках подключения
SQL Server или служб SSIS.
Но эти настройки не оказывают заметного влияния на производительность при гибридном
перемещении данных, очевидно, из-за того, что данные проходят через несколько сетей.
Дополнительные сведения см. в разделе Настройка параметров сети документа «Руководство по
эксплуатации и настройке служб SQL Server 2012». Это руководство находится в библиотеке MSDN
в узле Технические документы Майкрософт
(http://msdn.microsoft.com/ru-ru/library/hh403491.aspx) для SQL Server 2012.
44
Поток данных пакета служб SSIS
Когда сервер находится на локальной площадке, имеется три параметра конфигурации потока
данных служб SSIS, относящихся к гибридному перемещению данных.



DefaultBufferMaxRows
DefaultBufferSize
EngineThreads
Эти параметры позволяют лучше использовать ресурсы сервера служб SSIS и эффективно
считывать данные из источника данных.
Дополнительные сведения см. в разделе Параметры пакета служб SSIS документа «Руководство
по эксплуатации и настройке служб SQL Server 2012». Это руководство находится в библиотеке
MSDN в узле Технические документы Майкрософт (http://msdn.microsoft.com/ruru/library/hh403491.aspx) для SQL Server 2012.
Драйверы соединения
Мы протестировали производительность драйверов OLEDB и ADO.NET, чтобы сравнить скорость
загрузки. Тест показал, что существенной разницы по производительности между этими
драйверами нет.
База данных SQL Server (как получатель)
Следующие действия рекомендованы как при локальном, так и при гибридном перемещении
данных, чтобы обеспечить максимальную скорость загрузки данных.



45
Массовая загрузка данных.
Убедитесь, что процесс загрузки выполняется с минимальным протоколированием.
Избегайте операции SORT.
При гибридном перемещении данных использование операции SORT оказывает даже большее
отрицательное влияние на производительность, чем при локальном перемещении данных.
Операция SORT используется в гибридных решениях при загрузке в таблицу с кластеризованным
индексом.
Решение: масштабирование и гибридное перемещение данных
В этом разделе мы подробно опишем решение по масштабируемому гибридному перемещению
данных. Это решение будет включать службы SSIS для высокоскоростного конвейера и компонентов,
которые позволяют значительно упростить разработку потока данных. Оно будет включать
компоненты WA для обеспечения эластичной масштабируемости всех частей решения. Чтобы
свести к минимуму необходимую полосу пропускания сети и значительно сократить время
выполнения передачи данных, будет использована двухэтапная передача.
Примеры полного решения приведены в приложениях A, B и C.
Требования решения
Для реализации решения необходимы сжатые файлы в качестве источника и получателя, которые
можно загрузить с сайта CodePlex (http://compfilecomponents.codeplex.com/).
ПРИМЕЧАНИЕ. Можно создать собственные сжатые файлы или сжать файлы после их
извлечения. Эти компоненты рассчитаны на сжатие файлов по мере извлечения данных
и сохранения множества больших двоичных объектов в одном файле данных.
Это решение также потребует WA SDK, который можно загрузить со страницы
http://www.windowsazure.com/en-us/develop/downloads/. Этот SDK необходим для использования
очередей и хранилища больших двоичных объектов Azure.
Структура решения
Время на извлечение и перемещение данных обычно рассчитывается как T(e) + T(l), где T(e) —
время на извлечение, а T(l) — время на загрузку. Такие средства, как службы SSIS, позволяют
одновременно выполнять T(e) и T(l), поэтому время на выполнение операции будет равно
максимальному из значений T(e) и T(l). Когда в топологии появляется сеть с ограниченной
пропускной способностью, T(l) и T(e) увеличатся из-за возникновения узкого места. Чтобы
свести к минимуму его влияние, используем двухэтапную стратегию.
Далее в обсуждении прошедшее время будет обозначаться с помощью следующих терминов:






46
T(t) = общее время
T(e) = время, затраченное на извлечение в файл
T(c) = время, затраченное на сжатие
T(u) = время, затраченное на загрузку в промежуточное хранилище
T(d) = время, затраченное на распаковку
T(l) = время, затраченное на загрузку из файла
При двухэтапной передаче, когда все операции выполняются последовательно, время передачи
будет равно T(t) = T(e) + T(c) + T(u) + T(d) + T(l). Время T(t) можно сократить за счет параллельного
выполнения любого числа таких шагов. В данном решении выполним следующее.



Извлечение напрямую в сжатый файл. Вместо суммы T(e) и T(c) получаем наибольшее
значение T(e) или T(c). В данном примере это будет T(c). Это выполняется за счет
использования сжатого файла-получателя с сайта CodePlex.
Передавая сжатые файлы только по сети с ограниченной пропускной способностью,
можно сократить T(u).
Выполняйте распаковку непосредственно в конвейер служб SSIS для передачи. Это
превращает сумму T(d) и T(l) в значение, большее из T(d) и T(l). В данном примере
это будет T(l).
Выполнив такую параллелизацию, получаем T(t) = T(c) + T(u) + T(l). Если можно извлекать данные
из каждой таблицы независимо, то можно перейти к масштабированию перемещения данных,
сократив таким образом T(c) и T(l).
В ситуации, когда большинство данных находятся в единственной таблице, это может не дать
особого ускорения и не приведет к сокращению общего времени перемещения данных.
Чтобы решить эту проблему, нужно создать несколько параллельных процессов, извлекающих данные
из каждой таблицы небольшими фрагментами данных. Это позволяет каждому процессу извлечь
небольшой фрагмент данных и передать его в промежуточное хранилище, где его получит и загрузит
процесс, выполняющийся в центре обработки данных получателя. Это обеспечивает передачу данных
из каждой таблицы даже в том случае, если данные из нее извлекаются в другом центре обработки
данных. При достаточном сжатии устраняется узкое место в сети, а общее время T(t) сокращается
примерно до большего из значений t(c) и T(l), то есть приближается к показателю, который может
быть достигнут при отсутствии в топологии медленной сети. Фрагментирование данных также
позволяет избавиться от ограничений, связанных с большими размерами таблиц.
Архитектура
Топология решения с использованием компонентов платформы WA показана на следующей схеме.
47
Azure
Blob Storage (Holds SSIS Packages
and compressed files containing
data to be imported)
Import
Queue
(Holds
names of
Files
waiting to
be
imported)
Import Server 1 Import Server 2 Import Server 3
Export
Queue
(Holds
names of
Packages
needing
to be
exported)
Azure Database
Destination
Import Server N
On-Premises or Remote setup
Export Server 1 Export Server 2 Export Server 3
Export Server N
Non-Azure Source
Creates SSIS Packages and Enqueues Them
48
В этой топологии:







49
Хранилище больших двоичных объектов WA используется для промежуточного хранения
сжатых файлов данных.
Очередь экспорта WA будет содержать сообщения о пакетах служб SSIS, которые должны
быть извлечены следующими из локальных источников данных, сохранены в сжатые
файлы и переданы в хранилище больших двоичных объектов. Это первый этап
перемещения.
Очередь импорта WA будет содержать сообщения о том, какие сжатые файлы данных
переданы в промежуточную среду и готовы к импорту в получатель.
Серверами импорта в Azure являются виртуальные машины WA. На этих машинах работает
один или несколько процессов, опрашивающих очередь импорта на предмет наличия
файлов данных, готовых к обработке.
Когда запись успешно извлечена из очереди импорта, обработка выполняется следующим
образом.
o Сжатый файл загружается из промежуточного хранилища WA.
o Пакет служб SSIS строится программным способом для извлечения данных из
файла данных в сегментированную базу данных-получатель SQL Windows Azure.
o Пакет выполняется.
o Запись, извлеченная из очереди импорта, удаляется, чтобы избежать ее обработки
другим процессом.
Процессы импорта не могут обработать файлы до тех пор, пока они не загружены
и сообщение не помещено в очередь. Эти задачи обрабатываются процессами,
работающими на серверах экспорта в локальной конфигурации. Несколько
процессов экспорта могут работать на разных серверах. Эти процессы опрашивают
очередь экспорта на предмет возможности извлечения фрагментов данных таблиц.
Когда запись успешно извлечена из очереди экспорта, обработка выполняется следующим
образом.
o Пакет служб SSIS загружается из хранилища больших двоичных объектов WA,
которое извлекает этот фрагмент данных в сжатый файл данных.
o Пакет выполняется.
o Сжатый файл передается в промежуточное хранилище больших двоичных
объектов WA.
o Запись, извлеченная из очереди экспорта, удаляется, чтобы избежать повторной
обработки этого фрагмента данных другим процессом экспорта.




Процессы экспорта не могут выполнять экспорт до тех пор, пока в очереди не появятся записи,
содержащие пакеты служб SSIS для выполнения. Они создаются отдельным процессом.
Поскольку фрагменты данных должны быть определены с использованием диапазонов
ключей, чтобы избавиться от сложностей, связанных с координацией, данный процесс
считывает таблицы, которые должны быть перемещены, и для каждой таблицы создает
пакеты служб SSIS с целью извлечения фрагментов данных с предопределенным числом
строк данных. При создании пакета служб SSIS он передается в хранилище больших двоичных
объектов WA, а в очередь экспорта помещается сообщение, чтобы процессы экспорта могли
извлечь и выполнить эту операцию.
Сообщения, помещаемые в очередь экспорта, — это просто имена пакетов служб SSIS,
которые процесс экспорта должен загрузить и выполнить.
Сообщения, помещаемые в очередь импорта, — это просто имена файлов данных,
помещенных процессом экспорта в хранилище больших двоичных объектов.
Чтобы обеспечить извлечение нескольких фрагментов данных из каждой таблицы, для
пакетов служб SSIS и файлов данных должно быть использовано соглашение об именах
<schema>.<table>.<sequence>.<extension>.
Например, первый фрагмент данных из таблицы Production.TransactionHistory будет
извлекаться пакетом служб SSIS с именем Production.TransactionHistory.0.dtsx, а
данные помещаются в файл Production.TransactionHistory.0.dat. Второй фрагмент
данных извлекается пакетом служб SSIS с именем Production.TransactionHistory.1.dtsx,
а данные помещаются в файл Production.TransactionHistory.1.dat, и так далее.



50
В качестве источников в пакетах служб SSIS используются запросы. Если размер фрагмента
данных определен в 1 000 000 строк, то запрос для извлечения первого фрагмента должен
содержать «WHERE keyCol BETWEEN 1 AND 1000000 ORDER BY KeyCol». Использование
предложения Order By для кластеризованного индекса обеспечивает последовательную
вставку для получателя, что позволяет оптимизировать разбиение на страницы при
последовательной вставке.
Поскольку очереди служат для распределения работы между несколькими процессами,
число процессов импорта и экспорта может увеличиваться или уменьшаться, изменять
конфигурацию процесса. Код при этом не требуется.
Сообщения в очереди Azure имеют срок действия, начиная с момента их извлечения из
очереди. Если сообщение не удалено до истечения этого срока, то оно снова помещается
в эту очередь. Это приводит к повторной попытке обработки, если процесс импорта или
экспорта завершился сбоем.
Вопросы проектирования
В данном решении получателем является сегментированный получатель в SQL Azure. При
разработке решений для Azure необходимо включить логику повторов для обработки временных
шибок. Текущие получатели ADO.NET и ODBC не имеют встроенной логики повторов, однако
можно разработать пользовательские компоненты. Другой способ — повторное выполнение
пакета, создание потока данных для обработки нарушений первичного ключа. Повторы
обрабатываются на другом уровне одновременно с обычной обработкой очередей.
Реализация решения
Для этого решения требуется три отдельных приложения.



Приложение-координатор. Определяет фрагменты данных для извлечения
и соответствующим образом создает пакет служб SSIS. Пример координатора,
написанного на языке C#, см. в приложении А.
Приложение экспорта. Выполняет пакет служб SSIS, извлекая данные и передавая их
в хранилище больших двоичных объектов. Пример на языке C# см. в приложении В.
Приложение для формирования пакетов в облаке. Извлекает данные из хранилища
больших двоичных объектов и импортирует их в получатель. Пример на языке C# см.
в приложении С.
Требования к оборудованию и программному обеспечению
Чтобы сделать это решение действительно масштабируемым, необходимо несколько локальных
компьютеров для запуска процессов извлечения служб SSIS и несколько виртуальных машин Azure
для запуска процессов импорта служб SSIS. Каждый сервер и виртуальная машина способны
выполнять несколько процессов.
Установка служб SSIS на локальном компьютере и облачных виртуальных машинах
Чтобы процессы могли использовать службы SSIS, требуется установить среду выполнения
служб SSIS и SDK. Службы SSIS и приложение SQL Server для выполнения процессов не требуются.
Но если планируется использование возможностей каталога служб SSIS для мониторинга, то SQL
Server и каталог служб SSIS являются обязательными компонентами.
Установка пользовательских компонентов сжатых файлов
В решении используются сжатые файлы источника и получателя служб SSIS с сайта CodePlex
(http://compfilecomponents.codeplex.com/). Эти компоненты необходимо установить, следуя
инструкциям, приведенным по адресу http://msdn.microsoft.com/ru-ru/library/ms403356.
Настройка очереди экспорта и импорта SQL Azure
Необходимы две очереди — по одной на каждый из этапов перемещения данных. Создайте
их в учетной записи хранилища, которое будет использоваться решением перемещения данных.
Очередь экспорта будет использоваться процессами выполнения извлечения, а очередь импорта —
процессами импорта.
51
Настройка хранилища больших двоичных объектов SQL Azure
И пакеты служб SSIS, и файлы данных будут помещаться в хранилище больших двоичных объектов
WA. Создайте контейнер для этих файлов.
Создание приложения-координатора
Координатор — часть решения, которая не масштабируется. Оно не взаимодействует с процессами
напрямую. Вместо этого оно считывает список таблиц для передачи, создает пакеты служб SSIS для
извлечения из этих таблиц фрагментов данных предопределенного размера, передает пакеты SSIS
в хранилище больших двоичных объектов WA и ставит в очередь экспорта сообщения. Этот процесс
определяет, какие таблицы и в каком порядке будут передаваться, а также то, какой размер будут
иметь фрагменты данных. Процессы импорта и экспорта работают и опрашивают очереди, но
передача данных начинается тогда, когда в очереди экспорта появляются сообщения. Пример
на языке C# для этого решения см. в приложении А.
В этом примере единственное, что должно содержать сообщение в очереди, — это имя пакета
SSIS, которое будет извлечено и выполнено. Поскольку из одной таблицы извлекается несколько
фрагментов данных, используются соглашения об именах <Schema>.<Table>.<sequence>.dtsx.
Например, это будет имя файла Production.TransactionHistory.10.dtsx.
Создайте приложение экспорта
Приложение экспорта опрашивает очередь экспорта, извлекает из нее записи, загружает пакет служб
SSIS для экспорта фрагмента данных, выполняет этот пакет, передает файл данных, ставит в очередь
импорта сообщение, сигнализирующее процессу импорта о том, что файл загружен, и удаляет запись,
выведенную из очереди экспорта. Сообщение в очереди импорта должно содержать только имя
файла данных. Поскольку несколько фрагментов данных могут извлекаться из одной таблицы,
используется соглашение об именах <Schema>.<Table>.<Sequence>.dat. Пример имени файла
данных — Production.TransactionHistory.10.dat. Пример приложения экспорта см. в приложении В.
Создайте приложение для формирования пакетов в облаке
Это приложение опрашивает очередь импорта на предмет появления файлов данных в
промежуточном хранилище больших двоичных объектов WA. Когда сообщение извлекается
из очереди, этот процесс загружает файл из хранилища больших двоичных объектов, создает
пакет служб SSIS на основе данных, которые может собрать, выполняет этот пакет для импорта
данных в базу данных SQL Windows Azure, а затем удаляет запись, выведенную из очереди
импорта. Если имя файла создано с использованием соглашения об именах, то процессу
доступна большая часть метаданных, необходимых для создания пакета служб SSIS.



52
Сообщение, выведенное из очереди импорта, содержит имя исходного файла.
Имя исходного файла содержит схему и имя таблицы, куда необходимо импортировать
данные.
Файл содержит метаданные, необходимые для создания конвейера потока данных служб SSIS.
В этом примере информация о соединении и число сегментов — единственное, что неизвестно
на данном этапе. Эта информация может храниться в базе данных, в таблицах WA или даже
передаваться в параметрах командной строки в зависимости от ваших требований. Пользуйтесь
в своем приложении способом передачи метаданных по своему выбору.
Сообщения в очереди импорта используются так, как описано в предыдущем подразделе.
Пример приложения, создающего пакеты в облаке, приведен в приложении C.
Сегментирование данных с помощью преобразования «Условное разбиение»
При отправке данных в сегментированный получатель пользуйтесь условным разбиением для
направления данных соответствующему получателю, как описано в разделе Сегментирование
и федерации ранее в этой статье.
Заключение
Службы SQL Server Integration Services (SSIS) эффективны в качестве средства
перемещения данных в базы данных SQL Windows Azure и обратно в рамках общего
решения для извлечения, преобразования и загрузки данных (ETL) и решения переноса
данных. Службы SSIS могут быть эффективно задействованы для перемещения данных
между источниками и получателями данных в облаке, а также в гибридных сценариях,
включающих облачные и локальные базы данных. В этом документе описываются общие
рекомендации по использованию служб SSIS, касающиеся применения их для облачных
источников и получателей, обсуждается планирование проектов служб SSIS для
перемещения данных в Azure и гибридных средах, а также дается пошаговое описание
примера максимального повышения производительности работы гибридной схемы за счет
масштабирования переноса данных.
Дополнительные сведения:
http://www.microsoft.com/sqlserver/: веб-сайт SQL Server
http://technet.microsoft.com/ru-ru/sqlserver/: технический центр SQL Server
http://msdn.microsoft.com/ru-ru/sqlserver/: центр разработчиков данных SQL Server
Помогла ли вам эта статья? Пожалуйста, оставьте свой отзыв. Оцените материал по шкале
от 1 (плохо) до 5 (отлично) и укажите причины выставления своей оценки. Например.


Вы высоко оценили этот документ из-за наличия подходящих примеров, четких
снимков экрана, ясного изложения или по какой-либо другой причине?
Вы низко оценили полезность этого документа из-за плохих примеров, нечетких
снимков экрана и путаного изложения?
Ваш отзыв поможет нам повысить качество выпускаемых нами технических документов.
Отправить отзыв
53
Приложение A. Пример процесса координатора
Приведенный в этом приложении код на языке C# создает пакеты служб SSIS для извлечения
фрагментов данных из всех таблиц базы данных в сжатый файл-получатель, передает эти пакеты
в хранилище больших двоичных объектов и ставит в очередь экспорта сообщение, содержащее
имя пакета. Это приложение описано в разделе Масштабирование и гибридное перемещение
данных выше в данной статье.
Чтобы использовать этот код как есть, необходимо добавить ссылки на сборки
Microsoft.SqlServer.DtsPipelineWrap; Microsoft.SQLServer.DtsRuntimeWrap;
Microsoft.SQLServer.ManagedDTS; Microsoft.SqlServer.TxScript;
Microsoft.WindowsAzure.StorageClient. Необходимо также загрузить и установить
компоненты сжатых файлов с сайта http://compfilecomponents.codeplex.com/.
using
using
using
using
using
using
using
using
using
System;
System.Collections.Generic;
System.Linq;
System.Text;
System.Reflection;
Microsoft.SqlServer.Dts.Runtime;
Microsoft.SqlServer.Dts.Pipeline.Wrapper;
System.Data;
System.Data.SqlClient;
// доступ к Azure:
using Microsoft.WindowsAzure;
using Microsoft.WindowsAzure.StorageClient;
namespace CreateSSISFilesFromTemplate
{
class MakePackages
{
static void Main(string[] args)
{
computedColumns = new HashSet<String>();
MakePath();
app = new Application();
// получение списка таблиц:
getTables();
// инициализация ключа один раз:
initializeCloudInfo();
for (int i = 0; i < tables.Length; i++ )
{
makeSSISPackage(i);
if (deleteLocalFiles)
System.IO.File.Delete(packagePath + files[i] + ".dtsx");
}
54
Console.WriteLine("Нажмите любую кнопку, чтобы продолжить...");
Console.ReadKey();
}
/*
* Удобная функция, обеспечивающая простое
* помещение пакетов в очередь экспорта в одной строке
*
* */
private static void enqueSSISPackage(String fileName)
{
CloudQueueMessage msg = new CloudQueueMessage(fileName);
queue.AddMessage(msg);
}
/*
* uploadSSISPackage передает только один файл в
* хранилище больших двоичных объектов.
*
* */
private static void uploadSSISPackage(String fileName)
{
// сборка имени файла для передачи:
CloudBlob blob = container.GetBlobReference(fileName);
blob.Metadata["MetaName"] = fileName;
// передача файла:
blob.UploadFile(packagePath + fileName);
}
/*
* Вызывается один раз в начале, initializeCloudInfo использует
* сохраненные данные облака и возвращает ссылки для
* доступа к очереди и большим двоичным объектам в облаке путем выполнения
*
* */
private static void initializeCloudInfo()
{
// сначала просто установим соединение с учетной записью Azure:
key = new Microsoft.WindowsAzure.StorageCredentialsAccountAndKey(
accountName, primaryAccessKey);
account = new Microsoft.WindowsAzure.CloudStorageAccount(key, true);
// получение клиента и контейнера больших двоичных объектов:
client = account.CreateCloudBlobClient();
container = new CloudBlobContainer(blobContainerName, client);
container.CreateIfNotExist();
BlobContainerPermissions permissions = container.GetPermissions();
container.SetPermissions(permissions);
55
// настройка для работы с очередью
queueClient = account.CreateCloudQueueClient();
queue = queueClient.GetQueueReference(queueName);
queue.CreateIfNotExist();
}
/*
* Метод makeSSISPackage выполняет большую часть работы по созданию
* пакета шаблона и передаче этого пакета в большой двоичный объект
* больших двоичных объектов. Он вызывается с целочисленным значением,
* указывающим индекс в массиве таблиц для создания и передачи файла служб
* SSIS. Он вызывается один раз для каждой таблицы, для которой создается
* пакет служб SSIS для извлечения.
* */
private static void makeSSISPackage(int i)
{
Console.WriteLine("Tablename: " + tables[i] + " filename: " + files[i]);
Package p = app.LoadPackage(templatePath + templatePackageName, null);
// повторно сформировать идентификатор GUID, чтобы избежать конфликтов
// при выполнении.
p.RegenerateID();
// получим компоненты потока данных:
// установим параметр servername
// параметр ServerName должен существовать в пакете
// и используется в соединении с сервером
p.Parameters["ServerName"].Value = serverName;
p.Connections[0].AcquireConnection(null);
MainPipe dataFlowTask = (MainPipe)((TaskHost)p.Executables[
dataflowTaskName]).InnerObject;
// убедитесь, что он не равен null:
if (dataFlowTask == null)
return;
// теперь у нас есть задача потока данных, воспользуемся ей.
// удаление пути:
dataFlowTask.PathCollection.RemoveAll();
// после того как путь удален, удаляются все входные данные
// и создается новый вход
dataFlowTask.ComponentMetaDataCollection["CompressedFileDest"
].InputCollection.RemoveAll();
// создание экземпляра и задание свойств компонента
CManagedComponentWrapper outputInstance =
dataFlowTask.ComponentMetaDataCollection["CompressedFileDest"
].Instantiate();
outputInstance.ProvideComponentProperties();
// установим в новую таблицу
CManagedComponentWrapper instance =
dataFlowTask.ComponentMetaDataCollection["Source"].Instantiate();
56
instance.ProvideComponentProperties();
// ProvideComponentProperties сбрасывает имя. Восстановим его:
dataFlowTask.ComponentMetaDataCollection["OLE DB Source"].Name = "Источник";
if (dataFlowTask.ComponentMetaDataCollection["Источник"
].RuntimeConnectionCollection.Count > 0)
{
dataFlowTask.ComponentMetaDataCollection["Источник"
].RuntimeConnectionCollection[0].ConnectionManager =
DtsConvert.GetExtendedInterface(p.Connections[0]);
dataFlowTask.ComponentMetaDataCollection["Источник"
].RuntimeConnectionCollection[0].ConnectionManagerID
=
p.Connections[0].ID;
}
// зададим запрос и значения диапазона
p.Parameters["SQLSourceQuery"].Value = "SELECT * FROM " + tables[i];
// на данный момент просто зададим диапазон. Мы уточним его
// перед развертыванием.
p.Parameters["BeginRange"].Value = 1.ToString();
p.Parameters["EndRange"].Value = 1000000.ToString();
// зададим другие необходимые имена параметров:
p.Parameters["DataFile"].Value = tables[i] + "." +
fileSequence.ToString() + ".dat";
p.Parameters["MaxRowsInBuffer"].Value = getMaxRowsInBuffer(tables[i]);
p.Parameters["KeyColName"].Value = keyCols[i];
// зададим режим доступа компонента источника
// 0 ― таблица или представление, 3 ― SQL-запрос
instance.SetComponentProperty("AccessMode", 3);
instance.SetComponentProperty("SqlCommandVariable",
"User::FullSQLStatement");
// теперь все метаданные становятся недействительными
// сбрасываем их:
instance.Validate();
instance.AcquireConnections(null);
try
{
instance.ReinitializeMetaData();
}
catch (Exception e)
{
Console.WriteLine("Возникло исключение в: {0}\nСообщение: {1}",
tables[i], e.Message);
}
instance.ReleaseConnections();
// зададим диспетчер соединений
if (dataFlowTask.ComponentMetaDataCollection["CompressedFileDest"
57
].RuntimeConnectionCollection.Count > 0)
{
dataFlowTask.ComponentMetaDataCollection["CompressedFileDest"
].RuntimeConnectionCollection[0].ConnectionManager =
DtsConvert.GetExtendedInterface(p.Connections[1]);
dataFlowTask.ComponentMetaDataCollection["CompressedFileDest"
].RuntimeConnectionCollection[0].ConnectionManagerID =
p.Connections[1].ID;
}
// создадим путь для связывания двух компонентов
IDTSPath100 path = dataFlowTask.PathCollection.New();
// свяжем два компонента:
path.AttachPathAndPropagateNotifications(
dataFlowTask.ComponentMetaDataCollection["Источник"
].OutputCollection[0],
dataFlowTask.ComponentMetaDataCollection["CompressedFileDest"
].InputCollection[0]);
// теперь выберем все столбцы для вывода:
IDTSVirtualInput100 input =
dataFlowTask.ComponentMetaDataCollection["CompressedFileDest"
].InputCollection[0].GetVirtualInput();
foreach (IDTSVirtualInputColumn100 column in
input.VirtualInputColumnCollection)
{
// вызов метода setUsageType получателя
// добавление всех виртуальных входных столбцов в качестве входных
try
{
if (!computedColumns.Contains(String.Format("{0}.{1}", tables[i],
column.Name)))
outputInstance.SetUsageType(
dataFlowTask.ComponentMetaDataCollection[
"CompressedFileDest"].InputCollection[0].ID,
input, column.LineageID, DTSUsageType.UT_READONLY);
}
catch (Exception e)
{
Console.WriteLine(e.Message.ToString());
}
}
// проверка компонента, чтобы все свойства остались неизменными.
instance.Validate();
58
instance.AcquireConnections(null);
try
{
instance.ReinitializeMetaData();
}
catch (Exception e)
{
Console.WriteLine("Возникло исключение в: {0}\nСообщение: {1}",
tables[i],
e.Message);
}
instance.ReleaseConnections();
//
//
//
//
//
//
//
//
сохранение пакета с правильными порядковыми номерами:
необходимо добавить порядковый номер в файл
в этом примере запрос получает точные размеры фрагментов данных.
он не очень эффективен, но все, что он должен делать, —
это быстро отправить первые пакеты, чтобы
загрузить работой процессы экспорта, а затем опережать
их. Этого будет достаточно
при этом будет поддерживаться точный размер
// создать файлы посредством запроса фрагментов данных:
String SQLQuery = String.Format("SET NOCOUNT ON \n"
+ "DECLARE @minVal NVARCHAR(max) \n"
+ "declare @maxVal NVARCHAR(max) \n"
+ "declare @startVal NVARCHAR(max) \n"
+ "declare @chunkSize INT \n"
+ "set @startVal = @p0 \n"
+ "set @chunkSize = @p1 \n"
+ "\n"
+ "IF @startVal IS NULL \n"
+ "
SELECT @minVal = MIN({0}) FROM {1} \n"
+ "ELSE \n"
+ "
SELECT @minVal = MIN({0}) FROM {1} WHERE {0} > @startVal \n"
+ "\n"
+ "SELECT @maxVal = MAX({0}) FROM (SELECT TOP (@ChunkSize) {0} "
+ "FROM {1} WHERE {0} >= @MinVal ORDER BY {0}) chunk \n"
+ "SELECT @minVal, @MaxVal", keyCols[i], tables[i]);
String connStr = "Data Source=" + serverName + ";Initial Catalog="
+ dbName + ";Integrated Security=true;";
// создание подготовленной инструкции:
try
{
SqlConnection conn = new SqlConnection();
conn.ConnectionString = connStr;
conn.Open();
SqlCommand cmd = new SqlCommand(SQLQuery);
cmd.CommandTimeout = 0;
cmd.Connection = conn;
59
SqlParameter p0 = cmd.CreateParameter();
p0.Direction = ParameterDirection.Input;
p0.DbType = DbType.AnsiString;
p0.Value = DBNull.Value;
p0.ParameterName = @"@p0";
SqlParameter p1 = cmd.CreateParameter();
p1.Direction = ParameterDirection.Input;
p1.DbType = DbType.Int32;
p1.Value = getMaxRange(tables[i]);
p1.ParameterName = @"@p1";
cmd.Parameters.Add(p0);
cmd.Parameters.Add(p1);
// теперь выполним настройку и считаем значения:
Boolean wasNull = false;
int j;
j = 0;
while (!wasNull)
{
SqlDataReader rdr = cmd.ExecuteReader();
rdr.Read(); // в результатах возвращается только одна запись
if (rdr.IsDBNull(0))
{
wasNull = true;
}
else
{
String beginRange = rdr.GetString(0);
String endRange = rdr.GetString(1);
Console.WriteLine("beginRange: {0}, EndRange: {1}",
beginRange, endRange);
String fileName = String.Format("{0}.{1}.dtsx", files[i], j);
p.Parameters["BeginRange"].Value = beginRange;
p.Parameters["EndRange"].Value = endRange;
p.Parameters["DataFile"].Value = String.Format("{0}.{1}.dat",
files[i], j);
app.SaveToXml(packagePath + fileName, p, null);
// передача пакета в большой двоичный объект, находящийся
// в хранилище:
uploadSSISPackage(fileName);
Console.WriteLine("Постановка в очередь {0}", fileName);
// постановка сообщения в очередь
enqueSSISPackage(fileName);
// настройка для следующего выполнения.
p0.Value = endRange;
}
j++;
rdr.Close();
}
60
conn.Close();
}
catch (SqlException e)
{
Console.WriteLine("Обнаружено исключение SQL: {0}", e.Message);
Console.WriteLine(e.StackTrace);
}
}
/*
* Убедитесь, что каталог существует для шаблона и для
* рабочего каталога
*
* */
private static void MakePath()
{
if (! System.IO.Directory.Exists(templatePath))
System.IO.Directory.CreateDirectory(templatePath);
if (! System.IO.Directory.Exists(packagePath))
System.IO.Directory.CreateDirectory(packagePath);
}
/*
* В этом случае это жестко запрограммированные исключения на размер фрагмент
* а данных Более гибкий подход позволил бы сохранить это в другом месте
* и считывать эту конфигурацию во время выполнения
*
* */
private static int getMaxRange(String tabName)
{
if (tabName == "[Production].[ProductPhoto]" || tabName ==
"[Production].[ProductPhoto1]")
return 5000;
if (tabName == "[Sales].[Individual]" || tabName ==
"[Sales].[Individual1]")
return 2000000;
if (tabName == "[Production].[TransactionHistory]" || tabName ==
"[Production].[TransactionHistory1]")
return 10000000;
// вариант по умолчанию
return 1000000;
}
/*
*
*
*
*
*
*
61
Установка размера буфера крайне важна для производительности пакетов служб SSIS
Пакетам со значениями типа больших двоичных объектов необходимо задавать
намного меньшие значения MaxRowsInBuffer.
Хотя здесь значение задано в коде, возможен вариант с его хранением
во внешнем параметре и чтением этой настройки в процессе выполнения.
* */
private static int getMaxRowsInBuffer(String tabName)
{
if (tabName == "[Production].[ProductPhoto]"
|| tabName == "[Production].[ProductPhoto1]")
return 10;
if (tabName == "[Sales].[Individual]"
|| tabName == "[Sales].[Individual1]")
return 20;
if (tabName == "[Production].[TransactionHistory]"
|| tabName == "[Production].[TransactionHistory1]")
return 10000;
// вариант по умолчанию — 1 миллион в целях тестирования
return 10000;
}
/*
* В этом примере используется подход с чтением всех таблиц из
* представлений каталогов и их упорядочиванием по числу строк в порядке
DESC.
*
*
*
*
*
Это только пример, но для других перемещений, которые могут не включать
все таблицы, лучшим вариантов может оказаться хранение этих элементов в
еще одной таблице или файле и их считывание во время выполнения. Это
позволило бы корректировать список без внесения изменений в код.
*/
private static void getTables()
{
String connectionString = "Data Source=" + serverName +
";Initial Catalog=" + dbName + ";Integrated Security=true;";
// создание строки запроса, которая нужна для получения списка таблиц:
String queryString = "SELECT t.file_name, t.table_name, t.rows, "
+ "(SELECT TOP 1 sc.name FROM sys.indexes i \n"
+ "
JOIN sys.index_columns c ON i.index_id = c.index_id \n"
+ "
JOIN sys.columns sc ON c.object_id = sc.object_id \n"
+ "
WHERE i.is_primary_key = 1 AND c.object_id = t.object_id \n"
+ "
order by c.column_id ) as first_key_col \n"
+ "FROM \n"
+ "( \n"
+ "
SELECT schema_name(t.schema_id) + '.' + t.name AS "
+ " [file_name], \n"
+ "
'[' + schema_name(t.schema_id) + ']' + '.' + "
+ "'[' + t.name + ']' AS [table_name], \n"
+ "
SUM(p.rows) AS rows, t.object_id \n"
+ "
FROM sys.tables t \n"
+ "
JOIN sys.partitions p on "
+ "t.object_id = p.object_id \n"
+ "
LEFT JOIN sys.indexes i ON t.object_id = "
+ "i.object_id AND i.is_primary_key = 1 \n"
+ "
WHERE t.type_desc = 'USER_TABLE' \n"
+ "
GROUP BY t.schema_id, t.name, t.object_id \n"
62
+ ") AS t ORDER BY rows DESC \n";
// настройка соединения и запроса:
SqlConnection conn = new SqlConnection();
conn.ConnectionString = connectionString;
conn.Open();
SqlCommand cmd = new SqlCommand(queryString, conn);
SqlDataReader rdr = cmd.ExecuteReader();
System.Collections.ArrayList fileList =
new System.Collections.ArrayList();
System.Collections.ArrayList tableList =
new System.Collections.ArrayList();
System.Collections.ArrayList keyList =
new System.Collections.ArrayList();
while (rdr.Read())
{
fileList.Add(rdr.GetString(0));
tableList.Add(rdr.GetString(1));
keyList.Add(rdr.GetString(3));
}
rdr.Close();
tables = (String[]) tableList.ToArray(typeof(String));
files = (String[])fileList.ToArray(typeof(String));
keyCols = (String[])keyList.ToArray(typeof(String));
// получение списка вычисляемых столбцов,
// чтобы при необходимости их можно было не учитывать:
cmd.CommandText = "SELECT '[' + schema_name(t.schema_id) + ']' + "
+ " '.' + '[' + t.name + ']' AS [table_name], \n"
+ "
c.name FROM sys.tables t JOIN sys.columns c "
+ " ON t.object_id = c.object_id \n"
+ "
WHERE c.is_computed = 1;";
cmd.CommandTimeout = 0;
rdr = cmd.ExecuteReader();
while (rdr.Read())
{
computedColumns.Add(rdr.GetString(0) + "." + rdr.GetString(1));
}
conn.Close();
}
//
//
//
//
//
//
Константы и переменные, которые будут использоваться в пакете,
показанном в этом примере, чтобы продемонстрировать, какие конфигурации
применялись лучшим вариантом было бы хранить их во внешнем ресурсе,
чтобы код можно было использовать повторно — загружать значения
во время выполнения через параметр командной строки, внешнюю таблицу
или путь к файлу
// пакета шаблона
private const String templatePath = @"c:\MigrationPOC\";
// путь для хранения пакетов служб SSIS
private const String packagePath = @"c:\packages\";
private const String templatePackageName = @"TemplatePackage.dtsx";
// имя сервера-источника
63
private const String serverName = @"changed";
// имя базы данных
private const String dbName = @"AdventureWorks";
private const String pipelineString = @"SSIS.pipeline";
private const String BlobURL = "changed.blob.core.windows.net";
// получение из учетной записи хранилища,
private const String primaryAccessKey = "changed";
// в которой хранятся пакеты
private const String blobContainerName = "exports";
// Используемая учетная запись хранилища Azure
private const String accountName = "changed";
private const String queueName = "export";
private const String queueUrl = "changed.queue.core.windows.net";
// имя задачи потока данных в шаблоне
private const String dataflowTaskName = "MoveTheData";
/*
* Приведенные ниже переменные не являются конфигурациями, а
* используются в приложении
*
* */
private static Application app;
private static String[] tables;
private static String[] files;
private static String[] keyCols;
private static int fileSequence = 0;
private
private
private
private
private
private
private
private
}
}
64
static
static
static
static
static
static
static
static
bool deleteLocalFiles = true;
Microsoft.WindowsAzure.StorageCredentialsAccountAndKey key;
CloudStorageAccount account;
CloudBlobClient client;
CloudBlobContainer container;
CloudQueueClient queueClient;
CloudQueue queue;
HashSet <String> computedColumns;
Приложение B. Пример процесса извлечения
Приведенный в этом приложении код на языке C# является реализацией программы,
загружающей пакеты служб SSIS из хранилища больших двоичных объектов, выполняющей
их и передающей эти пакеты в хранилище больших двоичных объектов, как описано в разделе
Масштабируемое и гибридное перемещение данных ранее в этой статье. Он работает с кодом,
приведенным в приложениях A и C, являясь частью полного решения.
Этот код требует ссылки на Microsoft.SQLServer.ManagedDTS
и Microsoft.WindowsAzure.StorageClient.
using
using
using
using
using
using
using
System;
System.Data;
Microsoft.SqlServer.Dts.Runtime;
Microsoft.WindowsAzure.StorageClient;
Microsoft.WindowsAzure;
System.IO;
System.IO.Compression;
namespace PopAndExtract
{
public class PopAndExtract
{
public static void Main()
{
// настройка конфигурации для перемещения
PopAndExtract p = new PopAndExtract();
p.BlobURL = "changed.blob.core.windows.net";
p.PrimaryAccessKey = "changed";
p.containerName = "exports";
p.accountName = "changed";
p.setWorkingPath(@"d:\DataLanding\");
p.queueName = "export";
p.queueURL = "changed.queue.core.windows.net";
p.importQueueName = "import";
p.getContainerAndQueue();
p.deleteSourceFiles = true;
// настройка для возможности выполнения пакета:
p.app = new Microsoft.SqlServer.Dts.Runtime.Application();
// начало извлечения и выполнения:
int iteration = 0;
TimeSpan timeSpan = new TimeSpan(0, 10, 0);
CloudQueueMessage msg = p.queue.GetMessage(timeSpan);
p.fileName = (msg == null) ? "" : msg.AsString.Replace("[", ""
).Replace("]", "");
while (true)
{
65
p.fileName = (msg == null) ? "" : msg.AsString.Replace("[", ""
).Replace("]", "");
if (msg != null && p.fileName != null && p.fileName.Length > 0)
{
Console.WriteLine("Извлечение из большого двоичного объекта в
файл: {0}",
p.fileName);
CloudBlob blob = p.container.GetBlobReference(p.fileName);
int maxIterations = 5;
int iterations = 0;
bool successDownloading = false;
{
while (!successDownloading && iterations++ < maxIterations)
{
try
{
blob.DownloadToFile(p.workingPath + p.fileName);
// мы попадаем сюда только в случае успеха
successDownloading = true;
}
catch (Exception e)
{
Console.WriteLine(
"При загрузке {0} возникло исключение. " +
"Итерация {1} из максимального количества:
{2}.",
p.fileName, iteration, maxIterations);
Console.WriteLine("Сообщение об ошибке: {0}",
e.Message);
if (iteration < maxIterations)
Console.WriteLine("Повтор ... \n\n");
else
{
Console.WriteLine("ошибка... \n\n");
}
}
}
}
// затем выполним пакет:
DateTime begin = DateTime.Now;
Console.WriteLine("Подготовка и выполнение пакета: {0}, " +
"Начато в {1}", p.dtsxFileName, begin);
p.setupAndExecutePackage();
Console.WriteLine(p.result.ToString() + "\n\n" +
p.result.GetHashCode());
DateTime end = DateTime.Now;
Console.WriteLine("Выполнение пакета завершено в {0}", end);
Console.WriteLine("Общее время выполнения {0}",
end.Subtract(begin).TotalSeconds);
66
// передача файла
String cabFileName = p.workingPath + p.dataFileName;
bool successUploading = false;
iteration = 0;
begin = DateTime.Now;
Console.WriteLine("Начало загрузки в {0}", begin);
while (!successUploading && iteration++ < maxIterations)
{
Console.WriteLine("uploading {0}", p.dataFileName);
try
{
blob = p.container.GetBlobReference(p.dataFileName);
blob.UploadFile(p.workingPath + p.dataFileName);
successUploading = true;
}
catch (Exception e)
{
Console.WriteLine("Возникло исключение при отправке”+
"{0}. Итерация {1} из максимального количества:
{2}.",
p.fileName, iteration, maxIterations);
Console.WriteLine("Сообщение об ошибке: {0}",
e.Message);
if (iteration < maxIterations)
Console.WriteLine("Повтор... \n\n");
else
{
Console.WriteLine("ошибка... \n\n");
p.success = false;
}
}
Console.WriteLine("\n\n");
}
end = DateTime.Now;
Console.WriteLine("Передача завершилась в {0}", end);
Console.WriteLine("Общее время передачи: {0} секунд",
end.Subtract(begin).TotalSeconds);
//не помещать его в очередь передачи, если выполнение не было
//успешным идет передача.
if (!successUploading)
continue;
CloudQueueMessage inputMessage = new CloudQueueMessage(
p.dataFileName);
if (p.success)
p.importQueue.AddMessage(inputMessage);
p.queue.DeleteMessage(msg);
// когда управление попадает в эту точку, отправка полностью
// завершена. теперь выполним очистку и перейдем к следующему
// сообщению:
if (p.deleteSourceFiles)
67
{
// удаление пакета dtsx:
blob = p.container.GetBlobReference(p.fileName);
blob.DeleteIfExists();
//удаление локальной копии пакета dtsx:
System.IO.File.Delete(p.workingPath + p.dtsxFileName);
// удаление локального файла данных:
System.IO.File.Delete(p.workingPath + p.dataFileName);
}
// очистка завершена
msg = p.queue.GetMessage(timeSpan);
p.fileName = (msg == null) ? "" : msg.AsString.Replace("[", ""
).Replace("]", "");
}
while (msg == null)
{
System.Threading.Thread.Sleep(5000);
msg = p.queue.GetMessage(timeSpan);
}
iteration = 0;
}
}
public void setupAndExecutePackage()
{
Package p = app.LoadPackage(workingPath + fileName, null);
dataFileName = p.Parameters["DataFile"].Value.ToString();
// теперь выполним пакет:
try
{
result = p.Execute();
success = true;
}
catch (Exception e)
{
Console.WriteLine(fileName);
Console.WriteLine(e.Message);
Console.WriteLine(e.StackTrace);
Console.WriteLine();
success = false;
}
68
}
public void setWorkingPath(String pWorkingPath)
{
if (!pWorkingPath.Substring(pWorkingPath.Length - 1).Equals(@"\"))
pWorkingPath = pWorkingPath + @"\";
workingPath = pWorkingPath;
}
public void getContainerAndQueue()
{
Microsoft.WindowsAzure.StorageCredentialsAccountAndKey key
= new Microsoft.WindowsAzure.StorageCredentialsAccountAndKey(
accountName, PrimaryAccessKey);
CloudStorageAccount account =
new Microsoft.WindowsAzure.CloudStorageAccount(key, true);
CloudBlobClient client = account.CreateCloudBlobClient();
container = new CloudBlobContainer(containerName, client);
container.CreateIfNotExist();
BlobContainerPermissions permissions = container.GetPermissions();
container.SetPermissions(permissions);
// получение очередей
CloudQueueClient queueClient = account.CreateCloudQueueClient();
queue = queueClient.GetQueueReference(queueName);
importQueue = queueClient.GetQueueReference(importQueueName);
}
// переменные, используемые для хранения текущих сведений о выполняемой работе.
String BlobURL;
String PrimaryAccessKey;
String containerName;
String fileName;
String accountName;
String queueName;
String importQueueName;
CloudBlobContainer container;
CloudQueue queue;
CloudQueue importQueue;
String dataFileName;
String workingPath;
String dtsxFileName;
String queueURL;
Microsoft.SqlServer.Dts.Runtime.Application app;
DTSExecResult result;
bool success;
bool deleteSourceFiles;
String databaseName = @"adventureworks";
String SQLLogin = "sa";
String SQLPwd = "changed";
69
String SQLServerName = @"changed";
Boolean useWindowsAuthentication = true;
string messageText;
}
}
70
Приложение C. Пример процесса импорта
Приведенный в этом приложении код на языке C# служит примером прикладной программы,
импортирующей данные в решении с горизонтальным масштабированием, как описано в разделе
Масштабируемое и гибридное перемещение данных ранее в этой статье. Эта программа опрашивает
очередь импорта, загружает файл данных, создает программными средствами пакет служб SSIS
и импортирует данные в сегменты.
Этот код требует ссылки на Microsoft.SqlServer.DTSPipelineWrap,
Microsoft.SqlServer.DTSRuntimeWrap, Microsoft.SQLServer.ManagedDTS и
Microsoft.WindowsAzure.StorageClient.
using
using
using
using
using
using
using
using
using
using
using
using
System;
System.Collections.Generic;
System.Linq;
System.Text;
Microsoft.WindowsAzure;
Microsoft.WindowsAzure.StorageClient;
Microsoft.WindowsAzure.StorageClient.Protocol;
System.Reflection;
Microsoft.SqlServer.Dts.Runtime;
Microsoft.SqlServer.Dts.Pipeline.Wrapper;
System.Data;
System.Data.SqlClient;
namespace ImportIntoAzure
{
class Program
{
static void Main(string[] args)
{
if (args.Length > 0)
setupConfigs();
// убедитесь, что существуют пути для загрузки файлов и для их распаковки
MakePath();
// инициирование соединения с облаком для загрузки информации:
initializeCloudInfo();
// циклический перебор и обработка файлов:
TimeSpan t = new TimeSpan(1, 0, 0);
while (true)
{
try
{
CloudQueueMessage msg;
msg = queue.GetMessage(t);
while (msg == null)
{
71
System.Threading.Thread.Sleep(5000); // ожидание в течение
5 секунд
msg = queue.GetMessage(t);
}
//
//
//
//
if
{
обработка записи с вероятностью опасности
в данном примере просто удаляем ее.
можно зарегистрировать эту запись в другом месте,
чтобы обработать ее отдельно.
(msg.DequeueCount > 5)
// удаление сообщения о сбое:
queue.DeleteMessage(msg);
Console.WriteLine("Удаление сообщения о
сбое: {0}", msg.AsString);
continue;
}
// выделение названий таблиц и имен файлов.
String[] nameParts = msg.AsString.Split('.');
tableName = "\"" + nameParts[0] + "\".\"" + nameParts[1] + "\"";
dataFileName = msg.AsString;
// загрузка файла:
downloadAndUncab();
// Создание и выполнение пакета:
DateTime begin = DateTime.Now;
Console.WriteLine(
"Начато создание пакета и импорт в {0}", begin);
makeAndExecuteSSISPackage();
// при неуспешном выполнении возникнет исключение,
// если будет превышено максимально допустимое число повторов.
// в этом случае произойдет переход в блок catch
// и удаление сообщения будет пропущено.
DateTime end = DateTime.Now;
Console.WriteLine("Импорт завершен в {0}", end);
Console.WriteLine("Время импорта (сек): {0}",
end.Subtract(begin).TotalSeconds);
// удаление промежуточных файлов, если оно включено:
if (dropIntermediateFiles)
{
deleteIntermediateFiles();
}
// и удаление сообщения:
queue.DeleteMessage(msg);
}
catch (Exception e)
72
{
// здесь добавляется событие журнала
Console.WriteLine(tableName);
Console.WriteLine(e.StackTrace);
Console.WriteLine();
if (dropIntermediateFiles)
deleteIntermediateFiles();
}
}
}
private static void makeAndExecuteSSISPackage()
{
Application app = new Application();
Package p = new Package();
// добавление задачи потока данных:
Executable exec = p.Executables.Add("STOCK:PipelineTask");
TaskHost thMainPipe = exec as TaskHost;
thMainPipe.Name = "MoveDataToAzure";
thMainPipe.MaximumErrorCount = 1000;
MainPipe dataFlowTask =
thMainPipe.InnerObject as MainPipe;
thMainPipe.Properties["DefaultBufferMaxRows"].SetValue(
thMainPipe, getMaxRowsInBuffer(tableName));
thMainPipe.Properties["EngineThreads"].SetValue(
thMainPipe, numShards + 10);
thMainPipe.Properties["IsolationLevel"].SetValue(
thMainPipe, 4096);
thMainPipe.Properties["DefaultBufferSize"].SetValue(
thMainPipe, 10485760 * 5); // этот тест содержит большие объекты
IDTSComponentMetaData100 CompressedFileSource
= dataFlowTask.ComponentMetaDataCollection.New();
CompressedFileSource.ComponentClassID
= app.PipelineComponentInfos["CompressedFileSource"].CreationName;
CManagedComponentWrapper CompressedFileWrapper
= CompressedFileSource.Instantiate();
CompressedFileWrapper.ProvideComponentProperties();
CompressedFileSource.Name = "CompressedFileSource";
// создание диспетчера соединений для условного разбиения
// и получение метаданных, чтобы можно было подключить нисходящий поток:
// настройка диспетчера соединений для CompressedFileSource:
if (CompressedFileSource.RuntimeConnectionCollection.Count < 1)
{
CompressedFileSource.RuntimeConnectionCollection.New();
}
// теперь точно известно, что имеется коллекция соединений.
// Для нее нужен диспетчер соединений:
73
ConnectionManager sourceConn = p.Connections.Add("FILE");
sourceConn.Name = "SourceFile";
sourceConn.ConnectionString = dataFilePath + dataFileName;
sourceConn.Properties["FileUsageType"].SetValue(sourceConn, 0);
CompressedFileSource.RuntimeConnectionCollection[0].ConnectionManager =
DtsConvert.GetExtendedInterface(p.Connections["SourceFile"]);
CompressedFileSource.RuntimeConnectionCollection[0].ConnectionManagerID =
p.Connections["SourceFile"].ID;
// диспетчер соединений должен быть установлен
// теперь повторно инициализируем метаданные
CompressedFileWrapper.AcquireConnections(null);
CompressedFileWrapper.ReinitializeMetaData();
CompressedFileWrapper.ReleaseConnections();
// Теперь добавим условное разбиение для сегментирования:
IDTSComponentMetaData100 conditionalSplit
= dataFlowTask.ComponentMetaDataCollection.New();
conditionalSplit.ComponentClassID
= app.PipelineComponentInfos["Conditional Split"].CreationName;
CManagedComponentWrapper conditionalSplitWrapper
= conditionalSplit.Instantiate();
conditionalSplitWrapper.ProvideComponentProperties();
conditionalSplit.Name = "Разделитель сегмента";
// присоединение входа к условному разбиению:
IDTSPath100 path = dataFlowTask.PathCollection.New();
path.AttachPathAndPropagateNotifications(
CompressedFileSource.OutputCollection[0],
conditionalSplit.InputCollection[0]);
// установка свойств столбца, который будет использоваться в выражениях:
IDTSInput100 splitInput = conditionalSplit.InputCollection[0];
IDTSInputColumnCollection100 splitInputColumns
= splitInput.InputColumnCollection;
IDTSVirtualInput100 splitVirtualInput
= splitInput.GetVirtualInput();
IDTSVirtualInputColumnCollection100 splitVirtualInputColumns
= splitVirtualInput.VirtualInputColumnCollection;
conditionalSplitWrapper.SetUsageType(
splitInput.ID, splitVirtualInput,
splitVirtualInputColumns[0].LineageID,
DTSUsageType.UT_READONLY);
// создание условий для разбиения
// они предназначены только для демонстрации, так как
// просто используется остаток от деления.
74
IDTSOutputColumn100 conditionalColumn
= CompressedFileSource.OutputCollection[0].OutputColumnCollection[0];
String conditionalExpression = "";
if (conditionalColumn.DataType
== Microsoft.SqlServer.Dts.Runtime.Wrapper.DataType.DT_WSTR
|| conditionalColumn.DataType
== Microsoft.SqlServer.Dts.Runtime.Wrapper.DataType.DT_STR
|| conditionalColumn.DataType
== Microsoft.SqlServer.Dts.Runtime.Wrapper.DataType.DT_NTEXT)
conditionalExpression +=
"CODEPOINT ([" + conditionalColumn.Name + "]) % "
+ numShards.ToString();
else
conditionalExpression += "[" +
conditionalColumn.Name + "] % " + numShards.ToString();
// теперь метаданные заданы,
// пора добавить целевой адаптер соединения:
for (int i = 0; i < numShards; i++)
{
String shardName
= String.Format("Shard{0}", i.ToString().PadLeft(2, '0'));
IDTSOutput100 splitOutput
= conditionalSplitWrapper.InsertOutput(
DTSInsertPlacement.IP_BEFORE,
conditionalSplit.OutputCollection[0].ID);
splitOutput.Name = String.Format(shardName);
splitOutput.Description = "Вывод в сегмент";
splitOutput.IsErrorOut = false;
conditionalSplitWrapper.SetOutputProperty(splitOutput.ID,
"EvaluationOrder", i);
conditionalSplitWrapper.SetOutputProperty(splitOutput.ID,
"FriendlyExpression", String.Format("ABS({0}) == {1}",
conditionalExpression, i));
// добавляет диспетчер соединений, в который будут направляться
// данные подключение условного вывода:
ConnectionManager SQLAzureConnection = p.Connections.Add(
"ADO.NET:SQL");
SQLAzureConnection.ConnectionString = getConnectionString(i);
SQLAzureConnection.Description = String.Format(
"Подключение к {0}", shardName);
SQLAzureConnection.Name = String.Format(
"Соединение {0}", shardName);
// теперь создадим адаптер загрузки данных:
IDTSComponentMetaData100 ADODestination
= dataFlowTask.ComponentMetaDataCollection.New();
ADODestination.ComponentClassID
75
= app.PipelineComponentInfos["Назначение ADO NET"].CreationName;
ADODestination.ValidateExternalMetadata = true;
CManagedComponentWrapper destWrapper
= ADODestination.Instantiate();
destWrapper.ProvideComponentProperties();
// задание имени ПОСЛЕ вызова ProvideComponentProperties
// так как этот метод сбрасывает имя
ADODestination.Name = String.Format(
"Назначение ADO.NET для сегмента
{0}", i.ToString().PadLeft(2, '0'));
destWrapper.SetComponentProperty("TableOrViewName", tableName);
destWrapper.SetComponentProperty("BatchSize", 500);
if (ADODestination.RuntimeConnectionCollection.Count > 0)
{
// задать указанный выше диспетчер соединений
ADODestination.RuntimeConnectionCollection[0].ConnectionManager =
DtsConvert.GetExtendedInterface(SQLAzureConnection);
ADODestination.RuntimeConnectionCollection[0
].ConnectionManagerID =
SQLAzureConnection.ID;
}
// подключение источника и назначения:
destWrapper.AcquireConnections(null);
destWrapper.ReinitializeMetaData();
destWrapper.ReleaseConnections();
path = dataFlowTask.PathCollection.New();
// подключение вывода этого сегмента к новому назначению:
path.AttachPathAndPropagateNotifications(
conditionalSplit.OutputCollection[shardName],
ADODestination.InputCollection["Ввод назначения ADO NET"]);
IDTSVirtualInput100 virtualInput
= ADODestination.InputCollection["Ввод назначения ADO NET"
].GetVirtualInput();
IDTSInput100 destInput
= ADODestination.InputCollection["Ввод назначения ADO NET"];
IDTSInputColumnCollection100 destInputCols
= destInput.InputColumnCollection;
IDTSExternalMetadataColumnCollection100 destExtCols
= destInput.ExternalMetadataColumnCollection;
IDTSOutputColumnCollection100 sourceColumns
= CompressedFileSource.OutputCollection[0].OutputColumnCollection
;
// Сопоставление столбцов
76
foreach (IDTSOutputColumn100 outputCol in sourceColumns)
{
IDTSExternalMetadataColumn100 extCol
= (IDTSExternalMetadataColumn100)destExtCols[outputCol.Name];
if (extCol != null)
{
virtualInput.SetUsageType(
outputCol.ID, DTSUsageType.UT_READONLY);
IDTSInputColumn100 inputCol
= destInputCols.GetInputColumnByLineageID(outputCol.ID);
if (inputCol != null)
{
//сопоставление входного столбца с внешним столбцом метаданных
destWrapper.MapInputColumn(
destInput.ID, inputCol.ID, extCol.ID);
}
}
}
// наконец, настроим на выводе перенаправление строк ошибок:
ADODestination.InputCollection[0].ErrorOrTruncationOperation
= "Возможное нарушение первичного ключа при повторе";
ADODestination.InputCollection[0].ErrorRowDisposition
= DTSRowDisposition.RD_RedirectRow;
ADODestination.Validate();
}
// выполнение пакета:
string localFileName = dataFilePath + @"importPackages\"
+ tableName.Replace("\"", "") + ".dtsx";
Console.WriteLine("Импорт {0}", dataFileName);
results = p.Execute();
// Можно добавить код для получения полных сообщений журнала
// из выполняемого пакета.
if (saveLocalPackages)
app.SaveToXml(localFileName, p, null);
Console.WriteLine(results.ToString());
// добавление кода для повторной обработки ошибок.
int retries = 1;
int maxRetries = 5;
while ((p.ExecutionResult == DTSExecResult.Failure
|| p.ExecutionStatus == DTSExecStatus.Abend) && retries++ < maxRetrie
s)
{
Console.WriteLine("Ошибка при выполнении импорта ", dataFileName);
Console.WriteLine();
ErrorEnumerator en = p.Errors.GetEnumerator();
while ((en.MoveNext()) && en.Current != null)
Console.WriteLine(en.Current.Description);
results = p.Execute();
// вызвать исключение, если не удалось получить данные
// за максимальное количество попыток
if ((p.ExecutionResult == DTSExecResult.Failure
|| p.ExecutionStatus == DTSExecStatus.Abend)
77
&& retries>= maxRetries)
throw new Exception (
String.Format(
"Не удалось обработать файл {0}, число попыток: {1}.",
fileName, maxRetries));
}
}
private static String getConnectionString(int shardNo)
{
String curServer;
if (shardNo > 149)
curServer = serverName4;
else if (shardNo > 99)
curServer = serverName3;
else if (shardNo > 49)
curServer = serverName2;
else
curServer = serverName;
//String Retval = String.Format("Data Source=
String lDbName = "Shard";
if (shardNo < 10)
lDbName += "0";
lDbName += shardNo.ToString();
//Console.WriteLine(lDbName);
return String.Format(
"Data Source={0};Initial Catalog={1};User ID={2}@{0};" +
"Password={3}; Connect Timeout=60;"
, curServer, lDbName, dbAccountName, accountPwd);
}
private static void MakePath()
{
if (!System.IO.Directory.Exists(dataFilePath))
System.IO.Directory.CreateDirectory(dataFilePath);
if (!System.IO.Directory.Exists(downloadPath))
System.IO.Directory.CreateDirectory(downloadPath);
// убедимся, что символы окончания путей к каталогам заданы правильно:
if (!dataFilePath.Trim().EndsWith(@"\"))
dataFilePath = dataFilePath.Trim() + @"\";
if (!downloadPath.Trim().EndsWith(@"\"))
downloadPath = downloadPath.Trim() + @"\";
}
// загрузка файла:
private static void downloadAndUncab()
{
CloudBlob blob = container.GetBlobReference(dataFileName);
//String lBlobFileName = downloadPath + fileName;
String lDataFileName = dataFilePath + dataFileName;
int iteration = 0;
bool success = false;
int maxIterations = 5;
DateTime begin = DateTime.Now;
78
Console.WriteLine("Начало загрузки в {0}", begin);
while (!success && iteration++ < maxIterations)
{
try
{
blob.DownloadToFile(lDataFileName);
// если мы дошли сюда, то она выполнена успешно
success = true;
}
catch (Exception e)
{
if (iteration < maxIterations)
{
Console.WriteLine("Возникло исключение при загрузке " +
"{0}.Итерация {1} из максимального количества: {2}."
, fileName, iteration, maxIterations);
Console.WriteLine("Сообщение об ошибке: {0}", e.Message);
Console.WriteLine("Повтор... \n\n");
}
else
{
Console.WriteLine("Возникло исключение при загрузке " +
"{0}. Итерация {1} из максимального количества: {2}."
, fileName, iteration, maxIterations);
Console.WriteLine("Сообщение об ошибке: {0}", e.Message);
Console.WriteLine("Количество повторов исчерпано... \n\n");
//вызов исключения повторно:
throw e;
}
}
}
DateTime end = DateTime.Now;
Console.WriteLine("Загрузка завершилась в {0}", end);
Console.WriteLine("Время загрузки (сек): {0}",
end.Subtract(begin).TotalSeconds);
}
private static void initializeCloudInfo()
{
// сначала просто установим соединение с учетной записью Azure:
key = new Microsoft.WindowsAzure.StorageCredentialsAccountAndKey(
accountName, primaryAccessKey);
account = new Microsoft.WindowsAzure.CloudStorageAccount(key, true);
// получение клиента и контейнера больших двоичных объектов:
client = account.CreateCloudBlobClient();
client.Timeout = new TimeSpan(1, 0, 0);
container = new CloudBlobContainer(blobContainerName, client);
container.CreateIfNotExist();
BlobContainerPermissions permissions = container.GetPermissions();
container.SetPermissions(permissions);
// настройка для работы с очередью
79
queueClient = account.CreateCloudQueueClient();
queue = queueClient.GetQueueReference(queueName);
queue.CreateIfNotExist();
}
private static int getMaxRowsInBuffer(String tabName)
{
if (tabName == "\"Production\".\"ProductPhoto\""
|| tabName == "\"Production\".\"ProductPhoto1\"")
return 10;
if (tabName == "\"Sales\".\"Individual\""
|| tabName == "\"Sales\".\"Individual1\"")
return 20;
if (tabName == "\"Production\".\"TransactionHistory\""
|| tabName == "\"Production\".\"TransactionHistory1\"")
return 10000;
// вариант по умолчанию — 10 000 в целях тестирования
return 10000;
}
private static void deleteIntermediateFiles()
{
Uri filePath = new Uri(downloadPath);
System.IO.DirectoryInfo dldi = new System.IO.DirectoryInfo(
filePath.AbsolutePath);
filePath = new Uri(dataFilePath);
System.IO.DirectoryInfo dfdi = new System.IO.DirectoryInfo(
filePath.AbsolutePath);
foreach (System.IO.FileInfo fi in
dldi.GetFiles(tableName.Replace("\"", "") + "*.*"))
{
fi.Delete();
}
foreach (System.IO.FileInfo fi in
dfdi.GetFiles(tableName.Replace("\"", "") + "*.*"))
{
fi.Delete();
}
}
private static void setupConfigs()
{
/*
* -d — это файл данных и путь загрузки
* -s — это количество сегментов
* */
String[] configs = Environment.GetCommandLineArgs();
for (int i = 0; i < configs.Length; i++)
Console.WriteLine("parameter {0} is {1}", i, configs[i]);
for (int i = 1; i < configs.Length; i += 2)
{
80
if (!configs[i].StartsWith(@"-") && !configs[i].StartsWith(@"/"))
{
IncorrectArgs();
}
if (!(i + 1 < configs.Length))
IncorrectArgs();
if (!(configs[i].Substring(1, 1).Equals(
"d", StringComparison.Ordinal)
|| configs[i].Substring(1, 1).Equals(
"s", StringComparison.Ordinal)
))
IncorrectArgs();
else if (configs[i].Trim().EndsWith("d", StringComparison.Ordinal))
dataFilePath = configs[i + 1].Trim();
else if (configs[i].Trim().EndsWith("s", StringComparison.Ordinal))
if (!Int32.TryParse(configs[i + 1].Trim(), out numShards))
IncorrectArgs();
}
}
private static void IncorrectArgs()
{
String progname = Environment.CommandLine.Split(' ')[0];
Console.WriteLine("Пример использования: {0} -d datafilePath s 20", progname);
Environment.Exit(1);
}
/*
* Переменные класса
* Создание для запуска в качестве статического приложения
* */
// в идеальном случае вместо помещения значений в код
// они должны храниться во внешнем ресурсе, чтобы приложение
// могло использовать их повторно без написания дополнительного кода.
// В этом приложении они размещаются только в коде.
private static String dataFilePath = @"c:\MigrationPOC\";
private static String downloadPath = @"c:\MigrationPOC\";
private static String serverName = "changed.database.windows.net";
private static String serverName2 = "changed2.database.windows.net";
private static String serverName3 = "changed3.database.windows.net";
private static String serverName4 = "changed4.database.windows.net";
private static String dbName = @"adventureworks";
private static String BlobURL = @"changed.blob.core.windows.net";
private static String primaryAccessKey = "changed";
private static String blobContainerName = @"exports";
private static String accountName = "changed";
private static String dbAccountName = "changed";
private static String accountPwd = "changed";
private static String queueName = "import";
private static bool saveLocalPackages = false;
// другие переменные, используемые в программе
private static Application app;
81
private
private
private
private
private
private
private
private
private
private
private
private
private
private
}
}
82
static
static
static
static
static
static
static
static
static
static
static
static
static
static
String fileName;
String tableName;
String dataFileName;
Microsoft.WindowsAzure.StorageCredentialsAccountAndKey key;
bool deleteFilesFromBlob = true;
bool dropIntermediateFiles = true;
CloudStorageAccount account;
CloudBlobClient client;
CloudBlobContainer container;
CloudQueueClient queueClient;
CloudQueue queue;
DTSExecResult results;
int numShards = 10;
string workingDir;
Приложение D. Образец пакета шаблона
В этом приложении приведены основные параметры и компоненты потока данных для пакета
шаблона, используемого кодом, который приведен в приложении A. Обратите внимание, что
использование пакета шаблона абсолютно необязательно, так как пакет может формироваться
в памяти без использования шаблонов. Шаблон использовался в демонстрационных целях.
Дополнительный совет: параметры пакета — хорошее место для хранения дополнительных
сведений для последующих процессов без использования таблиц Azure, файлов или других
внешних источников данных.
Параметры, используемые в пакете шаблона. Они используются для создания строк подключения в диспетчерах
соединений и настройке для выполнения пакета. С помощью этого шаблона их можно изменять в программе
перед сохранением выполняемой версии.
Поток данных пакета шаблона.
83
Download