Таблица Windows Azure

advertisement
ТАБЛИЦА WINDOWS AZURE
Джей Харидас, Ниранджан Нилакантан и Брэд Калдер
Май, 2009 г.
Содержание
1
2
3
Введение ................................................................................................................................. 2
Модель таблицы данных ....................................................................................................... 3
Секционирование таблиц ...................................................................................................... 6
3.1 Влияние секционирования ............................................................................................ 6
3.1.1
3.1.2
3.1.3
3.2
Выбор ключа секции ...................................................................................................... 7
3.2.1
3.2.2
3.2.3
3.2.4
4
6
7
8
Транзакции групп объектов................................................................................................ 7
Эффективные запросы ........................................................................................................ 8
Масштабируемость ............................................................................................................. 8
Гибкое секционирование ................................................................................................... 9
Программирование таблиц ................................................................................................... 9
4.1 Управление версиями .................................................................................................. 11
4.2 Пример........................................................................................................................... 12
4.3 Определение класса объекта для таблицы ................................................................. 12
4.4 Создание таблицы ........................................................................................................ 13
4.5 Вставка блога ................................................................................................................ 13
4.6 Запрос блогов................................................................................................................ 14
4.7 Обновление блога......................................................................................................... 14
4.8 Удаление блога ............................................................................................................. 15
4.9 Транзакции групп объектов ........................................................................................ 15
4.9.1
4.9.2
5
Масштабируемость таблицы.............................................................................................. 6
Транзакции групп объектов................................................................................................ 7
Расположение объектов ..................................................................................................... 7
Обработка ответа .............................................................................................................. 17
Ошибки ............................................................................................................................... 18
4.10
Рекомендации по работе с DataServiceContext ...................................................... 20
4.11
Использование API REST ........................................................................................ 21
Параллельные обновления.................................................................................................. 21
5.1 Безусловные обновления ............................................................................................. 23
Разбиение результатов запроса на страницы .................................................................... 23
6.1 Получение первых N объектов ................................................................................... 23
6.2 Маркеры продолжения ................................................................................................ 24
Модель согласованности .................................................................................................... 25
7.1 Согласованность в рамках одной таблицы ................................................................ 25
7.2 Согласованность множества таблиц .......................................................................... 25
Советы и рекомендации ...................................................................................................... 26
8.1 Извлечение элементов, добавленных последними (моделирование расположения
элементов в порядке по убыванию) ...................................................................................... 26
1
8.2
8.3
Извлечение с использованием префикса ................................................................... 28
Пример секционирования данных .............................................................................. 28
8.3.1
8.3.2
8.3.3
8.4
Использование микроблоггинга ...................................................................................... 28
Динамическая настройка детализации PartitionKey ...................................................... 31
Разные виды объектов в одной таблице ........................................................................ 32
Обновление версий и управление ими ....................................................................... 37
8.4.1
8.4.2
8.4.3
Добавление нового свойства ........................................................................................... 37
Удаление типа свойства.................................................................................................... 38
Изменение типа свойства ................................................................................................. 39
Рекомендации по использованию таблицы Windows Azure ........................................... 39
9.1 Создание таблицы ........................................................................................................ 39
9.2 Асинхронная версия API служб обработки данных ADO.NET ............................... 39
9.3 Настройки DataServiceContext .................................................................................... 39
9.4 Схема секционирования .............................................................................................. 40
9.5 Безусловные обновления и удаления ......................................................................... 40
9.6 Обработка ошибок ....................................................................................................... 40
9
9.6.1
Ошибки сети и истечение времени ожидания при выполнении операций на стороне
сервера 40
9.6.2
Время ожидания повторных запросов и ошибки «Connection closed by Host»
(соединение прервано узлом) ......................................................................................................... 41
9.6.3
Конфликты при обновлениях ........................................................................................... 41
9.6.4
Настройка приложения для обработки повторяющихся ошибок превышения
времени ожидания ........................................................................................................................... 41
9.6.5
Обработка ошибок и составление отчетов ..................................................................... 41
9.7
Настройка производительности .NET и ADO.NET .................................................. 42
9.7.1
Улучшение производительности десериализации при использовании служб
обработки данных ADO.NET. ............................................................................................................ 42
9.7.2
Устанока значения по умолчанию «2» для HTTP-соединений .NET ............................. 42
9.7.3
Отключение «100- continue» ............................................................................................ 43
9.7.4
Отключение Nagle может ускорить операции вставки и обновления ......................... 44
9.8
10
Повторное использование имени удаленной таблицы............................................ 44
Резюме................................................................................Error! Bookmark not defined.
1 Введение
Windows Azure является основой облачной платформы Microsoft. Эта «облачная операционная
система» предоставляет разработчикам приложений основные технологические компоненты
для создания масштабируемых служб высокой доступности:




ВИРТУАЛИЗИРОВАННЫЕ ВЫЧИСЛЕНИЯ ;
МАСШТАБИРУЕМОЕ ХРАНИЛИЩЕ ;
АВТОМАТИЗИРОВАННОЕ УПРАВЛЕНИЕ ;
ОБШИРНЫЙ ПАКЕТ СРЕДСТВ РАЗРАБОТКИ .
2
Хранилище Windows Azure позволяет разработчикам приложений хранить данные в облаке.
Приложение получает доступ к данным в любой момент из любой точки и может хранить любой
объем данных в течение неограниченного времени. При этом гарантируется, что данные не будут
повреждены или утеряны. Хранилище Windows Azure поддерживает широкий набор абстракций
данных:



СЛУЖБА БОЛЬШИХ ДВОИЧНЫХ ОБЪЕКТОВ WINDOWS AZURE (WINDOWS AZURE BLOB) — ОБЕСПЕЧИВАЕТ
ХРАНЕНИЕ БОЛЬШИХ ЭЛЕМЕНТОВ ДАННЫХ .
ТАБЛИЦА WINDOWS AZURE (WINDOWS AZURE TABLE) — ПРЕДОСТАВЛЯЕТ СТРУКТУРИРОВАННОЕ ХРАНИЛИЩЕ
ДЛЯ ПОДДЕРЖКИ СОСТОЯНИЯ СЛУЖБЫ.
ОЧЕРЕДЬ WINDOWS AZURE (WINDOWS AZURE QUEUE) — ОБЕСПЕЧИВАЕТ ДИСПЕТЧЕРИЗАЦИЮ АСИНХРОННЫХ
ЗАДАНИЙ И ВЗАИМОДЕЙСТВИЕ МЕЖДУ СЛУЖБАМИ .
В этом документе описана таблица Windows Azure — структурированное хранилище на платформе
Windows Azure. Хранилище поддерживает в облачной среде высокомасштабируемые таблицы,
которые могут содержать миллиарды объектов и терабайты данных. По мере увеличения трафика
система эффективно масштабируется, автоматически подключая тысячи серверов.
Структурированное хранилище предоставляется в виде таблиц, содержащих ряд объектов
с набором именованных свойств. Ниже приведены основные характеристики таблицы
Windows Azure:
 поддержка для LINQ, ADO .NET Data Services и REST;
 проверка типов во время компиляции при использовании клиентской библиотеки службы
обработки данных ADO .NET;
 широкий набор типов данных для значений свойств;
 поддержка неограниченного количества таблиц и объектов без ограничения размеров
таблицы;
 строгая согласованность для каждой транзакции объекта;
 оптимистичный параллелизм при обновлениях и удалениях.
 для запросов, возвращающих большое количество результатов или прерванных
по завершении времени ожидания, возвращаются частичные результаты
с дополнительным маркером, которые позволяют возобновить запрос с момента
прерывания.
2 Модель таблицы данных
Ниже приведен обзор модели данных для таблицы Windows Azure.

УЧЕТНАЯ ЗАПИСЬ ХРАНИЛИЩА . ЧТОБЫ ПОЛУЧИТЬ ДОСТУП К ХРАНИЛИЩУ WINDOWS AZURE , ПРИЛОЖЕНИЕ
ДОЛЖНО ИСПОЛЬЗОВАТЬ ДЕЙСТВИТЕЛЬНУЮ УЧЕТНУЮ ЗАПИСЬ . НОВУЮ УЧЕТНУЮ ЗАПИСЬ МОЖНО СОЗДАТЬ ПРИ
ПОМОЩИ ВЕБ -ИНТЕРФЕЙСА ПОРТАЛА WINDOWS AZURE . ПОСЛЕ СОЗДАНИЯ УЧЕТНОЙ ЗАПИСИ ПОЛЬЗОВАТЕЛЬ
ПОЛУЧАЕТ 256-РАЗРЯДНЫЙ СЕКРЕТНЫЙ КЛЮЧ . Э ТОТ КЛЮЧ ПРЕДНАЗНАЧЕН ДЛЯ ПРОВЕРКИ ПОДЛИННОСТИ
ПОЛЬЗОВАТЕЛЕЙ И АВТОРИЗАЦИИ ЗАПРОСОВ К СИСТЕМЕ ХРАНЕНИЯ. С ПОМОЩЬЮ ЭТОГО СЕКРЕТНОГО КЛЮЧА
3
СОЗДАЕТСЯ ПОДПИСЬ
HMAC SHA256 ДЛЯ ЗАПРОСА . ЭТА ПОДПИСЬ ПЕРЕДАЕТСЯ С КАЖДЫМ ЗАПРОСОМ
ПРОВЕРКИ ПОДЛИННОСТИ ЗАПРОСОВ ПОЛЬЗОВАТЕЛЯ .








ИМЯ УЧЕТНОЙ ЗАПИСИ ЯВЛЯЕТСЯ ЧАСТЬЮ ИМЕНИ УЗЛА В URL.. ДЛЯ ДОСТУПА К ТАБЛИЦАМ ИСПОЛЬЗУЕТСЯ
СЛЕДУЮЩИЙ ФОРМАТ ИМЕНИ УЗЛА : <ИМЯ УЧЕТНОЙ ЗАПИСИ >.TABLE .CORE .WINDOWS .NET.
ТАБЛИЦА СОДЕРЖИТ НАБОР ОБЪЕКТОВ . ИМЕНА ТАБЛИЦ ОГРАНИЧИВАЮТСЯ УЧЕТНОЙ ЗАПИСЬЮ. ПРИЛОЖЕНИЕ
МОЖЕТ СОЗДАВАТЬ МНОЖЕСТВО ТАБЛИЦ В РАМКАХ УЧЕТНОЙ ЗАПИСИ ХРАНИЛИЩА .
Объект (строка). Объекты — это основные элементы данных, хранящиеся в таблице.
Объект является аналогом «строки» и имеет набор свойств. Каждая таблица имеет
два свойства, а именно PartitionKey (ключ секции) и RowKey (ключ строки), которые
образуют уникальный ключ объекта.
Свойство (столбец) представляет собой единое значение объекта. Имена свойств
чувствительны к регистру. Поддерживается широкий набор типов для значений свойств.
PartitionKey (ключ секции) — это первое свойство ключа каждой таблицы. Система
использует этот ключ для автоматического распределения объектов таблицы
по множеству узлов хранения.
RowKey (ключ строки) — это второе свойство ключа таблицы, уникальный идентификатор
объекта в секции, которой он принадлежит. PartitionKey в сочетании с RowKey однозначно
идентифицирует объект в таблице.
Timestamp (временная метка). Каждый объект имеет версию, которая поддерживается
системой.
Секция — это набор объектов в таблице с одинаковым значением ключа секции.
Порядок сортировки. Для CTP-версии предоставляется единый индекс, в котором
все объекты сортируются по свойству PartitionKey, а затем по RowKey. Запросы
с указанием этих ключей более эффективны; все возвращаемые результаты будут
отсортированы по свойству PartitionKey, а затем по свойству RowKey.
Таблица построена на основе гибкой схемы. Таблица Windows Azure отслеживает имя
и типизированное значение для каждого свойства каждого объекта. Приложение может
моделировать фиксированную схему на стороне клиента, обеспечивая одинаковый набор свойств
для всех создаваемых объектов.
Ниже приведены дополнительные сведения о таблицах, объектах и свойствах.


ТАБЛИЦА
o
Имена таблиц могут содержать только алфавитно-цифровые символы.
o
Имя таблицы не может начинаться с цифрового символа.
o
Имена таблиц не чувствительны к регистру.
o
Имена таблиц должны содержать от 3 до 63 символов.
ИМЯ СВОЙСТВА
o

Допускаются только алфавитно-цифровые символы и '_'.
ОБЪЕКТ МОЖЕТ ИМЕТЬ НЕ БОЛЕЕ 255 СВОЙСТВ , ВКЛЮЧАЯ ОБЯЗАТЕЛЬНЫЕ СИСТЕМНЫЕ СВОЙСТВА : PARTITIONKEY,
ROWKEY И TIMESTAMP . ИМЕНА ВСЕХ ОСТАЛЬНЫХ СВОЙСТВ ОБЪЕКТА ОПРЕДЕЛЯЮТСЯ ПРИЛОЖЕНИЕМ .
4





PARTITIONKEY И ROWKEY ЯВЛЯЮТСЯ СВОЙСТВАМИ СТРОКОВОГО ТИПА ; ИХ РАЗМЕР НЕ ДОЛЖЕН ПРЕВЫШАТЬ 1 КБ.
TIMESTAMP — ЭТО ОБСЛУЖИВАЕМОЕ СИСТЕМОЙ СВОЙСТВО , ДОСТУПНОЕ ТОЛЬКО ДЛЯ ЧТЕНИЯ; ОНО ДОЛЖНО
РАССМАТРИВАТЬСЯ КАК НЕПРОЗРАЧНОЕ СВОЙСТВО.
ОТСУТСТВИЕ ФИКСИРОВАННОЙ СХЕМЫ. ТАБЛИЦА WINDOWS AZURE НЕ СОХРАНЯЕТ СХЕМЫ, ПОЭТОМУ ВСЕ
СВОЙСТВА ХРАНЯТСЯ КАК ПАРЫ <ИМЯ, ТИПИЗИРОВАННОЕ ЗНАЧЕНИЕ >. Э ТО ОЗНАЧАЕТ , ЧТО ДВА ОБЪЕКТА ОДНОЙ
ТАБЛИЦЫ МОГУТ ИМЕТЬ СВОЙСТВА , КОТОРЫЕ СИЛЬНО РАЗЛИЧАЮТСЯ. ТАБЛИЦА МОЖЕТ СОДЕРЖАТЬ ДВА
ОБЪЕКТА С ОДИНАКОВЫМИ ИМЕНАМИ СВОЙСТВ , НО РАЗНЫМИ ТИПАМИ ЗНАЧЕНИЙ . ОДНАКО ИМЕНА СВОЙСТВ В
РАМКАХ ОДНОГО ОБЪЕКТА ДОЛЖНЫ БЫТЬ УНИКАЛЬНЫМИ .
СУММАРНЫЙ ОБЪЕМ ВСЕХ ДАННЫХ ОБЪЕКТА НЕ МОЖЕТ ПРЕВЫШАТЬ 1 МБ. В ЭТОТ ОБЪЕМ ВХОДИТ РАЗМЕР
ИМЕН СВОЙСТВ , А ТАКЖЕ РАЗМЕР ЗНАЧЕНИЙ СВОЙСТВ ИЛИ ИХ ТИПОВ , КОТОРЫЙ ВКЛЮЧАЕТ В СЕБЯ ДВА
ОБЯЗАТЕЛЬНЫХ СВОЙСТВА КЛЮЧЕЙ (PARTITION KEY И R OW KEY ).
ПОДДЕРЖИВАЮТСЯ СЛЕДУЮЩИЕ ТИПЫ СВОЙСТВ : BINARY (БИНАРНЫЙ ), BOOL (ЛОГИЧЕСКИЙ ), D ATETIME (ДАТА И
ВРЕМЯ), DOUBLE ( ДВОЙНОЙ ), GUID, INT ( ЦЕЛОЧИСЛЕННЫЙ ), I NT64 ( ЦЕЛОЧИСЛЕННЫЙ 64-РАЗРЯДНЫЙ ), STRING
(СТРОКОВЫЙ ). ОГРАНИЧЕНИЯ ПРЕДСТАВЛЕНЫ В ТАБЛИЦЕ .
Для объекта Http.sys используются ограничения по умолчанию, согласно которым размер
сегмента URI не может превышать 260 символов. Это приводит к ограничению размера секции
и ключа строки, поскольку для команд GetRow («Получить строку»), Delete («Удалить»), Update
(«Обновить») и Merge («Объединить») ключи секции и строки определяются как часть одного
сегмента URI. Например, следующий URI определяет один объект со свойствами PartitionKey «pk»
и RowKey «rk»:
http://myaccount.windows.core.net/Customers(PartitionKey="pk",RowKey="rk"). Согласно
ограничению для Http.sys, выделенная часть не может превышать 260 символов. Для решения
этой проблемы операции, имеющие такое ограничение, могут выполняться с помощью
транзакции групп объектов, поскольку в транзакции групп объектов URI, определяющий ресурс,
является частью тела запроса (см. раздел 4.9 для получения дополнительной информации).
Тип свойства
Подробная информация
Двоичный
Логический
Дата и время
Массив байтов размером до 64 КБ
Логическое значение
64-разрядное значение, представляющее время в формате UTC.
Поддерживаемый диапазон значений от 1/1/1601 до 12/31/9999
64-разрядное значение с плавающей точкой
128-разрядный глобальный уникальный идентификатор
32-разрядное целое значение
64-разрядное целое значение
Двойной
GUID
Целочисленный
Целочисленный
64-разрядный
Строковый
16-разрядное значение с кодировкой UTF. Максимальный размер
строковых значений — 64 КБ
5
3 Секционирование таблиц
Таблица Windows Azure обеспечивает масштабирование таблиц до тысяч узлов хранения,
распределяя объекты в таблице. При распределении объектов желательно убедиться, что набор
объектов, входящих в одно множество, располагается в одном узле хранения. Приложение
контролирует этот набор, выбирая определенное значение для свойства PartitionKey каждого
объекта.
Для достижения желаемых результатов приложения должны анализировать рабочую нагрузку
секции и моделировать максимальную рабочую нагрузку.
PartitionKey
RowKey
Свойство 3
…..
Свойство N
Имя
документа
Версия
Время
изменения
Examples Doc
V.1.0
02.08.2007
Examples Doc
V.2.0.1
28.09.2007
Рабочая версия Элис
FAQ Doc
V.1.0
02.05.2007
Фиксированная версия
FAQ Doc
V.1.0.1
06.07.2007
Рабочая версия Элис
FAQ Doc
V.1.0.2
01.08.2007
Рабочая версия Салли
Описание
…..
Фиксированная версия
Секция 1
Секция 2
Рис. 1. Пример секций
На приведенном выше рисунке показана таблица, содержащая множество версий документов.
Каждый объект этой таблицы соответствует определенной версии конкретного документа. В этом
примере ключом секции таблицы является имя документа, а ключом строки — строка версии.
Имя и версия документа однозначно идентифицируют определенный объект таблицы. В этом
примере все версии одного документа образуют отдельную секцию.
3.1 Влияние секционирования
Рассмотрим назначение секций таблицы и принципы выбора ключа секции.
3.1.1 Масштабируемость таблицы
Система хранения данных обеспечивает хорошую масштабируемость за счет распределения
секций среди множества узлов хранения.
Система отслеживает характер использования секций и автоматически распределяет эти секции
по всем узлам хранения. Это позволяет системе и приложению масштабироваться соответственно
количеству запросов к таблице. Если некоторые секции запрашиваются чаще других, система
автоматически разместит их на нескольких узлах хранения и распределит трафик между
несколькими серверами. Однако секция, то есть все объекты с одинаковым ключом секции, будет
6
обслуживаться одним узлом. Объем данных в рамках секции не ограничен емкостью хранилища
одного узла хранения.
3.1.2 Транзакции групп объектов
Приложение может автоматически выполнять транзакции для объектов, хранящихся в одной
таблице и одной секции (то есть имеющих одинаковое значение ключа секции). Это позволяет
приложению автоматически выполнять несколько операций Create/Update/Delete для множества
объектов в одном пакетном запросе к системе хранения данных, так как все объекты имеют
одинаковое значение ключа секции и находятся в одной таблице. При выполнении транзакции
обеспечивается изоляция моментального снимка независимо от того, были ли все операции
выполнены успешно или дали сбой. Все остальные запросы, выполняющиеся параллельно
в это же время, не будут видеть результат транзакции, поскольку работают с моментальным
снимком, сделанным до ее завершения. Результат транзакции становится видимым для запросов
только после ее полного успешного завершения.
Для транзакции групп объектов указание заголовка версии является обязательным; должна быть
указана версия «2009-04-14» или более поздняя. Дополнительные сведения см. в разделе 4.1.
3.1.3 Расположение объектов
Объекты одной секции хранятся вместе. Это обеспечивает наиболее эффективную обработку
запросов внутри секции. Благодаря такому расположению данных в секции приложение может
использовать все преимущества эффективного кэширования и других способов оптимизации
производительности.
В приведенном выше примере секцию образуют все версии одного документа. Таким образом,
доступ к одной секции позволяет эффективно извлечь «все версии данного документа». С другой
стороны, чтобы получить «все версии документов, измененные до 30.05.2007», нужно отправлять
запрос в несколько секций. По такому запросу придется проверять все секции, которые могут
находиться в разных узлах хранения. Это более дорогостоящая процедура.
3.2 Выбор ключа секции
Выбор ключа секции важен для эффективного масштабирования приложения. Необходимо найти
компромисс между размещением объектов в одной секции (обеспечивающим большую
эффективность запросов) и масштабируемостью таблицы (чем больше секций в таблице,
тем проще для таблицы Windows Azure распределить нагрузку между множеством серверов).
3.2.1 Транзакции групп объектов
Если приложение использует транзакции групп объектов, то свойство PartitionKey нужно выбирать
так, чтобы оно охватывало все объекты, участвующие в атомарной транзакции. Необходимо
выбирать такое свойство PartitionKey, которое группирует объекты для выполнения транзакций
над группами объектов. Так группируются объекты, которые должны обрабатываться атомарным
7
образом. При этом создается множество секций, что позволяет таблице Windows Azure
распределять нагрузку между несколькими серверами в соответствии с трафиком таблицы.
3.2.2 Эффективные запросы
Для наиболее частых и критичных по времени ожидания запросов необходимо использовать
свойство PartitionKey как условие фильтра запроса. Использование свойства PartitionKey в фильтре
запроса ограничивает выполнение запроса только одной или несколькими секциями
(в зависимости от используемого условия). Это повышает эффективность запроса.
Если свойство PartitionKey не является частью запроса, то в поисках необходимых объектов
просматриваются все секции таблицы. Это значительно снижает эффективность.
Ниже приведены рекомендации по выбору свойства PartitionKey таблицы для повышения
эффективности запросов.
1. Прежде всего, определите основные свойства таблицы. Эти свойства часто используются
как фильтры запросов.
2. Из этих основных свойств выберите возможные ключи.
a. Важно определить, какой запрос будет преобладающим для рабочей нагрузки
приложения. Из этого преобладающего запроса выберите свойства, используемые
в фильтрах запроса.
b. Это исходный набор свойств ключей.
c. Отсортируйте свойства ключей в порядке их значимости в запросе.
3. Проверьте, обеспечивают ли свойства ключей однозначную идентификацию объекта.
Если нет, включите в набор ключей уникальный идентификатор.
4. Если имеется только одно свойство ключа, используйте его в качестве PartitionKey.
5. Если имеется только два свойства ключей, используйте первое как PartitionKey, а второе —
как RowKey.
6. При наличии более двух свойств ключей можно попытаться распределить их в две группы:
первая объединенная группа — PartitionKey, а вторая — RowKey. В этом случае
приложение должно знать, что PartitionKey, например, состоит из двух ключей,
разделенных символом «-».
3.2.3 Масштабируемость
Теперь, когда приложение имеет потенциальный набор ключей, необходимо убедиться,
что выбранная схема секционирования является масштабируемой.
1. Исходя из статистики запросов, определите, не приведет ли секционирование
по выбранному выше PartitionKey к созданию слишком загруженных секций, которые
не смогут эффективно обслуживаться одним сервером. Это можно проверить при помощи
нагрузочного тестирования секции таблицы. Для проверки создается тестовая таблица
с использованием выбранных ключей. Одна из ее секций подвергается максимальной
8
нагрузке. Это позволяет определить, обеспечит ли таблица необходимую
производительность приложения.
2. Если тестирование прошло успешно, значит ключи выбраны правильно.
3. Если результаты нагрузочного тестирования отрицательные, выберите PartitionKey более
высокого уровня. Для этого можно выбрать другой PartitionKey или изменить имеющийся
(например, объединив его со следующим свойством ключа). Создание большего
количества секций позволяет избежать использования одной слишком большой
или слишком загруженной секции.
4.
СИСТЕМА ОБЕСПЕЧИВАЕТ МАСШТАБИРОВАНИЕ И ОБРАБОТКУ БОЛЬШОГО КОЛИЧЕСТВА ЗАПРОСОВ . ОДНАКО ПРИ
ЧРЕЗВЫЧАЙНО ВЫСОКОЙ ИНТЕНСИВНОСТИ ЗАПРОСОВ ЕЙ ПРИХОДИТСЯ ВЫПОЛНЯТЬ БАЛАНСИРОВКУ НАГРУЗКИ , В
РЕЗУЛЬТАТЕ ЧЕГО НЕКОТОРЫЕ ИЗ ЗАПРОСОВ МОГУТ ЗАВЕРШАТЬСЯ ОШИБКОЙ ПРЕВЫШЕНИЯ ВРЕМЕНИ ОЖИДАНИЯ .
СНИЖЕНИЕ ЧАСТОТЫ ЗАПРОСОВ МОЖЕТ СОКРАТИТЬ ИЛИ УСТРАНИТЬ ОШИБКИ ТАКОГО РОДА . БОЛЬШИНСТВО
ПОЛЬЗОВАТЕЛЕЙ РЕДКО СТАЛКИВАЮТСЯ С ОШИБКАМИ ПРЕВЫШЕНИЯ ВРЕМЕНИ ОЖИДАНИЯ . ТЕМ НЕ МЕНЕЕ , ЕСЛИ
ТАКИЕ ОШИБКИ ВОЗНИКАЮТ ЧАСТО ИЛИ НЕОЖИДАННО — ОБРАТИТЕСЬ К НАМ ЧЕРЕЗ ФОРУМЫ WINDOWS AZURE
НА MSDN. М Ы ПОМОЖЕМ ВАМ ОПТИМИЗИРОВАТЬ РАБОТУ С ТАБЛИЦЕЙ WINDOWS AZURE И ПРЕДОТВРАТИТЬ
ПОЯВЛЕНИЕ ЭТИХ ОШИБОК В ВАШЕМ ПРИЛОЖЕНИИ .
3.2.4 Гибкое секционирование
Может потребоваться анализ расширяемости выбранных ключей, особенно если на момент
их выбора нет точных сведений о характеристиках пользовательского трафика. В этом случае
важно выбирать легко расширяемые ключи для более тонкого секционирования. Если
секционирование оказывается слишком крупноструктурным, можно расширить текущую схему
секционирования. Подробный пример приведен ниже.
4 Программирование таблиц
Для таблиц и объектов поддерживаются следующие базовые операции:





СОЗДАНИЕ ТАБЛИЦЫ ИЛИ ОБЪЕКТА .
ИЗВЛЕЧЕНИЕ ТАБЛИЦЫ ИЛИ ОБЪЕКТА С ПРИМЕНЕНИЕМ ФИЛЬТРОВ .
ОБНОВЛЕНИЕ ОБЪЕКТА (НО НЕ ТАБЛИЦЫ).
УДАЛЕНИЕ ТАБЛИЦЫ ИЛИ ОБЪЕКТА .
ТРАНЗАКЦИИ ГРУПП ОБЪЕКТОВ , ПОДДЕРЖИВАЮЩИХ ТРАНЗАКЦИИ С МНОЖЕСТВОМ ОБЪЕКТОВ В ОДНОЙ ТАБЛИЦЕ
И СЕКЦИИ .
Для работы с таблицами в .NET-приложении можно использовать службы обработки данных
ADO.NET.
В следующей таблице приведен список API. Применение API службы обработки данных ADO.NET
сводится к передаче REST-пакетов. Поэтому приложения могут непосредственно использовать
REST, обеспечивающий доступ к хранилищу для языков, отличных от .NET. REST также позволяет
более точно управлять сериализацией и десериализацией объектов. Это полезно при работе
9
с разными типами объектов в одной таблице, а также при необходимости назначить объекту
большее количество свойств, чем допустимо.
Операция
Запрос
ADO.NET Data
Services
Запрос LINQ
HTTPРесурс
команда
GET
Таблица
Объект
Обновление UpdateObject &
PUT
всего
SaveChanges(SaveCh
angesOptions.Re
объекта
placeOnUpdate)
Объект
Обновление
части
объекта
Создание
нового
объекта
UpdateObject &
SaveChanges()
MERGE
Объект
AddObject &
SaveChanges()
POST
Таблица
Удаление
объекта
DeleteObject &
SaveChanges()
Объект
DELETE
Таблица
Объект
Транзакции
групп
объектов
SaveChanges
(SaveChanges
Options.Batch)
POST
$batch
10
Описание
Возвращает список всех таблиц
этой учетной записи хранения.
При наличии фильтра возвращает
список таблиц в соответствии
с условиями фильтрации.
Возвращает все объекты заданной
таблицы или подмножество
объектов, если заданы условия
фильтрации.
Обновляет значения свойств
объекта. Операция PUT заменяет
весь объект и может
использоваться для удаления
свойств.
Обновляет значения свойств
объекта.
Создает новую таблицу в этой
учетной записи хранения.
Вставляет новый объект
в указанную таблицу.
Удаляет таблицу в этой учетной
записи хранения.
Удаляет объект из указанной
таблицы.
Транзакция групп объектов
осуществляется при помощи
пакетной операции
над объектами одной таблицы
с одинаковым ключом секции.
При использовании опции
SaveChanges на платформе
ADO.NET Data Services
необходимо, чтобы запрос
выполнялся как одна транзакция.
Таблицы поддерживают следующие расширенные операции:


РАЗБИВКА НА СТРАНИЦЫ ;
ОБРАБОТКА КОНФЛИКТОВ , ВОЗНИКАЮЩИХ В РЕЗУЛЬТАТЕ ПАРАЛЛЕЛЬНЫХ ОБНОВЛЕНИЙ .
4.1 Управление версиями
Для всех решений Windows Azure Storage введен новый HTTP-заголовок «x-ms-version».
С помощью этого заголовка все изменения в API хранилища регистрируются в качестве версий.
Это позволяет применять к системе хранения предыдущие версии команд, расширять
возможности существующих команд и создавать новые.
Заголовок x-ms-version должен быть задан для всех запросов к хранилищу Windows Azure.
При поступлении запроса без указания версии система хранения выполнит команду самой старой
из поддерживаемых версий.
К PDC 2009 планируется сделать x-ms-version обязательным для всех неанонимных команд.
До тех пор, если версия запроса не задана, предполагается использование CTP-версии API
хранилища Windows Azure с PDC 2008. Запрос с недействительным x-ms-version будет отклонен.
Текущей версией является «x-ms-version: 2009-04-14». Она может использоваться для всех команд
и запросов к хранилищу Windows Azure. В этой версии реализована новая функция для таблиц
Windows Azure — транзакции групп объектов. Чтобы использовать эту функцию, в заголовке
запроса необходимо указать данную версию.
// добавление заголовка версии при помощи события SendingRequest.
Этот заголовок
// находится там же, где находился заголовок даты. Однако
// заголовок версии не является частью канонизированной строки, используемой
для
// создания подписи
context.SendingRequest +=
new EventHandler<SendingRequestEventArgs>(
delegate(object sender, SendingRequestEventArgs requestArgs)
{
HttpWebRequest request = requestArgs.Request as HttpWebRequest;
request.Headers.Add(
"x-ms-date",
DateTime.UtcNow.ToString("R", CultureInfo.InvariantCulture));
request.Headers.Add("x-ms-version", "2009-04-14");
// ... // ... добавление заголовка авторизации при помощи
общедоступного ключа
});
11
4.2 Пример
В приведенных ниже примерах описываются операции с таблицей «Blogs». В этой таблице
хранятся блоги приложения MicroBlogging.
В приложении MicroBlogging содержатся две таблицы: Channels («Каналы») и Blogs («Блоги»).
Имеется список каналов; блоги публикуются в определенном канале. Для этого приложения
пользователи подписываются на каналы и ежедневно получают новые блоги этих каналов.
Мы рассмотрим только таблицу Blogs и приведем примеры следующих операций:
1.
2.
3.
4.
5.
6.
7.
ОПРЕДЕЛЕНИЕ СХЕМЫ ТАБЛИЦЫ
СОЗДАНИЕ ТАБЛИЦЫ
ВСТАВКА БЛОГА В ТАБЛИЦУ
ПОЛУЧЕНИЕ СПИСКА БЛОГОВ ИЗ ТАБЛИЦЫ
ОБНОВЛЕНИЕ БЛОГА В ТАБЛИЦЕ
УДАЛЕНИЕ БЛОГА ИЗ ТАБЛИЦЫ
ВСТАВКА НЕСКОЛЬКИХ БЛОГОВ В ТАБЛИЦУ
4.3 Определение класса объекта для таблицы
Схема таблицы определена как класс C#. Эта модель используется платформой службой
обработки данных ADO.NET. Схема известна только клиентскому приложению и упрощает доступ
к данным. Сервер не использует эту схему.
Ниже приведено определение объектов Blog, хранящихся в таблице Blogs. Каждый объект блога
содержит следующие данные.
1.
2.
3.
4.
ИМЯ КАНАЛА — КАНАЛ, В КОТОРОМ РАЗМЕЩАЕТСЯ БЛОГ .
ДАТА РАЗМЕЩЕНИЯ.
ТЕКСТ — СОДЕРЖИМОЕ ТЕЛА БЛОГА.
РЕЙТИНГ — ПОПУЛЯРНОСТЬ БЛОГА .
Для этой таблицы «Blogs» в качестве свойства PartitionKey было выбрано имя канала, в качестве
RowKey — дата размещения блога. PartitionKey и RowKey — это ключи таблицы «Blogs».
Они указываются объявлением ключей с использованием атрибута класса DataServiceKey. Таблица
«Blogs» секционируется с помощью ChannelName. Это позволяет приложению эффективно
извлекать самые последние блоги канала, на которые подписан пользователь. Кроме ключей,
в качестве свойств объявлены характерные для пользователя атрибуты. Все свойства, имеющие
общие механизмы получения и установки, хранятся в таблице Windows Azure. Это демонстрирует
приведенный ниже пример.



ТЕКСТ И РЕЙТИНГ ЭКЗЕМПЛЯРА ОБЪЕКТА ХРАНЯТСЯ В ТАБЛИЦЕ AZURE .
RATING ASSTRING НЕ ХРАНИТСЯ , ТАК КАК ДЛЯ НЕГО НЕТ ОПРЕДЕЛЕННОГО ЗАДАНИЯ СВОЙСТВ .
ИДЕНТИФИКАТОР НЕ ХРАНИТСЯ, ТАК КАК ДЛЯ НЕГО НЕ СУЩЕСТВУЕТ ОБЩЕДОСТУПНЫХ МЕТОДОВ ДОСТУПА .
[DataServiceKey("PartitionKey", "RowKey")]
public class Blog
{
12
// Имя канала
public string PartitionKey { get; set; }
// Дата размещения
public string RowKey { get; set; }
// Определяемые пользователем свойства
public string Text { get; set; }
public int Rating { get; set; }
public string RatingAsString { get; }
protected string Id { get; set; }
}
4.4 Создание таблицы
Рассмотрим создание таблицы «Blogs» для учетной записи хранения. Создание таблицы
аналогично созданию объекта в основной таблице, которая называется «Tables». Каждая учетная
запись хранения имеет основную таблицу, определенную заранее. Каждая таблица, которая
используется учетной записью хранения, должна быть зарегистрирована в основной таблице.
Описание класса для основной таблицы приведено ниже. Свойство TableName представляет
собой имя создаваемой таблицы.
[DataServiceKey("TableName")]
public class TableStorageTable
{
public string TableName { get; set; }
}
Создание таблицы происходит следующим образом:
// Uri службы — “http://<Account>.table.core.windows.net/”
DataServiceContext context = new DataServiceContext(serviceUri);
TableStorageTable table = new TableStorageTable("Blogs");
// Создание новой таблицы путем добавления нового объекта в основную
// таблицу "Tables"
context.AddObject("Tables", table);
// SaveChanges приводит к вызову сервера
DataServiceResponse response = context.SaveChanges();
serviceUri — это URI службы таблицы, http://<Account name goes here>.table.core.windows.net/.
DataServiceContext — один из основных классов службы обработки данных ADO.NET, который
представляет собой контекст среды выполнения для службы. Он поддерживает API для вставки,
обновления, удаления и запроса объектов с помощью LINQ или RESTful URI и управляет
состоянием на стороне клиента.
4.5 Вставка блога
Чтобы вставить объект, приложение должно выполнить следующие действия:
1.
2.
СОЗДАТЬ НОВЫЙ ОБЪЕКТ C# И ЗАДАТЬ ВСЕ СВОЙСТВА.
СОЗДАТЬ ЭКЗЕМПЛЯР DATA SERVICE CONTEXT, КОТОРЫЙ ПРЕДСТАВЛЯЕТ ПОДКЛЮЧЕНИЕ К СЕРВЕРУ СЛУЖБЫ
ОБРАБОТКИ ДАННЫХ ADO .NET ДЛЯ УЧЕТНОЙ ЗАПИСИ ХРАНЕНИЯ.
13
3.
4.
ДОБАВИТЬ ОБЪЕКТ C# В КОНТЕКСТ .
ВЫЗВАТЬ МЕТОД SAVECHANGES НА DATA SERVICE CONTEXT ДЛЯ ОТПРАВКИ ЗАПРОСА СЕРВЕРУ ; В ЭТОТ МОМЕНТ
HTTP- ЗАПРОС ОТПРАВЛЯЕТСЯ НА СЕРВЕР С ОБЪЕКТОМ В XML-ФОРМАТЕ ATOM.
Примеры кода для вышеперечисленных операций:
Blog blog = new Blog {
PartitionKey = "Channel9",
// ChannelName
RowKey = DateTime.UtcNow.ToString(), // PostedDate
Text = "Hello",
Rating = 3
};
serviceUri = new Uri("http://<account>.table.core.windows.net");
var context = new DataServiceContext(serviceUri);
context.AddObject("Blogs", blog);
DataServiceContext response = context.SaveChanges();
4.6 Запрос блогов
Объекты запрашиваются с помощью LINQ. Это встроенный язык запросов в C#. В этом примере
извлекаются все блоги, рейтинг которых равен 3.
При перечислении запроса (например, с помощью оператора foreach), запрос передается на
сервер. Сервер отправляет результаты в XML-формате ATOM. Клиентская библиотека ADO.NET
Data Services десериализует результаты в объекты C#, после чего они могут использоваться
приложением.
var serviceUri = new Uri("http://<account>.table.core.windows.net");
DataServiceContext context = new DataServiceContext(serviceUri);
// запрос LINQ с использованием DataServiceContext для выбора всех объектов блога из
// таблицы Blogs, рейтинг которых = 3
var blogs =
from blog in context.CreateQuery<Blog>("Blogs")
where blogs.Rating == 3
select blog;
// Перечисление блогов начинается после отправки запроса на сервер
// и его выполнения
foreach (Blog blog in blogs) { ... }
4.7 Обновление блога
Чтобы обновить объект, приложение должно выполнить следующие действия:
1.
2.
СОЗДАТЬ DATA CONTEXT С MERGEOPTION ЗАДАВ ЗНАЧЕНИЕ OVERWRITE CHANGES ИЛИ PRESERVE CHANGES , КАК
ОПИСАНО В РАЗДЕЛЕ 4.10. Э ТО ОБЕСПЕЧИВАЕТ ПРАВИЛЬНУЮ ОБРАБОТКУ ETAG ДЛЯ КАЖДОГО ИЗВЛЕКАЕМОГО
ОБЪЕКТА .
ПОЛУЧИТЬ ОБНОВЛЯЕМЫЙ ОБЪЕКТ С ПОМОЩЬЮ LINQ DATA C ONTEXT. ПОЛУЧЕНИЕ ОБЪЕКТА С СЕРВЕРА
ГАРАНТИРУЕТ ОБНОВЛЕНИЕ ETAG В ОБЪЕКТАХ , ОТСЛЕЖИВАЕМЫХ КОНТЕКСТОМ , А ТАКЖЕ ИСПОЛЬЗОВАНИЕ
ОБНОВЛЕННОГО ETAG ПРИ ПОСЛЕДУЮЩИХ ОБНОВЛЕНИЯХ И УДАЛЕНИЯХ ЭЛЕМЕНТОВ В ЗАГОЛОВКЕ IF -MATCH .
ИЗМЕНИТЬ ОБЪЕКТ C#, ПРЕДСТАВЛЯЮЩИЙ ОБЪЕКТ.
14
3.
4.
ВЕРНУТЬ ОБЪЕКТ C# В ТОТ ЖЕ DATA CONTEXT ДЛЯ ОБНОВЛЕНИЯ. ИСПОЛЬЗОВАНИЕ ОДНОГО
И ТОГО ЖЕ DATA C ONTEXT ГАРАНТИРУЕТ АВТОМАТИЧЕСКОЕ ПОВТОРНОЕ ИСПОЛЬЗОВАНИЕ ET AG , ПОЛУЧЕННОГО
ДЛЯ ЭТОГО ОБЪЕКТА РАНЕЕ .
ВЫЗВАТЬ МЕТОД SAVECHANGES ДЛЯ ОТПРАВКИ ЗАПРОСА НА СЕРВЕР .
Blog blog =
(from blog in context.CreateQuery<Blog>("Blogs")
where blog.PartitionKey == "Channel9"
&& blog.RowKey == "Oct-29"
select blog).FirstOrDefault();
blog.Text = "Hi there";
context.UpdateObject(blog);
DataServiceResponse response = context.SaveChanges();
4.8 Удаление блога
Удаление объекта равносильно его обновлению. Для этого необходимо извлечь объект
с помощью DataServiceContext и вызвать метод UpdateObject вместо метода DeleteObject.
// Получение объекта Blog для ("Channel9", "Oct-29") в запросе выше
context.DeleteObject(blog);
DataServiceResponse response = context.SaveChanges();
4.9 Транзакции групп объектов
Транзакции групп объектов поддерживают выполнение до 100 команд CUD в одном пакете
для объектов одной таблицы с одинаковым значением ключа секции. Эти команды выполняются
атомарным образом, то есть или успешно завершаются все, или ни одна.
За некоторым исключением, мы используем протокол службы обработки данных ADO.NET
для пакетных транзакций. Дополнительные сведения см. на сайте http://msdn.microsoft.com/
en-us/library/cc668802.aspx). Ниже приведены отдельные термины, характерные для пакетной
обработки:


НАБОР ИЗМЕНЕНИЙ — ГРУППА ИЗ ОДНОЙ ИЛИ БОЛЕЕ КОМАНД CUD, ВЫПОЛНЯЕМЫХ АТОМАРНЫМ ОБРАЗОМ .
ПАКЕТ — СОДЕРЖИТ ОДИН ИЛИ НЕСКОЛЬКО НАБОРОВ ИЗМЕНЕНИЙ ЛИБО ЗАПРОСОВ .
Применение пакетных команд для таблицы Windows Azure имеет следующие ограничения:




НАБОР ИЗМЕНЕНИЙ МОЖЕТ СОДЕРЖАТЬ НЕ БОЛЕЕ 100 ОПЕРАЦИЙ .
ДОПУСКАЕТСЯ ВСЕГО ОДИН НАБОР ИЗМЕНЕНИЙ ИЛИ ЗАПРОС НА ПАКЕТНУЮ ОПЕРАЦИЮ; ЗАПРОС НЕ МОЖЕТ БЫТЬ
ОБЪЕДИНЕН С ОПЕРАЦИЯМИ CUD В ДАННОЙ ПАКЕТНОЙ ОПЕРАЦИИ ; ПОДДЕРЖИВАЮТСЯ ТОЛЬКО ЗАПРОСЫ,
КОТОРЫЕ ДЕТЕРМИНИРОВАННО ВОЗВРАЩАЮТ ОДНУ СТРОКУ , ТО ЕСТЬ ДЛЯ КОТОРЫХ PARTITION KEY И R OW KEY
ОПРЕДЕЛЯЮТСЯ С ПОМОЩЬЮ СИМВОЛА '=' В КАЧЕСТВЕ ФИЛЬТРА ЗАПРОСА ;
МАКСИМАЛЬНЫЙ РАЗМЕР ПАКЕТА — 4 MБ.
ВСЕ КОМАНДЫ НАБОРА ИЗМЕНЕНИЙ ДОЛЖНЫ ОТНОСИТСЯ К ОДНОЙ СЕКЦИИ В ОДНОЙ ТАБЛИЦЕ; ЭТО ОЗНАЧАЕТ,
ЧТО ВСЕ ОБЪЕКТЫ ДОЛЖНЫ ИМЕТЬ ОДНО И ТО ЖЕ ЗНАЧЕНИЕ КЛЮЧА СЕКЦИИ , А ОПЕРАЦИИ ДОЛЖНЫ
ВЫПОЛНЯТЬСЯ С ОДНОЙ ТАБЛИЦЕЙ .
15

КАЖДЫЙ ОБЪЕКТ МОЖЕТ ВСТРЕЧАТЬСЯ В НАБОРЕ ИЗМЕНЕНИЙ ТОЛЬКО ОДИН РАЗ; НАБОР ИЗМЕНЕНИЙ НЕ МОЖЕТ
ВЫПОЛНЯТЬ НЕСКОЛЬКО ОПЕРАЦИЙ С ОДНИМ И ТЕМ ЖЕ ОБЪЕКТОМ ; НАБОР ИЗМЕНЕНИЙ ПРЕДНАЗНАЧЕН ДЛЯ
ВЫПОЛНЕНИЯ НЕСКОЛЬКИХ ОПЕРАЦИЙ НАД РАЗНЫМИ ОБЪЕКТАМИ ОДНОЙ СЕКЦИИ И ТАБЛИЦЫ В КАЧЕСТВЕ
АТОМАРНОЙ ТРАНЗАКЦИИ .

ОТДЕЛЬНЫЕ КОМАНДЫ НАБОРА ИЗМЕНЕНИЙ ВЫПОЛНЯЮТСЯ В ПОРЯДКЕ , В КОТОРОМ ОНИ УКАЗАНЫ В НАБОРЕ
ИЗМЕНЕНИЙ .
При выполнении транзакции ETag передается не как заголовок всего запроса, а рассылается
в каждый набор изменений как описано в документации MSDN. На сервере проверяется,
соответствует ли ETag значению, хранящемуся на сервере. Если ETag не совпадает, то транзакция
дает сбой.
Следующий фрагмент кода для BLOB-объекта демонстрирует применение транзакции групп
объектов для атомарного создания, обновления и удаления множества объектов из секции
за одну пакетную операцию.
//
//
//
//
//
newBlogs является списком новых блогов в секции Channel_19
deletedBlogs является списком блогов, которые требуется удалить из секции Channel_19
updatedBlogs является списком блогов, которые требуется обновить в секции Channel_19
Предполагается, что deletedBlogs и updatedBlogs были отобраны
в контекст в предыдущих запросах.
// Вставка множества объектов блогов в секцию "Channel_19".
// newBlogs является списком блогов, которые были выбраны для
// вставки вне этой функции
for (int index = 0; index < newBlogs.Length; index++)
{
context.AddObject(newBlogs[index]);
}
// Удаление старых объектов блогов для секции Channel_19.
// deletedBlogs является списком блогов, которые были выбраны для
// удаления вне этой функции
for (int index = 0; index < deletedBlogs.Length; index++)
{
context.DeleteObject(deletedBlogs[index]);
}
// Обновление существующих объектов блогов для секции Channel_19.
// updatedBlogs является списком блогов, которые были выбраны для
// увеличения рейтинга
for (int index = 0; index < updatedBlogs.Length; index++)
{
updatedBlogs[index].Rating++;
context.UpdateObject(updatedBlogs[index]);
}
// Все операции CUD, приведенные выше, выполняются как один пакетный запрос.
DataServiceResponse response =
context.SaveChanges(SaveChangesOptions.Batch);
Заданный параметр SaveChangeOptions.Batch указывает команде SaveChanges, что все ожидающие подтверждения изменения группируются в один набор изменений и передаются в систему
16
хранения как единая транзакция групп объектов. Пакетная транзакция проверяется на сервере
на соответствие всем перечисленным выше ограничениям.
Если бы при выполнении описанной выше команды SaveChanges не использовался параметр
SaveChangesOptions.Batch, то каждый объект операции создания, обновления и удаления
передавался и выполнялся бы как отдельный запрос.
Следующий фрагмент кода демонстрирует один запрос пакетной команды. Обратите внимание:
пакетная команда может быть либо транзакцией групп объектов, как показано выше, либо
отдельным запросом, как показано ниже. Это позволяет выполнять запрос Get к одной строке,
используя формат URI, представляющий один объект, для обхода ограничения размера сегмента
URI (не более 260 символов), налагаемого HTTP.sys (см. раздел 2).
// Выполнение запроса в пакете — обратите внимание, что требуется фильтр
// имеющий следующую форму:
// PartitionKey == 'Some Value' && RowKey == 'Some Value'
var q1 = from o in context.CreateQuery<Blogs>("Blogs")
where o.PartitionKey == "Channel_19" &&
o.RowKey == "2"
select o;
DataServiceResponse response =
context.ExecuteBatch((DataServiceQuery<RetailStoreV1>)q1);
Примеры трассировки можно найти в документации MSDN.
ПРИМЕЧАНИЕ: Чтобы обойти эту проблему без применения пакетных запросов, можно
сформировать запрос, используя приведенный ниже синтаксис. В этом случае фильтры, ключ
секции и ключ строки определены не в сегменте URI, а в параметре запроса $filter.
from o in context.CreateQuery<Blogs>("Blogs")
where o.PartitionKey == "Channel_19"
select o into d
where d.RowKey == "2"
select d;
4.9.1 Обработка ответа
Если пакетный запрос принят и обработан сервером, ответом на него всегда будет код возврата
202 Accepted. Ответы на отдельные операции пакета возвращаются в элементе ответа
как полезная нагрузка пакета. По сути, если ни одна операция набора изменений не дала сбой,
то ответ на пакетную операцию полностью соответствует запросу. В этом случае для изменения
набора возвращается только один ответ, так как операции изменения набора являются
атомарными.

ЕСЛИ ВСЕ ОПЕРАЦИИ НАБОРА ИЗМЕНЕНИЙ ВЫПОЛНЕНЫ УСПЕШНО , ТО ВОЗВРАЩАЕТСЯ ОТВЕТ ДЛЯ КАЖДОЙ
ОПЕРАЦИИ НАБОРА ИЗМЕНЕНИЙ. Е СЛИ КАКАЯ-ЛИБО ОПЕРАЦИЯ НАБОРА ДАЕТ СБОЙ , ТО ВОЗВРАЩАЕТСЯ ВСЕГО
ОДИН ОБЪЕКТ ОТВЕТА , ПРЕДСТАВЛЯЮЩИЙ ЕДИНЫЙ ОТВЕТ ДЛЯ ВСЕХ ОПЕРАЦИЙ НАБОРА ИЗМЕНЕНИЙ .
17

ПЕРЕДАЧА HTTP-ТЕГОВ E TAG ОСУЩЕСТВЛЯЕТСЯ ТЕМИ ЖЕ МЕХАНИЗМАМИ , КОТОРЫЕ ОПИСАНЫ В ДОПОЛНЕНИИ К
ОСНОВНОМУ ПРОТОКОЛУ СПЕЦИФИКАЦИИ НЕЖЕСТКОЙ БЛОКИРОВКИ , ГДЕ ЭЛЕМЕНТ ОТВЕТА ИГРАЕТ РОЛЬ HTTPЗАГОЛОВКОВ ОТВЕТА ДЛЯ ЗАДАННОЙ ОПЕРАЦИИ .
4.9.2 Ошибки
При сбое пакетного запроса ни одна из его операций не выполняется. Однако приложению важно
знать, какая из команд привела к сбою всей транзакции. Для этого в сообщении об ошибке
возвращается порядковый номер команды, как видно в выделенной строке приведенного ниже
примера ответа. Если ошибка возникает из-за проблем с авторизацией или по какой-то другой
причине, не связанной с какой-либо определенной командой, порядковый номер не предоставляется. Таким образом, полезная нагрузка в случае сбоя будет включать в себя:
1.
2.
3.
КЛЮЧ ОПЕРАЦИИ , ДАВШЕЙ СБОЙ .
ОСНОВНОЙ НУЛЕВОЙ ИНДЕКС ОПЕРАЦИИ В ПАКЕТЕ, КОТОРЫЙ ПРИВЕЛ К СБОЮ (ИНДЕКС НАЧИНАЕТСЯ С 0).
СТРОКУ , ОПИСЫВАЮЩУЮ СБОЙ .
Пример ответа:
HTTP/1.1 202 Accepted
Cache-Control: no-cache
Transfer-Encoding: chunked
Content-Type: multipart/mixed; boundary=batchresponse_7ab1553a-7dd6-44e7-8107-bf1ea1ab1876
Server: Table Service Version 1.0 Microsoft-HTTPAPI/2.0
x-ms-request-id: 45ac953e-a4a5-42ba-9b4d-97bf74a8a32e
Date: Thu, 30 Apr 2009 20:45:13 GMT
6E7
--batchresponse_7ab1553a-7dd6-44e7-8107-bf1ea1ab1876
Content-Type: multipart/mixed; boundary=changesetresponse_6cc856b4-8cb9-41eb-b8d2bb73475c6cec
--changesetresponse_6cc856b4-8cb9-41eb-b8d2-bb73475c6cec
Content-Type: application/http
Content-Transfer-Encoding: binary
HTTP/1.1 400 Bad Request
Content-ID: 4
Content-Type: application/xml
Cache-Control: no-cache
DataServiceVersion: 1.0;
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<error xmlns="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata">
<code>InvalidInput</code>
<message xml:lang="en-US">1:One of the request inputs is not valid.</message>
</error>
--changesetresponse_6cc856b4-8cb9-41eb-b8d2-bb73475c6cec---batchresponse_7ab1553a-7dd6-44e7-8107-bf1ea1ab1876-0
При возникновении исключения можно получить порядковый номер команды, ставшей причиной
сбоя (выделенная строка в предыдущем примере), следующим образом:
try
{
// ... // ... сохранение изменений
}
catch (InvalidOperationException e)
{
DataServiceClientException dsce = e.InnerException as DataServiceClientException;
int? commandIndex;
string errorMessage;
18
ParseErrorDetails(dsce, out commandIndex, out errorMessage);
}
void ParseErrorDetails(
DataServiceClientException e,
out string errorCode,
out int? commandIndex,
out string errorMessage)
{
GetErrorInformation(e.Message, out errorCode, out errorMessage);
commandIndex = null;
int indexOfSeparator = errorMessage.IndexOf(':');
if (indexOfSeparator > 0)
{
int temp;
if (Int32.TryParse(errorMessage.Substring(0, indexOfSeparator), out temp))
{
commandIndex = temp;
errorMessage = errorMessage.Substring(indexOfSeparator + 1);
}
}
}
void GetErrorInformation(
string xmlErrorMessage,
out string errorCode,
out string message)
{
message = null;
errorCode = null;
XName xnErrorCode = XName.Get(
"code",
"http://schemas.microsoft.com/ado/2007/08/dataservices/metadata");
XName xnMessage = XName.Get(
"message",
"http://schemas.microsoft.com/ado/2007/08/dataservices/metadata");
using (StringReader reader = new StringReader(xmlErrorMessage))
{
XDocument xDocument = null;
try
{
xDocument = XDocument.Load(reader);
}
catch (XmlException)
{
// Не удалось выполнить синтаксический разбор XML. Это могло произойти из-за
невозможности подключения
// к серверу, или если ответ не содержал
// сведений об ошибке (например, если код состояния ответа не был ни кодом ошибки
// ни кодом успешного завершения, а 3XX-кодом, таким как NotModified.
return;
}
XElement errorCodeElement =
xDocument.Descendants(xnErrorCode).FirstOrDefault();
if (errorCodeElement == null)
{
return;
}
errorCode = errorCodeElement.Value;
XElement messageElement =
xDocument.Descendants(xnMessage).FirstOrDefault();
19
if (messageElement != null)
{
message = messageElement.Value;
}
}
}
4.10 Рекомендации по работе с DataServiceContext
Приведем некоторые рекомендации по работе с DataServiceContext:



ОБЪЕКТ DATA SERVICE CONTEXT НЕ ОБЕСПЕЧИВАЕТ БЕЗОПАСНОСТЬ ПОТОКОВ , ПОЭТОМУ ОН НЕ МОЖЕТ
ИСПОЛЬЗОВАТЬСЯ СОВМЕСТНО РАЗНЫМИ ПОТОКАМИ . DATA SERVICE C ONTEXT НЕ ЯВЛЯЕТСЯ ОБЪЕКТОМ
С ПРОДОЛЖИТЕЛЬНЫМ ВРЕМЕНЕМ СУЩЕСТВОВАНИЯ . В МЕСТО ТОГО ЧТОБЫ ИСПОЛЬЗОВАТЬ ОДИН ОБЪЕКТ
DATA SERVICE CONTEXT В ТЕЧЕНИЕ ВСЕГО ВРЕМЕНИ СУЩЕСТВОВАНИЯ ПОТОКА , РЕКОМЕНДУЕТСЯ СОЗДАВАТЬ
DATA SERVICE CONTEXT КАЖДЫЙ РАЗ , КОГДА ВОЗНИКАЕТ НЕОБХОДИМОСТЬ ВЫПОЛНИТЬ НАБОР ТРАНЗАКЦИЙ С
WINDOWS AZURE TABLE , А ЗАТЕМ ЭТОТ ОБЪЕКТ УДАЛИТЬ.
ЕСЛИ ПРИ ИСПОЛЬЗОВАНИИ ОДНОГО ЭКЗЕМПЛЯРА DATA SERVICE CONTEXT ДЛЯ ВСЕХ ОПЕРАЦИЙ ВСТАВКИ ,
ОБНОВЛЕНИЯ И УДАЛЕНИЯ ВОЗНИКАЕТ СБОЙ ПРИ ВЫПОЛНЕНИИ SAVE C HANGES , ТО СВЕДЕНИЯ ОБ ОПЕРАЦИИ ,
КОТОРАЯ ДАЛА СБОЙ , СОХРАНЯЮТСЯ В DATA SERVICE C ONTEXT . ПОПЫТКА ВЫПОЛНИТЬ ЭТУ ОПЕРАЦИЮ
ПОВТОРЯЕТСЯ ПРИ ПОСЛЕДУЮЩЕМ ВЫЗОВЕ SAVE C HANGES .
DATA SERVICE CONTEXT ИМЕЕТ СВОЙСТВО MERGE OPTION, КОТОРОЕ ИСПОЛЬЗУЕТСЯ ДЛЯ КОНТРОЛЯ ОБРАБОТКИ
ОТСЛЕЖИВАЕМЫХ ОБЪЕКТОВ . ВОЗМОЖНЫЕ ЗНАЧЕНИЯ :
o APPENDONLY : ЭТО ЗНАЧЕНИЕ ПО УМОЛЧАНИЮ, ПРИ ИСПОЛЬЗОВАНИИ КОТОРОГО DATA SERVICECONTEXT
НЕ ЗАГРУЖАЕТ ЭКЗЕМПЛЯР ОБЪЕКТА С СЕРВЕРА , ЕСЛИ ОН УЖЕ ИМЕЕТСЯ В ЕГО КЭШЕ .
o OVERWRITECHANGES : DATA SERVICE CONTEXT ВСЕГДА ЗАГРУЖАЕТ ЭКЗЕМПЛЯР ОБЪЕКТА С СЕРВЕРА И
ПЕРЕЗАПИСЫВАЕТ ПРЕДЫДУЩИЙ ВАРИАНТ ОБЪЕКТА , ТО ЕСТЬ ОБЕСПЕЧИВАЕТ СООТВЕТСТВИЕ
ЭКЗЕМПЛЯРА ОБЪЕКТА ЕГО ТЕКУЩЕМУ СОСТОЯНИЮ.
o PRESERVE CHANGES : ЕСЛИ ЭКЗЕМПЛЯР ОБЪЕКТА СУЩЕСТВУЕТ В DATA SERVICE CONTEXT,
ОН НЕ ЗАГРУЖАЕТСЯ ИЗ ПОСТОЯННОГО ХРАНИЛИЩА . В СЕ ИЗМЕНЕНИЯ СВОЙСТВ , ВНОСИМЫЕ В ОБЪЕКТЫ
В DATA SERVICE C ONTEXT , СОХРАНЯЮТСЯ , НО ET AG ОБНОВЛЯЕТСЯ . П ОЭТОМУ ДАННУЮ ОПЦИЮ СЛЕДУЕТ
ИСПОЛЬЗОВАТЬ ПРИ НЕОБХОДИМОСТИ ВОССТАНОВЛЕНИЯ ПОСЛЕ ОШИБОК СОВМЕСТНОГО ДОСТУПА С
НЕЖЕСТКОЙ БЛОКИРОВКОЙ .
o
NOTRACKING : DATA SERVICE CONTEXT НЕ ОТСЛЕЖИВАЕТ ЭКЗЕМПЛЯРЫ ОБЪЕКТОВ. ОБНОВЛЕНИЕ ОБЪЕКТА
В КОНТЕКСТЕ БЕЗ ОТСЛЕЖИВАНИЯ РЕАЛИЗУЕТСЯ С ПОМОЩЬЮ ETAG , КОТОРЫЙ ОБНОВЛЯЕТСЯ
ПОСРЕДСТВОМ ATTACH TO. Э ТОТ ВАРИАНТ НЕ РЕКОМЕНДУЕТСЯ К ПРИМЕНЕНИЮ.
context.AttachTo("Blogs", blog, "etag to use");
context.UpdateObject(blog);
context.SaveChanges();
ЕСЛИ ДЛЯ MERGEOPTION КОНТЕКСТА ЗАДАНО ЗНАЧЕНИЕ APPENDONLY И DATA SERVICE CONTEXT
УЖЕ ОТСЛЕЖИВАЕТ ОБЪЕКТ В РЕЗУЛЬТАТЕ ПРЕДЫДУЩЕЙ ОПЕРАЦИИ ИЗВЛЕЧЕНИЯ ИЛИ ДОБАВЛЕНИЯ , ПОВТОРНОЕ
ИЗВЛЕЧЕНИЕ ОБЪЕКТА С СЕРВЕРА НЕ ПРИВЕДЕТ К ОБНОВЛЕНИЮ ОТСЛЕЖИВАЕМОГО ОБЪЕКТА В КОНТЕКСТЕ .

ТАКИМ ОБРАЗОМ , ЕСЛИ ОБЪЕКТ НА СЕРВЕРЕ БЫЛ ИЗМЕНЕН , ТО ПОСЛЕДУЮЩИЕ ОПЕРАЦИИ ОБНОВЛЕНИЯ И
УДАЛЕНИЯ ПРИВЕДУТ К СБОЮ ПРЕДВАРИТЕЛЬНЫХ УСЛОВИЙ . В ПРИМЕРЕ КОДА , ПРИВЕДЕННОМ В РАЗДЕЛЕ ,
MERGE OPTION ЗАДАНО КАК ЗНАЧЕНИЕ PRESERVE CHANGES ; БЛАГОДАРЯ ЭТОМУ ОБЪЕКТ ВСЕГДА ЗАГРУЖАЕТСЯ С
СЕРВЕРА .
ВСЛЕДСТВИЕ ИЗВЕСТНОЙ ПРОБЛЕМЫ С ПРОИЗВОДИТЕЛЬНОСТЬЮ КЛИЕНТСКОЙ БИБЛИОТЕКИ ADO.NET DATA
SERVICES ПРИ ОПИСАНИИ КЛАССА РЕКОМЕНДУЕТСЯ ИСПОЛЬЗОВАТЬ ИМЯ ТАБЛИЦЫ ИЛИ ОПРЕДЕЛЯТЬ ДЕЛЕГАТ
20
RESOLVE TYPE ДЛЯ DATA SERVICECONTEXT. Е СЛИ ЭТОГО НЕ СДЕЛАТЬ И ЕСЛИ ИМЯ КЛАССА НЕ СООТВЕТСТВУЕТ
ИМЕНИ ТАБЛИЦЫ , ТО ПРОИЗВОДИТЕЛЬНОСТЬ ЗАПРОСА ДЕГРАДИРУЕТ С УВЕЛИЧЕНИЕМ КОЛИЧЕСТВА ОБЪЕКТОВ ,
ВОЗВРАЩАЕМЫХ В ОТВЕТЕ . НИЖЕ ПРИВЕДЕН ПРИМЕР ИСПОЛЬЗОВАНИЯ ДЕЛЕГАТА R ESOLVE TYPE :
PUBLIC VOID
Q UERY(D ATASERVICECONTEXT CONTEXT )
{
// ЗАДАЕМ R ESOLVE T YPE В КАЧЕСТВЕ МЕТОДА, ВОЗВРАЩАЮЩЕГО ОПРЕДЕЛЕННЫЙ
// ТИП, КОТОРЫЙ ДОЛЖЕН БЫТЬ СОЗДАН
CONTEXT .R ESOLVE T YPE = THIS .R ESOLVE E NTITY T YPE ;
...
}
PUBLIC
T YPE RESOLVE ENTITY T YPE(STRING NAME )
{
// ЕСЛИ КОНТЕКСТ ВКЛЮЧАЕТ В СЕБЯ ВСЕГО ОДИН ТИП, ТО ОН МОЖЕТ БЫТЬ ВОЗВРАЩЕН БЕЗ ПРОВЕРКИ
// ЗНАЧЕНИЯ " NAME". В ПРОТИВНОМ СЛУЧАЕ ПРОВЕРЬТЕ ИМЯ И ВЕРНИТЕ СООТВЕТСТВУЮЩИЙ
// ТИП (ВОЗМОЖНО , БУДЕТ ПОЛЕЗНЫМ СОПОСТАВЛЕНИЕ DICTIONARY < STRING, TYPE> )
T YPE TYPE = TYPEOF (C USTOMER);
RETURN TYPE ;
}
4.11 Использование API REST
Результатом всех рассматриваемых выше операций является передача HTTP-сообщений на сервер
и получение их с сервера. Приложение может отказаться от использования клиентской
библиотеки .NET и работать на уровне HTTP/REST.
5 Параллельные обновления
Для обновления объекта необходимо выполнить следующие операции:
1.
2.
ПОЛУЧИТЬ ОБЪЕКТ С СЕРВЕРА .
ОБНОВИТЬ ОБЪЕКТ ЛОКАЛЬНО И ОТПРАВИТЬ ЕГО ОБРАТНО НА СЕРВЕР .
Предположим, что два процесса, выполняющиеся параллельно, пытаются обновить один
и тот же объект. Поскольку шаги 1 и 2 не являются атомарными, любой из них может закончиться
внесением изменений в уже устаревшую версию объекта. Для решения этой проблемы таблица
Windows Azure использует нежесткую блокировку.
1.
2.
3.
4.
5.
ДЛЯ ЛЮБОГО ОБЪЕКТА СИСТЕМА СОХРАНЯЕТ ВЕРСИЮ, КОТОРАЯ ИЗМЕНЯЕТСЯ СЕРВЕРОМ ПРИ КАЖДОМ
ОБНОВЛЕНИИ .
ПРИ ИЗВЛЕЧЕНИИ ОБЪЕКТА СЕРВЕР ОТПРАВЛЯЕТ ЭТУ ВЕРСИЮ КЛИЕНТУ В ВИДЕ ETAG HTTP.
КОГДА КЛИЕНТ ОТПРАВЛЯЕТ ЗАПРОС UPDATE НА СЕРВЕР , ОН ПЕРЕДАЕТ ЭТОТ ETAG В ВИДЕ ЗАГОЛОВКА IF-MATCH.
ЕСЛИ ВЕРСИЯ ОБЪЕКТА НА СЕРВЕРЕ СООТВЕТСТВУЕТ ETAG В ЗАГОЛОВКЕ IF-MATCH, ТО ИЗМЕНЕНИЕ ПРИНИМАЕТСЯ.
ХРАНЯЩИЙСЯ НА СЕРВЕРЕ ОБЪЕКТ ПОЛУЧАЕТ НОВУЮ ВЕРСИЮ. НОВАЯ ВЕРСИЯ ВОЗВРАЩАЕТСЯ КЛИЕНТУ В
КАЧЕСТВЕ ЗАГОЛОВКА ET AG .
ЕСЛИ ВЕРСИЯ ОБЪЕКТА НА СЕРВЕРЕ НЕ СООТВЕТСТВУЕТ ETAG В ЗАГОЛОВКЕ IF-MATCH, ТО ИЗМЕНЕНИЕ
ОТКЛОНЯЕТСЯ И КЛИЕНТУ ВОЗВРАЩАЕТСЯ HTTP-ОШИБКА « НЕОБХОДИМОЕ УСЛОВИЕ НЕ ВЫПОЛНЕНО ».
Если клиентское приложение получает ошибку «необходимое условие не выполнено»,
используется типовой алгоритм повторения всей операции (см. фрагмент кода ниже).
21
1.
2.
ПРИЛОЖЕНИЕ ДОЛЖНО ИЗВЛЕЧЬ ЭТОТ ОБЪЕКТ СНОВА И ПОЛУЧИТЬ ЕГО ПОСЛЕДНЮЮ ВЕРСИЮ.
ОБНОВИТЬ ОБЪЕКТ ЛОКАЛЬНО И ОТПРАВИТЬ ЕГО ОБРАТНО НА СЕРВЕР .
При использовании клиентской библиотеки .NET приложение получает HTTP-код ошибки в виде
исключения (DataServiceRequestException).
В приведенном ниже примере два разных клиента выполняют один и тот же код для изменения
текста. Эти два клиента пытаются задать свойству «Text» разные значения. Рассмотрим
возможную последовательность событий, иллюстрирующую обработку параллельных
обновлений.
1.
2.
3.
4.
5.
6.
ОБА КЛИЕНТА ИЗВЛЕКАЮТ ОБЪЕКТ. ПРИ ЭТОМ ДЛЯ КАЖДОГО ОБЪЕКТА ИЗВЛЕКАЕТСЯ ETAG , НАПРИМЕР , «V1».
ОБА КЛИЕНТА ПРЕДПОЛАГАЮТ, ЧТО ПРЕДЫДУЩАЯ ВЕРСИЯ ОБЪЕКТА — «V1».
КАЖДЫЙ КЛИЕНТ ЛОКАЛЬНО ОБНОВЛЯЕТ СВОЙСТВО TEXT.
КАЖДЫЙ КЛИЕНТ ВЫЗЫВАЕТ МЕТОДЫ UPDATE OBJECT И SAVECHANGES .
КАЖДЫЙ КЛИЕНТ ОТПРАВЛЯЕТ НА СЕРВЕР HTTP-ЗАПРОС С ЗАГОЛОВКОМ «IF-MATCH:V1».
ЗАПРОС ОДНОГО ИЗ КЛИЕНТОВ ПОПАДАЕТ НА СЕРВЕР ПЕРВЫМ .
a. СЕРВЕР СРАВНИВАЕТ ЗАГОЛОВОК «IF-MATCH» С ВЕРСИЕЙ ОБЪЕКТА . ОНИ СОВПАДАЮТ .
b. СЕРВЕР ПРИНИМАЕТ ИЗМЕНЕНИЕ .
c. ВЕРСИЯ ОБЪЕКТА НА СЕРВЕРЕ ОБНОВЛЯЕТСЯ И СТАНОВИТСЯ «V2».
d. В КАЧЕСТВЕ ОТВЕТА КЛИЕНТУ ОТПРАВЛЯЕТСЯ НОВЫЙ ЗАГОЛОВОК «ETAG:V2».
ДАЛЕЕ НА СЕРВЕР ПОСТУПАЕТ ЗАПРОС ДРУГОГО КЛИЕНТА . НА ЭТОТ МОМЕНТ ИЗМЕНЕНИЯ ПЕРВОГО КЛИЕНТА УЖЕ
ПРИМЕНЕНЫ .
a. СЕРВЕР СРАВНИВАЕТ ЗАГОЛОВОК «IF-MATCH» С ВЕРСИЕЙ ОБЪЕКТА . ОНИ НЕ СОВПАДАЮТ , ПОСКОЛЬКУ
ВЕРСИЯ ОБЪЕКТА УЖЕ ИЗМЕНЕНА НА « V 2», ТОГДА КАК В ЗАПРОСЕ УКАЗЫВАЕТСЯ ВЕРСИЯ « V 1».
b. СЕРВЕР ОТКЛОНЯЕТ ЗАПРОС.
// Указываем параметры объединения, которые сохраняют обновления, но позволяют обновлять etag.
// По умолчанию применяется значение AppendOnly, при котором отслеживаемый объект не
перезаписывается
// значениями, полученными с сервера; в результате используется недействительный etag
// если объект на сервере изменился.
context.MergeOption = MergeOption.PreserveChanges;
Blog blog =
(from blog in context.CreateQuery<Blog>("Blogs")
where blog.PartitionKey == "Channel9"
&& blog.RowKey == "Oct-29"
select blog).FirstOrDefault();
blog.Text = "Hi there again";
try
{
context.UpdateObject(blog);
DataServiceResponse response = context.SaveChanges();
}
catch (DataServiceRequestException e)
{
OperationResponse response = e.Response.First();
if (response.StatusCode == (int)HttpStatusCode.PreconditionFailed)
{
// повторный запрос к объекту для получения последнего etag
22
// и его обновление
}
}
5.1 Безусловные обновления
Для безусловного обновления объекта приложению необходимо выполнить следующие операции:
1.
2.
3.
4.
СОЗДАТЬ НОВЫЙ ОБЪЕКТ DATASERVICE CONTEXT ИЛИ ОТСОЕДИНИТЬ ОБЪЕКТ В СЛУЧАЕ ИСПОЛЬЗОВАНИЯ
СУЩЕСТВУЮЩЕГО КОНТЕКСТА ( СМ . ПРИМЕР НИЖЕ ).
ПРИСОЕДИНИТЬ ОБЪЕКТ К КОНТЕКСТУ И ИСПОЛЬЗОВАТЬ СИМВОЛ «*» В КАЧЕСТВЕ НОВОГО ЗНАЧЕНИЯ ETAG.
ОБНОВИТЬ ОБЪЕКТ .
ВЫЗВАТЬ SAVECHANGES .
// установка параметра объединения, разрешающего перезапись для обновления отслеживаемого
объекта
context.Detach(blog);
// Прикрепление объекта к контексту с использованием имени таблицы, обновляемого объекта,
// и символа "*" в качестве значения etag.
context.AttachTo("Blogs", blog, "*");
blog.Text = "Hi there again";
try
{
context.UpdateObject(blog);
DataServiceResponse response = context.SaveChanges();
}
catch (DataServiceRequestException e)
{
// Обработка ошибки; в данном случае невозможно формирование ошибки "необходимое условие
не выполнено"
}
6 Разбиение результатов запроса на страницы
Система поддерживает два механизма обработки запросов, которые могут возвращать большое
количество результатов:
1.
2.
ИСПОЛЬЗОВАНИЕ LINQ- ФУНКЦИИ TAKE(N) ДЛЯ ПОЛУЧЕНИЯ ПЕРВЫХ N ОБЪЕКТОВ .
ИСПОЛЬЗОВАНИЕ МАРКЕРА ПРОДОЛЖЕНИЯ, ОБОЗНАЧАЮЩЕГО НАЧАЛО СЛЕДУЮЩЕГО НАБОРА РЕЗУЛЬТАТОВ .
6.1 Получение первых N объектов
Система поддерживает функцию возвращения первых N объектов, соответствующих запросу.
Например, в .NET, для извлечения первых N объектов можно использовать LINQ-функцию Take(N)
(в данном примере это первые 100 объектов).
serviceUri = new Uri("http://<account>.table.core.windows.net");
DataServiceContext svc = new DataServiceContext(serviceUri);
var allBlogs = context.CreateQuery<Blog>("Blogs");
foreach (Blog blog in allBlogs.Take(100))
{
// выполнение операции с каждым блогом
}
23
Аналогичные функции поддерживаются при использовании интерфейса REST с опцией строки
запроса Stop=N. Например, запрос «GET http://<serviceUri>/Blogs?$top=100» обеспечил
бы возвращение первых 100 объектов, соответствующих запросу. Фильтрация выполняется
на сервере, поэтому в ответе клиенту может быть передано максимум 100 объектов.
6.2 Маркеры продолжения
КОЛИЧЕСТВО ВОЗВРАЩАЕМЫХ ЗАПРОСОМ ОБЪЕКТОВ МОЖЕТ БЫТЬ ОГРАНИЧЕНО ПО ОДНОЙ ИЗ СЛЕДУЮЩИХ ПРИЧИН:
1. В ЗАПРОСЕ УКАЗЫВАЕТСЯ МАКСИМАЛЬНОЕ ЧИСЛО ОБЪЕКТОВ , КОТОРОЕ ДОЛЖНО БЫТЬ ВОЗВРАЩЕНО .
2. КОЛИЧЕСТВО ОБЪЕКТОВ ПРЕВЫШАЕТ МАКСИМАЛЬНО РАЗРЕШЕННОЕ СЕРВЕРОМ ЧИСЛО ОБЪЕКТОВ В ОТВЕТЕ (В
НАСТОЯЩЕЕ ВРЕМЯ — 1000 ОБЪЕКТОВ ).
3. ОБЩИЙ РАЗМЕР ОБЪЕКТОВ В ОТВЕТЕ ПРЕВЫШАЕТ МАКСИМАЛЬНО ДОПУСТИМЫЙ РАЗМЕР ОТВЕТА (В НАСТОЯЩЕЕ
ВРЕМЯ — 4 МБ, ВКЛЮЧАЯ ИМЕНА СВОЙСТВ , НО ИСКЛЮЧАЯ XML -ТЕГИ , ИСПОЛЬЗУЕМЫЕ ДЛЯ REST).
4. НА ВЫПОЛНЕНИЕ ЗАПРОСА ТРЕБУЕТСЯ ВРЕМЯ , ПРЕВЫШАЮЩЕЕ ЗАДАННЫЙ ПЕРИОД ОЖИДАНИЯ СЕРВЕРА (В
НАСТОЯЩЕЕ ВРЕМЯ — 60 СЕКУНД ).
В любом из этих случаев ответ содержит маркер продолжения в виде специального заголовка.
Для запроса к объектам используются настраиваемые заголовки, представляющие собой маркер
продолжения:


X -MS -CONTINUATION -N EXTPARTITION KEY
X -MS -CONTINUATION -N EXTR OWKEY
Клиент должен передать оба эти полученные значения назад со следующим запросом в виде
параметров HTTP-запроса; во всем остальном запрос остается неизменным. Это обеспечит
возвращение следующего набора объектов, начиная с места, обозначенного маркером
продолжения.
Следующий запрос выглядит так:
http://<serviceUri>/Blogs?<originalQuery>&NextPartitonKey=<someValue>&NextRowKey=<someOtherValue>
Это повторяется до тех пор, пока клиент не получит ответ без маркера продолжения,
что свидетельствует об извлечении всех соответствующих запросу результатов.
Маркер продолжения должен рассматриваться как непрозрачное значение. Он указывает на точку
начала следующего запроса и может не соответствовать фактическому объекту в таблице. Если
добавляется новый объект, так что Key(новый объект) > Key(последний объект, извлеченный
запросом), но Key(новый объект) < «Маркер продолжения», тогда этот новый объект не будет
возвращен повторным запросом, использующим маркер продолжения. Тем не менее новые
объекты, добавленные так, что Key(новый объект) > «Маркер продолжения», войдут в результаты,
возвращаемые последующими запросами, которые используют маркер продолжения.
24
7 Модель согласованности
Рассмотрим модель согласованности, обеспечиваемую таблицей Windows Azure.
7.1 Согласованность в рамках одной таблицы
В рамках одной таблицы система обеспечивает транзакции ACID для всех операций вставки,
обновления и удаления одного объекта.
Для запросов в рамках одной секции выполняется изоляция моментального снимка. Запрос имеет
согласованное представление секции с момента его начала и в течение всей транзакции.
Моментальный снимок характеризуется следующими свойствами:
1.
2.
ОТСУТСТВИЕ «ГРЯЗНОГО СЧИТЫВАНИЯ ». ТРАНЗАКЦИЯ НЕ БУДЕТ ВИДЕТЬ НЕЗАФИКСИРОВАННЫЕ ИЗМЕНЕНИЯ,
ВНОСИМЫЕ ДРУГИМИ ТРАНЗАКЦИЯМИ , КОТОРЫЕ ВЫПОЛНЯЮТСЯ ПАРАЛЛЕЛЬНО . БУДУТ ВИДИМЫ ТОЛЬКО
ИЗМЕНЕНИЯ , ЗАВЕРШЕННЫЕ ДО НАЧАЛА ВЫПОЛНЕНИЯ ЗАПРОСА НА СЕРВЕРЕ .
МЕХАНИЗМ ИЗОЛЯЦИИ МОМЕНТАЛЬНОГО СНИМКА ПОЗВОЛЯЕТ ПРОИЗВОДИТЬ ЧТЕНИЕ ПАРАЛЛЕЛЬНО
С ОБНОВЛЕНИЕМ СЕКЦИИ БЕЗ БЛОКИРОВАНИЯ ЭТОГО ОБНОВЛЕНИЯ .
Изоляция моментального снимка поддерживается только внутри секции в рамках одного запроса.
Система не поддерживает изоляцию моментального снимка для нескольких секций таблицы
или различных продолжений запроса.
7.2 Согласованность множества таблиц
Приложения обеспечивают согласованность множества таблиц.
В примере MicroBlogging использовалось две таблицы: Channels и Blogs. Приложение
обеспечивает согласованность таблиц Channels и Blogs. Например, если канал удаляется
из таблицы Channels, приложение удаляет соответствующие блоги из таблицы Blogs.
Во время синхронизации состояния нескольких таблиц могут возникать сбои. Приложение должно
обрабатывать такие сбои и иметь возможность возобновлять работу с момента, на котором
она была прервана.
В предыдущем примере, если канал удаляется из таблицы каналов, приложение должно также
удалить все блоги этого канала из таблицы Blogs. При выполнении этого процесса могут возникать
сбои приложения. Для обработки таких сбоев приложение может сохранять транзакцию в очередях
Windows Azure. Это позволяет пользователю возобновить операцию удаления канала и всех
его блогов даже в случае сбоя.
Вернемся к примеру с таблицами «Channels» и «Blogs». Объект таблицы «Channels» имеет
следующие свойства: {имя — PartitionKey, пустая строка — RowKey, владелец, дата создания}.
Объект таблицы «Blogs» имеет свойства {имя канала — PartitionKey, дата создания — RowKey,
название, блог, идентификатор пользователя}. Теперь, когда канал удален, необходимо удалить
все ассоциированные с ним блоги. Для этого следует выполнить следующие шаги:
25
1.
2.
3.
4.
СОЗДАЕМ ОЧЕРЕДЬ ДЛЯ ОБЕСПЕЧЕНИЯ СОГЛАСОВАННОСТИ ТАБЛИЦ , НАЗОВЕМ ЕЕ «DELETE CHANNEL
ANDBLOGS ».
ПРИ ПОСТУПЛЕНИИ ЗАПРОСА НА УДАЛЕНИЕ КАНАЛА ОТ РОЛИ ВЕБ -ИНТЕРФЕЙСА СТАВИМ В СОЗДАННУЮ ВЫШЕ
ОЧЕРЕДЬ ЭЛЕМЕНТ , ОПРЕДЕЛЯЮЩИЙ ИМЯ КАНАЛА .
СОЗДАЕМ РАБОЧИЕ РОЛИ , КОТОРЫЕ БУДУТ ОЖИДАТЬ СОБЫТИЕ ДОБАВЛЕНИЯ ЭЛЕМЕНТА В ОЧЕРЕДЬ
«DELETE CHANNELANDB LOGS ».
РАБОЧАЯ РОЛЬ ИЗЫМАЕТ ЭЛЕМЕНТ ИЗ ОЧЕРЕДИ DELETE CHANNELANDBLOGS , ЗАДАВАЯ ДЛЯ ИЗВЛЕЧЕННОГО
ЭЛЕМЕНТА ОЧЕРЕДИ ВРЕМЯ НЕВИДИМОСТИ В ТЕЧЕНИЕ N СЕКУНД . ПРИ ЭТОМ ИЗВЛЕКАЕТСЯ ЭЛЕМЕНТ ,
ОПРЕДЕЛЯЮЩИЙ ИМЯ КАНАЛА , КОТОРЫЙ ДОЛЖЕН БЫТЬ УДАЛЕН . ЕСЛИ РАБОЧАЯ РОЛЬ УДАЛЯЕТ ЭЛЕМЕНТ
ОЧЕРЕДИ В ТЕЧЕНИЕ ЭТИХ N СЕКУНД , ТО ДАННЫЙ ЭЛЕМЕНТ БУДЕТ УДАЛЕН ИЗ ОЧЕРЕДИ . В ПРОТИВНОМ СЛУЧАЕ
ЭЛЕМЕНТ СТАНЕТ ВНОВЬ ВИДИМЫМ И ДОСТУПНЫМ ДЛЯ ИСПОЛЬЗОВАНИЯ РАБОЧЕЙ РОЛЬЮ . ПРИ ИЗВЛЕЧЕНИИ
ЭЛЕМЕНТА РАБОЧАЯ РОЛЬ ДЕЛАЕТ СЛЕДУЮЩЕЕ :
a. В ТАБЛИЦЕ «CHANNELS » ПОМЕЧАЕТ КАНАЛ КАК НЕДЕЙСТВИТЕЛЬНЫЙ , ЧТОБЫ С ЭТОГО МОМЕНТА НИКТО
НЕ МОГ ВЫПОЛНЯТЬ ЧТЕНИЕ ИЗ НЕГО .
b. УДАЛЯЕТ ИЗ ТАБЛИЦЫ BLOGS ВСЕ ЗАПИСИ , ДЛЯ КОТОРЫХ PARTITIONKEY = «ИМЯ КАНАЛА », СОГЛАСНО
ПОЛУЧЕННОМУ ЭЛЕМЕНТУ ОЧЕРЕДИ .
c. УДАЛЯЕТ КАНАЛ ИЗ ТАБЛИЦЫ «CHANNELS ».
d. УДАЛЯЕТ ЭЛЕМЕНТ ИЗ ОЧЕРЕДИ.
e. ВОЗВРАЩАЕТСЯ К ИСХОДНОМУ СОСТОЯНИЮ .
Предположим, что в ходе выполнения шага 4 возникает сбой, например, аварийное завершение
рабочего процесса, и элемент не удаляется из очереди. Как только элемент очереди станет снова
видимым (то есть когда истечет время ожидания видимости), это сообщение будет вновь извлечено из очереди рабочим процессом, и процесс удаления возобновится с шага 4. Для получения
более подробной информации об обработке очередей обратитесь к документации по очередям
Windows Azure.
8 Советы и рекомендации
В этом разделе будут рассмотрены некоторые проблемы, возникающие при работе приложения,
и их возможные решения.
8.1 Извлечение элементов, добавленных последними (моделирование
расположения элементов в порядке по убыванию)
В приложениях для работы с электронной почтой, новостями, блогами и т. д. элемент, добавленный
самым последним, обычно необходимо отображать первым. Учитывая, что все строки сортируются
по свойствам PartitionKey и RowKey, маловероятно, что первым в списке будет стоять элемент,
созданный позже других.
Для выполнения этого требования в качестве RowKey можно задать строку фиксированной длины,
равную DateTime.MaxValue.Ticks — DateTime.UtcNow.Ticks. Это позволяет RowKey сортировать
элементы соответственно смещению по времени, начиная с элементов, добавленных последними.
26
Например:
Blog blog= new Blog();
// Заметьте, что используется фиксированная длина 19, так как максимальная длина такта —
19 разрядов.
string rowKeyToUse = string.Format("{0:D19}",
DateTime.MaxValue.Ticks — DateTime.UtcNow.Ticks);
blog.RowKey = rowKeyToUse;
Итак, блог b1 от 01.10.2008 10:00:00 будет равен 25217944559999999994455999999999 как RowKey,
а b2 от 02.10.2008 10:00:00 будет равен 25217935919999999993591999999999 как RowKey.
Следовательно, b2 будет располагаться выше b1.
Для извлечения всех блогов, созданных после 01.10.2008 10:00:00, будет использоваться
следующий запрос:
string rowKeyToUse = string.Format("{0:D19}",
DateTime.MaxValue.Ticks — DateTime.UtcNow.Ticks);
var blogs =
from blog in context.CreateQuery<Blog>("Blogs")
where blog.PartitionKey == "Football"
&& blog.RowKey.CompareTo(rowKeyToUse) > 0
select blog;
ПРИМЕЧАНИЕ. Фиксированная длина строки должна быть достаточной для обработки большого
диапазона значений. Приведем пример. Максимальная длина DateTime.MaxValue.Ticks — Date
Time.UtcNow.Ticks составляет 19 разрядов. Однако для дат, начиная с 15.02.6831 14:13:20,
эта длина составляет 18 разрядов. Если не добавить '0' в начале строки, лексикографическая
сортировка даст в этом случае нежелательные результаты, то есть 92 > 10000; но если дополнить
это значение нулями, получим 10000 > 00092.
Для CTP-версии допускается только один RowKey. Однако при помощи конкатенации строк
RowKey может представлять несколько ключей. Предположим, что блог может быть оценен
по шкале от 0 до 5. Требуется, чтобы блоги были отсортированы по рейтингу и по времени
создания в убывающем порядке. Это означает, что сначала блоги сортируются по рейтингу,
а затем в рамках каждого рейтинга по времени создания. Ключ строки может быть задан
как <Рейтинг>+<Разряды, обеспечивающие сортировку элементов в порядке от более свежих
до более старых записей>. Рассмотрим приведенный выше пример с двумя блогами и предположим, что blog1 получил рейтинг 5, а blog2 — рейтинг 1. Поскольку рейтинг имеет больше
преимущество чем разряды, то blog1 будет располагаться в списке перед blog2, несмотря
на то что blog2 был размещен позднее, чем blog1.
PartitionKey и RowKey — это свойства строкового типа. Однако соблюдение следующих правил
позволит применять в этих свойствах данные и других типов. Это обеспечивает сортировку
значений в ожидаемом порядке. Например:

INTEGER — СОХРАНЯЙТЕ КАК ЗНАЧЕНИЕ ФИКСИРОВАННОГО РАЗМЕРА С ДОПОЛНЯЮЩИМИ НУЛЯМИ В НАЧАЛЕ .
27

DATETIME — СОХРАНЯЙТЕ В ФОРМАТЕ ГГГГ-ММ-ДД ИЛИ ГГГГ -ММ-ДД-ЧЧ-СС, ЕСЛИ ТРЕБУЕТСЯ БОЛЕЕ ТОЧНОЕ
УКАЗАНИЕ ДАТЫ И ВРЕМЕНИ . ЕСЛИ НОВЫЕ ОБЪЕКТЫ ДОЛЖНЫ РАСПОЛАГАТЬСЯ В СПИСКЕ ВЫШЕ СТАРЫХ ,
ИСПОЛЬЗУЙТЕ РАЗРЯДЫ , КАК ОПИСАНО РАНЕЕ .
8.2 Извлечение с использованием префикса
ПОСКОЛЬКУ СОПОСТАВЛЕНИЕ ПРЕФИКСОВ НЕ ПОДДЕРЖИВАЕТСЯ, ОНО МОЖЕТ БЫТЬ СЫМИТИРОВАНО ПРИМЕНЕНИЕМ К
СВОЙСТВУ ФИЛЬТРА >= & <. НАПРИМЕР , ЕСЛИ У КАЖДОГО PDC ЕСТЬ СОБСТВЕННЫЙ КАНАЛ И ТРЕБУЕТСЯ ИЗВЛЕЧЬ ВСЕ
БЛОГИ ЭТОГО PDC, ЗАПРОС ДОЛЖЕН ОБЕСПЕЧИВАТЬ ИЗВЛЕЧЕНИЕ ВСЕХ БЛОГОВ ВСЕХ КАНАЛОВ , В КОТОРЫХ PARTITION KEY
НАЧИНАЕТСЯ С «PDC». Э ТО МОЖНО ОСУЩЕСТВИТЬ СЛЕДУЮЩИМ ЗАПРОСОМ :
var blogs =
from blog in context.CreateQuery<Blog>("Blogs")
where blog.PartitionKey.CompareTo("PDC") >= 0
&& blog.PartitionKey.CompareTo("PDD") < 0
select blog;
8.3 Пример секционирования данных
Секционирование данных заключается в группировке объектов таблицы в секции, так что секция
становится наименьшим элементом, применяемым для балансировки нагрузки. Таблица Windows
Azure использует свойство PartitionKey для группировки объектов в секции. В этом разделе
мы рассмотрим, как секционировать данные для обеспечения лучшей производительности.
8.3.1 Использование микроблогинга
Для примера возьмем приложение микроблогинга, в котором пользователи могут создавать
каналы (или категории) и блоги в них. При выборе канала на экран выводится страница блогов.
Предположим, что блог в таблице Blogs однозначно идентифицируется именем канала и датой
создания. Оптимизируем таблицу для преобладающего запроса «Получить 10 последних блогов
канала».
В этом примере рассматриваются различные варианты выбора PartitionKey, обеспечивающие
формирование секций различного размера, от очень больших до очень маленьких, вплоть
до секции, содержащей всего один объект. Таким образом, размер и количество секций в таблице
могут быть различными.
8.3.1.1 Несколько больших секций
Очевидный выбор для использования в качестве PartitionKey — имя канала. Метод, описанный
в разделе, также обеспечит сортировку блогов в канале по свойству RowKey в порядке
поступления, начиная от поступившего последним. Ниже приведено описание класса объектов
таблицы Blogs для данного метода секционирования:
28
[DataServiceKey("PartitionKey", "RowKey")]
public class Blog
{
// Имя канала
public string PartitionKey { get; set; }
// Для сортировки по RowKey, начиная с самого нового и заканчивая
// самым старым блогом, RowKey определяем как DateTime.MaxValue.Ticks —
DateTime.UtcNow.Ticks
public string RowKey { get; set; }
// Определяемые пользователем свойства
public string Text { get; set; }
public DateTime CreatedOn { get; set; }
public int Rating { get; set; }
}
Это позволяет эффективно запрашивать и выводить на экран все блоги канала «Football», сортируя
их по дате публикации, начиная с последнего, на основании заданного выше свойства RowKey.
var blogs =
from blog in context.CreateQuery<Blog>("Blogs")
where blog.PartitionKey == "Football"
select blog;
foreach (Blog blog in blogs) { ... }
При такой схеме секционирования элементом балансировки нагрузки является канал. Блоги
разных каналов располагаются в разных секциях. Эти секции могут быть распределены по разным
серверам для оптимизации трафика таблицы «Blogs».
8.3.1.2 Множество небольших секций
Предположим, что канал может содержать 10 млн блогов с множеством активных обсуждений.
Имя канала непригодно для использования в качестве ключа для секционирования, поскольку
это приведет к созданию одной очень большой секции с 10 млн блогов. В этом случае нельзя
обеспечить балансировку нагрузки, обусловленной активными обсуждениями, путем
распределения данных по разным серверам.
Одно из возможных решений, позволяющее уйти от таких монолитных секций, — использовать
в качестве PartitionKey комбинацию имени канала с датой и временем создания блога. В этом
случае PartitionKey однозначно идентифицирует каждый объект. Любая секция таблицы содержит
один объект. Это наилучшим образом подходит для балансировки нагрузки, поскольку обеспечивает системе хранения данных абсолютную свободу в распределении объектов по серверам
в соответствии с потребностями приложения. В зависимости от распределения секций
по серверам запросы типа «Получить все блоги за последний день или неделю» могут стать
довольно ресурсоемкими из-за необходимости доступа к нескольким узлам хранения.
29
8.3.1.3 Секция среднего размера
Альтернативным вариантом является группировка блогов на основании имени канала, периода
времени (недели/месяцы/дни) и популярность канала. Если особенная активность в канале
наблюдается в течение определенных месяцев, недель или дней, целесообразно секционировать
блоги по времени создания. В этом случае свойство PartitionKey должно содержать имя канала
и сведения о периоде времени. Это обеспечивает более точное секционирование и большую
гибкость распределения нагрузки между множеством серверов для оптимизации трафика.
При этом запросы типа «Получить все блоги канала Х за прошедший день» будут более
эффективными, поскольку направлены на небольшое целевое множество, определяемое
промежутком времени, включенным в PartitionKey.
Возьмем для примера канал «Football». Периоды особой активности в нем наблюдаются
в течение сезона, то есть с сентября по февраль. Лучше секционировать таблицу не только
по имени канала, но также по месяцам или неделям. Использование «недели» в PartitionKey
позволит сбалансировать нагрузку на такие популярные секции, как «Суперкубок», и обеспечить
высокую надежность и эффективность запросов.
Поскольку таблица Windows Azure поддерживает только один PartitionKey, приложение
микроблогинга может использовать как PartitionKey для данной таблицы <Имя_Канала>_<9999 —
Год>_<5 — Номер недели> или <Имя_Канала>_<9999 — Год>_<13 — Месяц>. Выбор между <9999 —
Год>_<13 — Месяц> или <9999 — Год>_<5 — Номер недели> зависит от того, что обеспечивает
более сбалансированную нагрузку: группировка блогов по неделям или по месяцам. В этом
примере компонент даты вычитается из верхней границы для сортировки объектов по дате
поступления, начиная с последнего. Ниже приведен пример класса объекта для данного метода
секционирования:
[DataServiceKey("PartitionKey", "RowKey")]
public class Blog
{
// <Имя канала>_<9999-ГГГГ>_<13-Месяц>
public string PartitionKey { get; set; }
// Для сортировки по RowKey, начиная с самого нового и заканчивая
// самым старым блогом, RowKey определяем как DateTime.MaxValue.Ticks —
DateTime.UtcNow.Ticks
public string RowKey { get; set; }
// Определяемые пользователем свойства.
public string Text { get; set; }
public DateTime CreatedOn { get; set; }
public int Rating { get; set; }
}
Это позволяет выполнять запросы и отображать блоги, опубликованные в заданном году и месяце:
// для 10.2008 будем использовать <Имя канала>_7991_03. 7991 = 9999 — 2008; 03 = 13 — 10
var blogs =
30
from blog in context.CreateQuery<Blog>("Blogs")
where blog.PartitionKey == "Football_7991_03_04"
select blog;
foreach (Blog blog in blogs) { ... }
Если нужно выбрать все блоги за год, можно использовать следующий запрос.
var blogs =
(from blog in context.CreateQuery<Blog>("Blogs")
where blog.PartitionKey.CompareTo("PDC") >= 0
&& blog.PartitionKey.CompareTo("PDD") < 0
select blog).Take(1000);
foreach (Blog blog in blogs) { ... }
ПРИМЕЧАНИЕ. Во всех фрагментах кода для краткости опущены маркеры продолжения.
В реальном приложении для получения желаемых результатов может понадобиться несколько
запросов с маркером продолжения.
8.3.2 Динамическая настройка детализации PartitionKey
В рассмотренном выше примере динамическое секционирование может обеспечить большую
эффективность. Используя этот пример, можно было бы начать с секционирования по <Имя_канала>_
<9999 — Год>_<13 — Месяц>. Но на время предполагаемых «горячих» месяцев можно сократить
размеры секций за счет использования секционирования <Имя_канала>_<9999 — Год>_<13 —
Месяц>_<5 — Неделя>. Приложение должно поддерживать динамическую схему секционирования.
Приведем пример. Если секционирование блогов выполняется по следующим правилам:
1.
2.
В ПЕРИОД С МАРТА ПО ИЮЛЬ, ТО ЕСТЬ В МЕРТВЫЙ СЕЗОН, ИСПОЛЬЗУЕТСЯ <ИМЯ _КАНАЛА >_<9999 —
ГОД>_<13 — МЕСЯЦ>.
В ОСТАЛЬНЫЕ МЕСЯЦЫ (ТО ЕСТЬ ВО ВРЕМЯ СЕЗОНА НАЦИОНАЛЬНОЙ ФУТБОЛЬНОЙ ЛИГИ ) ИСПОЛЬЗУЕТСЯ
<ИМЯ_КАНАЛА >_<9999 — ГОД>_<13 — МЕСЯЦ >_<5 — НЕДЕЛЯ>
Теперь для извлечения всех блогов, опубликованных 03.07.2008, приложение может использовать
следующий запрос:
// long rowKeyStart = DateTime.MaxValue.Ticks — d1.Ticks where d1 = 7/3/2008 00:00:00
// long rowKeyEnd = DateTime.MaxValue.Ticks — d2.Ticks where d2 = 7/4/2008 00:00:00
var blogs =
(from blog in context.CreateQuery<Blog>("Blogs")
where blog.PartitionKey == "Football_7991_06"
&& blog.RowKey.CompareTo(rowKeyStart) >= 0 &&
&& blog.RowKey.CompareTo(rowKeyEnd) < 0
select blog).Take(1000);
foreach (Blog blog in blogs) { }
Для извлечения блога, созданного 03.10.08, приложение может использовать следующий запрос:
// long rowKeyStart = DateTime.MaxValue.Ticks — d1.Ticks where d1 = 10/3/2008 00:00:00
// long rowKeyEnd = DateTime.MaxValue.Ticks — d2.Ticks where d2 = 10/4/2008 00:00:00
var blogs =
(from blog in context.CreateQuery<Blog>("Blogs")
where blog.PartitionKey == "Football_7991_03_04"
31
&& blog.RowKey.CompareTo(rowKeyStart) >= 0 &&
&& blog.RowKey.CompareTo(rowKeyEnd) < 0
select blog).Take(1000);
foreach (Blog blog in blogs) { }
Данный запрос более эффективен при
использовании «недели» в схеме
секционирования, поскольку в этом случае
область поиска сводится к блогам, созданным
только в первую неделю месяца, в котором
ожидается повышенная активность публикации
блогов.
8.3.3 Разные виды объектов в одной таблице
Часто в приложениях используются данные, которые представляют разные объекты, но должны
извлекаться и обрабатываться вместе. Например, на сайте социальной сети пользователь может
вести блог, размещать фотографии и т. д. Обычно пользовательский интерфейс группирует всю эту
информацию для конкретного пользователя. На странице пользователя Джо в социальной сети
можно увидеть последние изменения (то есть самые недавние сообщения блога, фотографии,
видео и т. д.). Домашняя страница отображает самые последние изменения, поэтому нужно
оптимизировать запросы для наиболее эффективного извлечения этих данных. Учитывая такие
требования, можно обеспечить кластеризацию данных для их эффективного извлечения, создав
единственную таблицу для хранения всех объектов с указанием их «вида», отсортированных
по дате публикации, начиная с самых последних. Данные могут быть секционированы по идентификатору пользователя. Объекты могут различаться по RowKey (то есть <Время публикации
(от самых новых до самых старых)>_<Вид объекта, например, фотография, сообщение блога,
ролик и т. д.>).
Это позволяет использовать транзакции групп объектов для создания, обновления и удаления
за одну транзакцию всех связанных объектов, принадлежащих одному пользователю (см. раздел 4.9).
Примечание. Если основным сценарием является извлечение объектов соответственно их виду,
в качестве RowKey может использоваться <Вид объекта>_<Время публикации (от самых новых
до самых старых)>. Мы хотим оптимизировать доступ к последним изменениям домашней
страницы, поэтому в данном примере время создания будет иметь больший приоритет,
чем вид объекта.
// Это объединение всех объектов будет использоваться только для запросов. При вставке и
обновлении
// объектов используются отдельные классы для каждого вида объекта.
[DataServiceKey("PartitionKey", "RowKey")]
public class SocialNetworkEntity
{
32
// Идентификатор пользователя
public string PartitionKey { get; set; }
// Для сортировки объектов от самых новых до самых старых
// <DateTime.MaxValue.Ticks — DateTime.UtcNow.Ticks>_EntityType
// длина может быть не более 19 разрядов
public string RowKey { get; set; }
// Определяемые пользователем свойства будут объединением всех свойств объекта.
// Тип объекта повторяется для облегчения поиска
public string EntityType { get; set; }
// Свойства блога
public string Text { get; set; }
public string Category { get; set; }
// Свойства фотографии
public string Uri { get; set; }
public string ThumbnailUri { get; set; }
// Общее для блога и фотографии
public string Caption { get; set; }
// Общее для блога и фотографии
public DateTime CreatedOn { get; set; }
public int Rating { get; set; }
// Данный класс может отвечать за возвращение соответствующего
// вида объекта, что избавляет бизнес-логику от необходимости
// обработки объединения. Это частный метод присвоения, поэтому он не
// сохраняется в хранилище.
public BlogEntity Blog { get; private set }
public PhotoEntity Photo { get; private set }
}
ПРИМЕЧАНИЕ. Класс с объединением всех свойств всех объектов, сгруппированных в одной
таблице, необходим для выполнения запросов только в службах ADO.Net. При использовании REST
он не нужен, поскольку в этом случае имеется общий контроль над сериализацией и десериализацией.
Это означает, что, получив параметр Kind (Вид объекта) в результате запроса REST, мы можем
заполнить соответствующий тип полями данных, возвращенных запросом REST.
Для извлечения объектов, отображаемых на домашней странице, можно использовать такой
запрос:
var entities =
(from entity in
context.CreateQuery<SocialNetworkEntity>("SocialNetworkTable")
where entity.PartitionKey == userId
select entity).Take(100);
Следующий запрос позволяет получить только фотографии для отображения в фотоальбоме:
string startPhotoKey = string.Format("{0:D19}_Photo", DateTime.MinValue.Ticks);
string endPhotoKey = string.Format("{0:D19}_Photo", DateTime.MaxValue.Ticks);
var entities =
(from entity in
context.CreateQuery<SocialNetworkEntity>("SocialNetworkTable")
where entity.PartitionKey == userId
and RowKey.CompareTo(startPhotoKey) >= 0
and RowKey.CompareTo(endPhotoKey) <= 0
33
select entity).Take(100);
ПРИМЕЧАНИЕ. При создании RowKey префикс должен иметь фиксированную длину.
Использование 19 разрядов позволяет задействовать весь диапазон длинных целых чисел.
Если целью приложения является извлечение объектов по их видам, то вид объекта в свойстве
RowKey должен иметь больший приоритет, чем время создания. Но в данном примере основной
задачей приложения является извлечение и отображение на веб-странице последних N
изменений; выбранный RowKey оптимизирован для решения этой задачи.
8.3.3.1 Десериализация объектов различных типов
Для десериализации объектов различных типов можно использовать событие Astoria
ReadingEntity. Предположим, что в одной таблице хранятся блоги и комментарии. PartitionKey
для этой таблицы — «имя пользователя», а RowKey — <Ключ строки блока>_<CommentId> .
/// <summary>
/// Вспомогательный класс используется для хранения данных, полученных в результате
синтаксического разбора полезной нагрузки ответа.
/// </summary>
class GenericType
{
public string ValueType { get; set; }
public string Value { get; set; }
public string PropertyName { get; set; }
public bool IsNull { get; set; }
}
/// <summary>
/// Обработчик считывает объекты и сохраняет все
/// блоги в словаре. Каждый блог содержит все связанные с ним комментарии. Этот класс
/// воссоздает объекты из полезной нагрузки и сохраняет их для последующей обработки.
/// </summary>
class BlogReader
{
private static readonly XNamespace AtomNamespace = "http://www.w3.org/2005/Atom";
private static readonly XNamespace AstoriaDataNamespace =
"http://schemas.microsoft.com/ado/2007/08/dataservices";
private static readonly XNamespace AstoriaMetadataNamespace =
"http://schemas.microsoft.com/ado/2007/08/dataservices/metadata";
internal Dictionary<string, Blogs> Blogs = new Dictionary<string, Blogs>();
internal BlogReader(DataServiceContext context)
{
context.ReadingEntity +=
new EventHandler<ReadingWritingEntityEventArgs>(OnReadingEntity);
context.ResolveType = this.ResolveType;
}
public Type ResolveType(string name)
{
return typeof(BaseEntity);
}
public void OnReadingEntity(object sender, ReadingWritingEntityEventArgs e)
{
// Чтение всех свойств из полезной нагрузки для этого объекта и их сохранение
// при помощи экземпляров GenericType.
var q = from p in e.Data.Element(AtomNamespace + "content")
34
.Element(AstoriaMetadataNamespace + "properties")
.Elements()
select new GenericType()
{
PropertyName = p.Name.LocalName,
IsNull = string.Equals("true",
p.Attribute(AstoriaMetadataNamespace
+ "null") == null ? null :
p.Attribute(AstoriaMetadataNamespace + "null").Value,
StringComparison.OrdinalIgnoreCase),
ValueType =
p.Attribute(AstoriaMetadataNamespace + "type") == null ?
null : p.Attribute(AstoriaMetadataNamespace + "type").Value,
Value = p.Value
};
сохранения.
// Тип объекта сохранен в свойстве "EntityType".
// Его необходимо считать для создания соответствующих типов объектов и их
string type = (from s in q.AsQueryable()
where s.PropertyName == "EntityType"
select s.Value.ToString()).FirstOrDefault();
if (string.Equals(type, "Blogs"))
{
Blogs blog = ReadBlog(e.Entity as BaseEntity, q);
this.Blogs.Add(blog.RowKey, blog);
}
else
{
// Комментарий сохраняет в собственном ключе идентификатор блога; поэтому нужно
выполнить его синтаксический разбор,
// чтобы воссоздать отношение. Прочитанный комментарий сохраняется
// вместе с блогом, прочитанным ранее, так как ключ блога < ключа
// комментария.
Comments comment = ReadComment(e.Entity as BaseEntity, q);
string[] tokens = comment.RowKey.Split('_');
this.Blogs[tokens[0]].Comments.Add(comment);
}
}
private Blogs ReadBlog(BaseEntity entity, IEnumerable<GenericType> blogProperties)
{
Blogs blog = new Blogs();
blog.PartitionKey = entity.PartitionKey;
blog.RowKey = entity.RowKey;
blog.Timestamp = entity.Timestamp;
// Чтобы извлечь интересующие нас свойства, необходимо либо перебрать все свойства, либо
использовать Linq.
foreach (GenericType t in blogProperties)
{
if (string.Equals(t.PropertyName, "Message"))
{
blog.Message = GetTypedEdmValue(t.ValueType, t.Value, t.IsNull).ToString();
}
}
return blog;
}
private Comments ReadComment(BaseEntity entity, IEnumerable<GenericType> properties)
{
Comments comment = new Comments();
comment.PartitionKey = entity.PartitionKey;
comment.RowKey = entity.RowKey;
comment.Timestamp = entity.Timestamp;
35
// Чтобы извлечь интересующие нас свойства, необходимо либо перебрать все свойства, либо
использовать Linq.
foreach (GenericType t in properties)
{
if (string.Equals(t.PropertyName, "Comment"))
{
comment.Comment = GetTypedEdmValue(t.ValueType, t.Value, t.IsNull).ToString();
}
else if (string.Equals(t.PropertyName, "Rating"))
{
comment.Rating = (int)GetTypedEdmValue(t.ValueType, t.Value, t.IsNull);
}
}
return comment;
}
private static object GetTypedEdmValue(string type, string value, bool isnull)
{
// Создание значения в зависимости от типа
if (isnull) return null;
if (string.IsNullOrEmpty(type)) return value;
switch (type)
{
case "Edm.String": return value;
case "Edm.Byte": return Convert.ChangeType(value, typeof(byte));
case "Edm.SByte": return Convert.ChangeType(value, typeof(sbyte));
case "Edm.Int16": return Convert.ChangeType(value, typeof(short));
case "Edm.Int32": return Convert.ChangeType(value, typeof(int));
case "Edm.Int64": return Convert.ChangeType(value, typeof(long));
case "Edm.Double": return Convert.ChangeType(value, typeof(double));
case "Edm.Single": return Convert.ChangeType(value, typeof(float));
case "Edm.Boolean": return Convert.ChangeType(value, typeof(bool));
case "Edm.Decimal": return Convert.ChangeType(value, typeof(decimal));
case "Edm.DateTime": return XmlConvert.ToDateTime(value,
XmlDateTimeSerializationMode.RoundtripKind);
case "Edm.Binary": return Convert.FromBase64String(value);
case "Edm.Guid": return new Guid(value);
default: throw new NotSupportedException("Not supported type " + type);
}
}
}
С помощью указанных выше классов можно считать объекты, которые принадлежат разным
типам. Для этого используется следующий код:
DataServiceQuery<BaseEntity> query = ((
from a in context.CreateQuery<BaseEntity>(DataContext.TableName)
where a.PartitionKey == user
select a)) as DataServiceQuery<BaseEntity>;
// выполнение запроса для вызова события OnReadingEntity класса BlogReader
BlogReader reader = new BlogReader(context);
query.Execute().ToList();
// доступ к блогам, хранящимся в BlogReader
foreach (Blogs blog in reader.Blogs.Values)
{
Console.WriteLine("{0}", blog);
foreach (Comments comment in blog.Comments)
{
Console.WriteLine("\t{0}", comment);
}
}
36
8.4 Обновление версий и управление ими
Каждое приложение сталкивается с необходимостью обновления текущей схемы в соответствии
с новыми требованиями.
Важно отметить, что таблица Windows Azure не хранит схему; хранятся только пары <имя,
типизированное значение> для каждого свойства объекта. Поэтому в одной таблице могут
находиться два объекта с разными свойствами. Свойства двух объектов таблицы могут иметь одно
имя, но разные типы. Например, таблица может содержать объект А со свойством «Rating» типа
Int и объект B со свойством «Rating» типа Double. При выполнении запроса сервер игнорирует
объект, если тип свойства в условии запроса не соответствует типу свойства данного объекта.
В приведенном выше примере по запросу для поиска «Rating > 1.2» будет выбран только объект В;
объект А не войдет в результат из-за несоответствия типа свойства.
При обновлении свойства важно, чтобы приложение обрабатывало обе его версии до тех пор,
пока не будут обновлены все имеющиеся в хранилище данные. Далее мы рассмотрим практическую пользу сохранения версий свойств каждого объекта для обработки разных видов
обновлений, а также использование службы ADO.NET для выполнения обновления.
Все приведенные ниже сценарии имеют две возможности выполнения обновления:
1.
2.
ОБНОВЛЕНИЕ ПРИ ДОСТУПЕ, ТО ЕСТЬ ОБНОВЛЕНИЕ ОБЪЕКТА ДО БОЛЕЕ НОВОЙ ВЕРСИИ ПРОИСХОДИТ ПРИ
КАЖДОМ ЕГО ИЗВЛЕЧЕНИИ ДЛЯ ОБНОВЛЕНИЯ .
ОБНОВЛЕНИЕ В ФОНОВОМ РЕЖИМЕ . ОБНОВЛЕНИЕ ВЫПОЛНЯЕТСЯ В ФОНОВОМ РЕЖИМЕ
С ИСПОЛЬЗОВАНИЕМ РАБОЧЕЙ РОЛИ . Д ЛЯ ЭТОГО РАБОЧАЯ РОЛЬ ИЗВЛЕКАЕТ ВСЕ ОБЪЕКТЫ, ОТНОСЯЩИЕСЯ К
СТАРОЙ ВЕРСИИ ( ПРИ ПОМОЩИ СТОЛБЦА ВЕРСИИ ) И ОБНОВЛЯЕТ ИХ .
Как говорилось ранее, при выполнении операций чтения приложение должно обрабатывать обе
версии. Прежде чем обсуждать детали добавления и удаления свойств, рассмотрим важное
логическое свойство IgnoreMissingProperties класса DataServiceContext. Свойство
IgnoreMissingProperties определяет, будет ли служба обработки данных ADO.NET генерировать
исключение, если класс объекта на стороне клиента не определяет свойство, возвращенное
сервером. Приведем пример. Если для объекта Blog сервер возвращает свойство «Rating»,
но свойство «Rating» не определено в клиентском описании класса для объекта Blog, служба
обработки данных ADO.NET формирует исключение, только если свойство IgnoreMissingProperties
имеет значение false.
// УКАЗЫВАЕМ ЭТОТ ПАРАМЕТР, ЧТОБЫ СДЕЛАТЬ ДОПУСТИМЫМ ОТСУТСТВИЕ НЕКОТОРЫХ СВОЙСТВ В КЛИЕНТСКОМ ОПИСАНИИ
ОБЪЕКТА ,
// В РЕЗУЛЬТАТЕ ДОБАВЛЕНИЯ ИЛИ УДАЛЕНИЯ СВОЙСТВ ОБЪЕКТОВ , ХРАНЯЩИХСЯ НА СЕРВЕРЕ .
context.IgnoreMissingProperties = true;
8.4.1 Добавление нового свойства
Если приложение добавляет новое свойство, оно должно быть определено и в описании объекта.
37
Поскольку могут выполняться и предыдущие версии приложения, рекомендуется всегда задавать
свойству IgnoreMissingProperties класса DataServiceContext значение true, как показано выше.
Это обеспечивает чтение объектов клиентами старых версий, не имеющих новых свойств.
В противном случае служба обработки данных ADO.NET будет формировать исключение
для каждого свойства в возвращенном пакете REST, не описанного классом объекта.
8.4.2 Удаление типа свойства
Рассмотрим пример удаления свойства «Rating» объектов блога.
1.
СОЗДАДИМ НОВОЕ ОПИСАНИЕ ОБЪЕКТА BLOG, В КОТОРОМ СВОЙСТВО «RATING » НЕ ОПРЕДЕЛЕНО .
[DataServiceKey("PartitionKey", "RowKey")]
public class BlogV2
{
// <Имя канала>_<9999-ГГГГ>_<13-Месяц>
public string PartitionKey { get; set; }
// Для сортировки по RowKey, начиная с самого нового и заканчивая
// самым старым блогом, RowKey определяем как DateTime.MaxValue.Ticks —
DateTime.UtcNow.Ticks
public string RowKey { get; set; }
// Определяемые пользователем свойства.
public string Text { get; set; }
public DateTime CreatedOn { get; set; }
// ПРИМЕЧАНИЕ. Удаляем это свойство так, чтобы оно также могло быть удалено из
// хранилища.
// public int Rating { get; set; }
}
2.
ТЕПЕРЬ, ЧТОБЫ УДАЛИТЬ СВОЙСТВО, БУДЕМ ИЗВЛЕКАТЬ ОБЪЕКТЫ БЛОГА С СЕРВЕРА И ОБНОВЛЯТЬ
ИХ , ИСПОЛЬЗУЯ НОВОЕ ОПРЕДЕЛЕНИЕ ОБЪЕКТА :
// Принимаем меры для того, чтобы исключение не формировалось при отсутствии описания Rating
в BlogV2
// как обсуждалось выше в разделе 8.4)
context.IgnoreMissingProperties = true;
// Извлечение объекта блога в BlogV2.
// ПРИМЕЧАНИЕ: Маркеры продолжения опущены для краткости
var blogs =
(from blog in context.CreateQuery<BlogV2>("Blogs")
select blog);
foreach(BlogV2 blog in blogs)
{
context.UpdateObject(blog);
}
3.
ВЫЗЫВАЕМ SAVECHANGES С SAVECHANGES OPTIONS .REPLACE ONUPDATE , ЧТОБЫ ВСЕ ОТСУТСТВУЮЩИЕ СВОЙСТВА
БЫЛИ УДАЛЕНЫ И НА СЕРВЕРЕ .
// В результате вызова SaveChangesOptions.ReplaceOnUpdate на сервер отправляется запрос
"PUT"
// вместо запроса "MERGE", что обеспечит удаление всех отсутствующих свойств.
38
context.SaveChanges(SaveChangesOptions.ReplaceOnUpdate);
8.4.3 Изменение типа свойства
Если необходимо изменить тип свойства, можно создать новое свойство заданного типа с новым
именем, скопировать данные из старого свойства в новое, затем удалить старое свойство.
Для этого используются приемы, описанные выше в разделах 8.4.1 и 8.4.2.
9 Рекомендации по использованию таблицы Windows Azure
Этот раздел содержит практические рекомендации, позволяющие сделать приложение
надежным, масштабируемым и гибким.
9.1 Создание таблицы
Логика создания таблицы должна быть отделена от основной бизнес-логики приложения.
В качестве примера рассмотрим приложения, в которых таблица создается перед каждой
вставкой. Чтобы избежать повторного создания таблицы, в качестве индикатора используются
исключения «Table already exists» («Таблица уже существует»). Большое количество лишних
обращений к хранилищу приводит к ненужным транзакциям и к дополнительным расходам.
Для создания таблицы рекомендуется использовать отдельный сценарий при первой настройке
службы, при запуске роли или при возврате ошибки «TableNotFound» («Таблица не найдена»)
с HTTP-кодом состояния 404. Логика создания таблицы не должна строиться на исключении «Table
already exists»; перед созданием таблицы должна выполняться попытка извлечения этой таблицы.
Обратите внимание: после удаления таблицы таблица с таким же именем не может быть создана
еще в течение примерно 30 секунд, пока не будет выполнена сборка мусора.
9.2 Асинхронная версия API служб обработки данных ADO.NET
При применении асинхронных API ввода-вывода, ЦП используется более продуктивно,
чем при блокировке приложения в ожидании ответа сервера. Службы обработки данных ADO.NET
обеспечивают выполнение асинхронных версий операций CRUD. Для повышения производительности рекомендуется применять асинхронные версии. Например, для выполнения запроса лучше
использовать метод BeginExecute класса DataServiceContext.
9.3 Настройки DataServiceContext


ПРИ СБОЕ ОПЕРАЦИИ ОБНОВЛЕНИЯ , УДАЛЕНИЯ ИЛИ ДОБАВЛЕНИЯ ИЗМЕНЕНИЕ ВСЕ РАВНО ОТСЛЕЖИВАЕТСЯ И
ПОСЛЕДУЮЩИЙ SAVE C HANGES ПОВТОРИТ ЗАПРОС. РЕКОМЕНДУЕТСЯ ИСПОЛЬЗОВАТЬ НОВЫЙ
DATA SERVICE CONTEXT ИЛИ В ЯВНОЙ ФОРМЕ ОТСОЕДИНИТЬ ОБЪЕКТ, ДЛЯ КОТОРОГО ПРОИЗОШЕЛ СБОЙ .
ЧТОБЫ ОБЕСПЕЧИТЬ ЛУЧШУЮ ОБРАТНУЮ СОВМЕСТИМОСТЬ , РЕКОМЕНДУЕТСЯ ВСЕГДА ПРИСВАИВАТЬ СВОЙСТВУ
IGNORE MISSING PROPERTIES ДЛЯ КОНТЕКСТА ДАННЫХ ЗНАЧЕНИЕ «TRUE ».
39
9.4 Схема секционирования
Рекомендуется выбирать схему секционирования, исходя из производительности,
масштабируемости и возможности расширения в будущем. Более подробно эти вопросы
рассматриваются в разделах 3 и 8.3.
9.5 Безусловные обновления и удаления
Теги ETag можно рассматривать как версии объектов и использовать для контроля взаимосовместимости с помощью заголовка If-Match при обновлениях и удалениях. Astoria сохраняет ETag,
который передается с полезной нагрузкой каждого объекта. Для получения более детальных
сведений Astoria отслеживает объекты в контексте с помощью context.Entities, который является
коллекцией EntityDescriptors. EntityDescriptor имеет свойство «Etag», значение которого сохраняет
Astoria. При каждой операции обновления или удаления ETag передается на сервер. По умолчанию
Astoria отправляет обязательный заголовок «If-Match» со значением этого ETag. На стороне
сервера таблица Windows Azure проверяет, соответствует ли присланный в заголовке «If-Match»
ETag значению свойства Timestamp в хранилище данных. Если значения совпадают, сервер
продолжает обработку и выполняет обновление или удаление; в противном случае сервер
возвращает код состояния 412 («Необходимое условие не выполнено»), свидетельствующий о том,
что объект, возможно, был изменен. Переданный клиентом символ «*» в заголовке «If-Match»
указывает серверу на необходимость безусловного обновления или удаления, то есть операция
должна быть выполнена независимо от того, был изменен объект в хранилище или нет. Клиент
может выполнить запрос на безусловное обновление или удаление следующим образом:
context.AttachTo("TableName", entity, "*");
context.UpdateObject(entity);
Если этот объект уже отслеживается, клиенту потребуется отсоединить его перед присоединением:
context.Detach(entity);
9.6 Обработка ошибок
При проектировании приложения для работы с таблицей Windows Azure важно правильно
организовать обработку ошибок. В этом разделе рассматриваются вопросы, которые необходимо
учесть при разработке приложения.
9.6.1
Ошибки сети и истечение времени ожидания при выполнении операций
на стороне сервера
Даже если при выполнении операции возникла ошибка сети, внутренняя ошибка сервера
или истекло время ожидания, операция на сервере могла завершиться успешно. В этом случае
рекомендуется выполнить операцию повторно и быть готовым к получению ошибки, обусловленной тем, что предыдущий запрос был завершен успешно. Например, если возник сбой при удалении,
попытайтесь удалить объект еще раз и ожидайте ошибки «не найдено», если предыдущая
операция удаления была завершена успешно. Другой пример — добавление объекта. Объект
может быть вставлен в таблицу даже при превышении времени ожидания. В этом случае
40
приложение должно обработать ошибку «объект уже существует», возникающую при повторном
выполнении операции.
9.6.2
Время ожидания повторных запросов и ошибки «Connection closed by Host»
(«Соединение прервано узлом»)
Запросы, завершившиеся ошибками превышения времени ожидания или «Connection closed
by Host» («Соединение прервано узлом»), возможно, не были обработаны таблицей Windows
Azure. Например, если запрос PUT возвращается в результате превышения времени ожидания,
последующий запрос GET может возвратить либо старое, либо обновленное значение. При получении многократных ошибок превышения времени ожидания необходимо выполнить запрос
повторно, увеличив задержку во избежание дальнейшей перегрузки системы.
9.6.3 Конфликты при обновлениях
Как описано в разделе 5, приложение должно обрабатывать ошибку «Необходимое условие
не выполнено» путем извлечения последней версии объект и формирования последующего
обновления.
9.6.4
Настройка приложения для обработки повторяющихся ошибок превышения
времени ожидания
Ошибки превышения времени ожидания могут происходить из-за ошибок сети при передаче
данных между приложением и ЦОД. Для глобальной сети рекомендуется разбивать операцию
по передаче большого объема данных на несколько мелких операций. Следует предусмотреть,
чтобы после обработки сбоев (превышения времени ожидания) приложение могло продолжить
работу. Ограничение размера для каждого запроса устанавливается, исходя из характеристик
сетевого соединения.
Система обеспечивает масштабирование и обработку большого количества запросов. Однако при
чрезвычайно высокой интенсивности запросов ей приходится выполнять балансировку нагрузки,
в результате чего некоторые из запросов могут завершаться ошибкой превышения времени
ожидания. Снижение частоты запросов может сократить или устранить ошибки такого рода.
Большинство пользователей редко сталкиваются с ошибками превышения времени ожидания.
Тем не менее, если такие ошибки возникают часто или неожиданно — обратитесь к нам через
форумы Windows Azure на MSDN. Мы поможем вам оптимизировать работу с таблицей Windows
Azure и предотвратить появление этих ошибок в вашем приложении.
9.6.5 Обработка ошибок и составление отчетов
API REST выглядит как стандартный HTTP-сервер, взаимодействующий с HTTP-клиентами
(браузерами, клиентскими библиотеками HTTP, прокси, кэшем и т. д.). Для корректной обработки
ошибок HTTP-клиентами каждой ошибке таблицы Windows Azure поставлен в соответствие
определенный HTTP-код состояния.
41
HTTP-коды состояния менее выразительные, чем коды ошибок таблицы Windows Azure,
и содержат меньше информации об ошибке. Тем не менее клиенты, понимающие HTTP,
обычно обрабатывают ошибки правильно.
Поэтому при обработке ошибок или создании сообщений об ошибках таблицы Windows Azure
для конечных пользователей следует использовать коды ошибок таблицы Windows Azure вместе
с HTTP-кодом состояния, это обеспечит максимум информации об ошибке. Кроме того, при отладке
приложения нужно обращать внимание на предназначенный для пользователя элемент
<ExceptionDetails> XML-сообщения об ошибке.
Каждое сообщение об ошибке таблицы Windows Azure содержит следующие HTTP-заголовки.
HTTP/1.1 204 No Content
Content-Length: 0
ETag: W/"datetime'2008-10-01T15%3A27%3A34.4838174Z'"
x-ms-request-id: 7c1b5e22-831d-403c-b88a-caa4443e75cb
Если у вас возникли подозрения по поводу обработки вашего запроса на сервере, обратитесь
на форум MSDN в раздел «Хранилище Windows Azure», используя приведенный выше
x-ms-request-id. Это поможет нам диагностировать проблему.
9.7 Настройка производительности .NET и ADO.NET
Мы собрали самые распространенные проблемы, с которыми сталкиваются пользователи
при работе с таблицей Windows Azure, и привели возможные решения. Некоторые проблемы
связанны со службами обработки данных .NET или ADO.NET (также называемыми Astoria).
9.7.1
Улучшение производительности десериализации при использовании служб
обработки данных ADO.NET
В запросе, выполняемом с использованием служб обработки данных ADO.NET, содержатся
два важных имени: имя CLR-класса объекта и имя таблицы Windows Azure. Мы заметили,
что если эти имена отличаются, на десериализацию каждого объекта, полученного в запросе,
уходит примерно 8–15 мс. Дополнительные сведения см. в разделе 4.10.
9.7.2
Установка значения по умолчанию «2» для HTTP-соединений .NET
Это известная проблема, с которой сталкиваются многие разработчики. По умолчанию HTTP-соединения .NET имеют значение 2, что подразумевает возможность обслуживания только двух одновременных подключений. При попытке формирования большего числа параллельных запросов
появляется сообщение «underlying connection was closed...» («Приоритетное соединение прервано»).
Настройки по умолчанию можно изменить в конфигурационном файле или в коде приложения:
Конфигурационный файл :
<system.net>
<connectionManagement>
42
<add address = "*" maxconnection = "48" />
</connectionManagement>
</system.net>
Код:
ServicePointManager.DefaultConnectionLimit = 48;
Точное значение зависит от конкретного приложения. На сайте http://support.microsoft.com/kb/821268
описана настройка приложений на стороне сервера. Это значение может быть задано для конкретного
URI, если вместо «*» указать этот URI. При задании количества соединений через код вместо
класса ServicePointManager можно использовать класс ServicePoint, например:
SERVICEPOINT MYS ERVICE POINT = SERVICEP OINTMANAGER .FINDS ERVICEP OINT (MYSERVICEU RI);
MYS ERVICE P OINT .C ONNECTION L IMIT = 48;
9.7.3 Отключение «100- continue»
Что такое «100-continue»? При отправке запроса POST/PUT клиент может отложить отправку
полезной нагрузки, передав заголовок «Expect: 100-continue».
1. Сервер будет использовать URI плюс заголовки, чтобы обеспечить возможность вызова.
2. Сервер возвратит клиенту ответ с кодом состояния 100 (Continue).
3. Клиент отправит остальные данные.
Это позволяет уведомить клиента о большинстве ошибок без лишних затрат на отправку всех
данных. Однако ошибки могут возникать и при получении полезной нагрузки на стороне сервера.
При использовании библиотеки .NET HttpWebRequest по умолчанию отправляет «Expect:
100-Continue» для всех запросов PUT/POST (несмотря на то что MSDN предлагает делать
это только для запросов POST).
В таблицах, больших двоичных объектах и очередях Windows Azure ошибки аутентификации
и ошибки, возникающие в результате использования неподдерживаемых команд и пропущенных
заголовков, могут быть протестированы только посредством получения заголовков и URI. Если
пользователь уверен, что приложение Windows Azure не будет формировать некорректные
запросы, он может отключить заголовок «100-continue», чтобы обеспечить отправку сразу всего
запроса. Это особо актуально при передаче небольшой полезной нагрузки в службы таблиц
или очередей. Настройка может быть отключена в коде или в конфигурационном файле.
Код:
// установка служебной точки, если нужно отключить только определенную службу.
ServicePointManager.Expect100Continue = false;
Конфигурационный файл:
<system.net>
<settings>
<servicePointManager expect100Continue="false" />
</settings>
</system.net>
43
Прежде чем отключать заголовок «100-continue», рекомендуется протестировать работу
приложения с этим заголовком и без него.
9.7.4 Отключение Nagle может ускорить операции вставки и обновления
Замечено, что отключение Nagle позволяет существенно уменьшить задержку для операций
вставки и обновления в таблице. Обычно это происходит при наличии большого количества
мелких объектов. Однако известно, что отключение Nagle неблагоприятно сказывается
на пропускной способности; нужно тщательно протестировать приложение, чтобы понять,
имеет ли такое отключение смысл.
Эта настройка может быть отключена в коде или в конфигурационном файле, как показано ниже.
Код:
ServicePointManager.UseNagleAlgorithm = false;
Конфигурационный файл:
<system.net>
<settings>
<servicePointManager expect100Continue="false" useNagleAlgorithm="false"/>
</settings>
</system.net>
9.8 Повторное использование имени удаленной таблицы
На этапе разработки может возникать необходимость регулярного удаления таблиц. В настоящее
время удаленная таблица может быть воссоздана не ранее, чем через 40 секунд. При разработке
приложения рекомендуется использовать в именах таблиц идентификатор или номер версии
в качестве суффикса. Это избавит от вынужденного ожидания при воссоздании таблицы после
ее удаления. Необходимо задать ResolveType, чтобы избежать снижения производительности
клиентской библиотеки службы обработки данных ADO.NET при запросах к таблице, имя которой
не совпадает с именем класса объекта.
10 Резюме
Таблица Windows Azure предоставляет структурированную платформу хранения, позволяя создавать
высокомасштабируемые таблицы в облаке. Система эффективно масштабирует таблицы, автоматически распределяя секции по разным серверам и обеспечивая балансировку нагрузки по мере
увеличения трафика. Поддерживается широкий набор типов данных для свойств и доступ
при помощи служб данных ADO.NET и REST. Служба обработки данных ADO.NET обеспечивает
проверку типа во время компиляции. Это позволяет работать с таблицей Windows Azure
как с любой другой структурированной таблицей. Поддерживаются функции разбиения
на страницы, нежесткой блокировки, оптимистичного параллелизма и маркеров продолжения
для запросов, выполнение которых требует длительного времени. Высокомасштабируемая
система хранения таблиц, обеспечивающая быструю обработку запросов, позволяет
пользователям создавать привлекательные приложения.
44
Download