Pull to refresh
0
Microsoft
Microsoft — мировой лидер в области ПО и ИТ-услуг

Организация одновременного доступа к данным в облачном хранилище Microsoft Azure Storage

Reading time 13 min
Views 6.3K
Original author: Jason Hogg


В современных веб-приложениях часто возникают ситуации, когда несколько пользователей одновременно работают с одними и теми же данными.

Чтобы обеспечить успешное выполнение действий каждого из пользователей, разработчикам приложений необходимо тщательно продумывать обработку и реализацию таких сценариев, особенно в тех случаях, когда действительно необходима одновременная обработка данных группой пользователей.

Наиболее часто разработчики используют следующие три стратегии по управлению одновременным доступом к данным:

  1. Оптимистичный параллелизм (Optimistic concurrency)

    Приложение обновит данные только после того, как проверит их на наличие изменений с момента последнего обращения к ним.

    Например, два пользователя просматривают одну и ту же wiki-страницу, а затем одновременно решают ее обновить.

    В этом случае вики-платформа должна обеспечить процесс, при котором обновление второго пользователя не заместит обновление первого, и оба пользователя поймут было ли произведено действие каждого из них.

    Эта стратегия используется в веб-приложениях чаще всего.

  2. Пессимистичный параллелизм (Pessimistic concurrency)

    В этом случае, приложение блокирует обновляемые данные, и пока блокировка не будет снята, т.е. пока первый пользователь не закончит редактирование данных, доступ к ним будет ограничен для других пользователей.

    Например, в master/slave сценариях репликации данных, где только мастер выполняет обновления, как правило, только он может установить долговременную блокировку данных, для предотвращения редактирования данных кем-либо еще.

  3. «Выигрывает последний» (Last writer wins)

    Такой подход позволяет выполнять любые операции с данными, без поверки данных на обновление с момента последнего обращения приложения к ним.

    Эта стратегия (или отсутствие формальной стратегии), как правило, применяется там, где данные распределены таким образом, что вероятность доступа нескольких пользователей к одному и тому же разделу исключена.

    Также описанный выше способ полезен при обработке кратковременных потоков данных.

В данной статье разговор пойдет о том, как платформа Azure Storage упрощает разработку приложений, использующих хранилища данных, предоставляя поддержку всех трех стратегий организации одновременного доступа.

Azure Storage – упрощает облачную разработку


Azure Storage поддерживает все три стратегии одновременного доступа к данным, хотя и существуют некоторые особенности поддержки оптимистичного и пессимистичного параллелизма, так как изначально хранилища были спроектированы для модели строгого параллелизма, гарантирующей, что в момент, когда сервис хранилища завершает операции добавления и редактирования данных, все последующие обращения к этим данным обнаружат последнее обновление.

Платформы хранилищ, использующие модель согласованности, содержат лаг – задержку между периодом, когда осуществляется запись одним пользователем и периодом, когда обновленные данные видны другим пользователям, что приводит к усложнению разработки клиентских приложений из-за необходимости предотвращать несоответствия в данных для конечных пользователей.

В дополнение к выбору подходящей стратегии доступа к данным, разработчики должны обратить внимание на то, как платформа хранилища организует изоляцию изменений – частичные изменения одних и тех же объектов в транзакциях.

Сервис Azure Storage использует изоляцию снэпшотами для обеспечения возможности одновременного чтения и записи в пределах одного раздела.

В отличие от других уровней изоляции, изоляция снэпшотами гарантирует, что все операции чтения видят согласующийся снэпшот данных даже в момент происходящих обновлений, особенно при возвращении последних сохраненных значений во время исполнения транзакции обновления.

Организация одновременного доступа к BLOB-объектам


Вы можете выбрать, какую стратегию параллелизма использовать для управления доступом к блоб-объектам и контейнерам в сервисе блобов.

Если вы не укажете стратегию явно, то по умолчанию будет использоваться стратегия «Выигрывает последний».

Оптимистичный параллелизм для блобов и контейнеров


Сервис хранилища присваивает идентификатор для каждого хранящегося объекта. Этот идентификатор обновляется каждый раз при выполнении операции обновления над объектом. Идентификатор возвращается клиенту как часть ответа на запрос HTTP GET, используя заголовок ETag (тег сущности), определенный в HTTP протоколе.

Пользователь, выполняющий обновление на таком объекте, может отправить вместе с оригинальным ETag условный заголовок, для того чтобы убедиться, что обновление произойдет только при определенных условиях — в данном случае условием является заголовок «If -Match», который требуется сервисом хранилища.

Ниже в общих чертах представлен план этого процесса:

  1. Получаем блоб из сервиса хранилища, ответ включает параметр заголовка HTTP ETag, который идентифицирует текущую версию объекта в сервисе хранилища.
  2. Когда вы обновляете блоб, включите полученный параметр ETag из предыдущего шага в условный заголовок запроса If-Match, который вы отправляете сервису.
  3. Сервис сравнивает значение ETag в запросе с текущим значением ETag в блобе.
  4. Если текущее значение ETag блоба отличается от ETag в заголовке запроса If-Match, то сервис возвращает клиенту ошибку 412. Это указывает клиенту на то, что другой процесс обновил блоб, с тех пор, как клиент его запрашивал.
  5. Если текущее значение ETag не отличается от значения ETag в заголовке запроса, то сервис выполнит запрошенную операцию и обновит текущее значение ETag блоба, чтобы показать, что было обновление данных.

Представленный ниже С# cниппет показывает простой пример создания условия If-Match c помощью класса AccessCondition, основанного на значении ETag, которое было получено из свойств, ранее извлеченного или добавленного блоба.

Затем это условие использует объект AccessCondition во время обновления блоба: объект AccessCondition добавляет заголовок If-Match в запрос.

Если другой процесс обновил блоб, то сервис блобов вернет сообщение HTTP 412 (Precondition Failed).

Полный пример можно скачать здесь.

// Получает Etag из результата, ранее произведенной блоб-операции UploadText
string orignalETag = blockBlob.Properties.ETag;
// Этот код имитирует обновление сторонним процессом
string helloText = "Blob updated by a third party.";
// Не предоставлено никакого etag, поэтому первоначальный блоб обновлен (это сгенерирует новый etag)
blockBlob.UploadText(helloText);
Console.WriteLine("Blob updated. Updated ETag = {0}", blockBlob.Properties.ETag);
// Теперь пробуем обновить блоб, используя первоначальный ETag, полученный при создании блоба 
try
{
     Console.WriteLine("Trying to update blob using orignal etag to generate if-match access condition");
     blockBlob.UploadText(helloText,accessCondition:
     AccessCondition.GenerateIfMatchCondition(orignalETag));
}
catch (StorageException ex)
{
     if (ex.RequestInformation.HttpStatusCode == (int)HttpStatusCode.PreconditionFailed)
     {
          Console.WriteLine("Precondition failure as expected. Blob's orignal etag no longer matches");
     }
}


Сервис хранилища также поддерживает и другие условные заголовки, такие как If-Modified-Since, If-Unmodified-Since и If-None-Match.

Дополнительная информация документация на MSDN.

В таблице приведены операции над контейнерами, принимающие условные заголовки, такие как If-Match,в запросе и возвращающие ETag в ответе:
Операция Возвращает значение ETag Принимает условные заголовки
Create Container Yes No
Get Container Properties Yes No
Get Container Metadata Yes No
Set Container Metadata Yes Yes
Get Container ACL Yes No
Set Container ACL Yes Yes (*)
Delete Container No Yes
Lease Container Yes Yes
List Blobs No No

(*) Разрешения, определяемые SetContainerACL кэшируются, а их обновление занимает 30 секунд, в течение которых не гарантируется согласованность обновлений.

В таблице приведены операции блобов, принимающие в запросе условные заголовки, такие как If-Match, и возвращающие значение ETag:
Операция Возвращает значение ETag Принимает условные заголовки
Put Blob Yes Yes
Get Blob Yes Yes
Get Blob Properties Yes Yes
Set Blob Properties Yes Yes
Get Blob Metadata Yes Yes
Set Blob Metadata Yes Yes
Lease Blob (*) Yes Yes
Snapshot Blob Yes Yes
Copy Blob Yes Yes (for source and destination blob)
Abort Copy Blob No No
Delete Blob No Yes
Put Block No No
Put Block List Yes Yes
Get Block List Yes No
Put Page Yes Yes
Get Page Ranges Yes Yes

(*) Лизинг блоба не меняет его Etag.

Пессимистичный параллелизм в блобах


Чтобы заблокировать блоб для исключительного использования, применяется механизм лизинга. При использовании лизинга вы указываете его продолжительность: 15 — 60 секунд, либо без окончания, что означает исключительную блокировку. Еще вы можете продлить блокировку, либо разблокировать блоб после завершения работы с ним. Сервис блобов автоматически отключает лизинг, если срок его действия на блобе истек.

Лизинг позволяет использовать различные стратегии синхронизации, включая эксклюзивную запись/ разделенное чтение, эксклюзивную запись / эксклюзивное чтение, разделенную запись / эксклюзивное чтение.

Там, где существует лизинг, сервис хранилища упорядочивает эксклюзивную запись (операции put, set, delete). Для обеспечения эксклюзивности операций чтения, разработчику требуется предусмотреть, чтобы все клиентские приложения использовали идентификатор лизинга, и только один клиент в один момент времени имел подходящий идентификатор лизинга. Операции чтения, которые не включают в себя идентификатор лизинга происходят в разделенном чтении.

В коде ниже (C#) показан эксклюзивный 30 секундный лизинг на блобе, обновление блоба и прекращение лизинга. Если необходимый лизинг на блобе уже установлен, то при попытке установки нового, сервис блобов вернет результат «HTTP (409) Conflict”.

При создании запроса на обновление блоба в службе хранилища, в коде, для информации о лизинге, используется объект AccessCondition.

Полный пример можно скачать здесь.

// Устанавливаем лизинг на 15 секунд
string lease = blockBlob.AcquireLease(TimeSpan.FromSeconds(15), null);
Console.WriteLine("Blob lease acquired. Lease = {0}", lease);

// Обновляем блоб, используя лизинг. Это операция пройдет успешно
const string helloText = "Blob updated";
var accessCondition = AccessCondition.GenerateLeaseCondition(lease);
blockBlob.UploadText(helloText, accessCondition: accessCondition);
Console.WriteLine("Blob updated using an exclusive lease");

//Имитация обновления блоба без лизинга сторонним процессом Simulate third party update to blob without lease
try
{
     // Операция ниже не произойдет, так как не предоставлено соответствующего лизинга
     Console.WriteLine("Trying to update blob without valid lease");
     blockBlob.UploadText("Update without lease, will fail");
}
catch (StorageException ex)
{
     if (ex.RequestInformation.HttpStatusCode == (int)HttpStatusCode.PreconditionFailed)
          Console.WriteLine("Precondition failure as expected. Blob's lease does not match");
     else
          throw;
}


Если произвести операцию записи на блобе с лизингом без передачи идентификатора лизинга, то запрос упадет с ошибкой 412. Обратите внимание — если срок лизинга истечет перед вызовом метода UploadText, а вы все еще передаете идентификатор лизинга, запрос опять же упадет с ошибкой 412.

Для получения информации об управлении продолжительностью лизинга и идентификатором лизинга, посмотрите документацию.

В следующем списке приведены операции блобов, которые могут использовать лизинг для пессимистичного параллелизма:
  • Put Blob
  • Get Blob
  • Get Blob Properties
  • Set Blob Properties
  • Get Blob Metadata
  • Set Blob Metadata
  • Delete Blob
  • Put Block
  • Put Block List
  • Get Block List
  • Put Page
  • Get Page Ranges
  • Snapshot Blob – идентификатор лизинга не требуется
  • Copy Blob – идентификатор требуется, если на блобе установлен лизинг
  • Abort Copy Blob – идентификатор требуется, если на блобе установлен не ограниченный лизинг
  • Lease Blob


Пессимистичный параллелизм для контейнеров


Лизинг на контейнерах предоставляет поддержку той же стратегии синхронизации, что и на блобах (эксклюзивная запись / разделенное чтение, эксклюзивная запись / эксклюзивное чтение и разделенная запись / эксклюзивное чтение), однако, в отличие от блобов, сервис хранилища применяет стратегию эксклюзивности для операций удаления.

Для удаления контейнера с активным лизингом клиент должен включить идентификатор активного лизинга в запрос удаления.

Другие операции над лизинг-контейнером не должны обязательно содержать идентификатор лизинга, и такие операции называются разделяемыми.

Если требуются операции эксклюзивного обновления (put или set) или чтения, то разработчикам нужно, чтобы каждый клиент использовал идентификатор лизинга и только один клиент использовал подходящий на данный момент идентификатор.

Ниже перечислены операции контейнера, которые могут использовать лизинг для пессимистичного параллелизма:
  • Delete Container
  • Get Container Properties
  • Get Container Metadata
  • Set Container Metadata
  • Get Container ACL
  • Set Container ACL
  • Lease Container

  Дополнительная информация:

Организация параллелизма в сервисе Tables


Сервис таблиц во время работы с сущностями по умолчанию использует оптимистичную стратегию одновременного доступа к данным, в отличие от сервиса блобов, где надо явно выбрать использование оптимистичного параллелизма.

Другое различие сервисов таблиц и блобов заключается в том, что с таблицами вы можете управлять поведением при параллельном доступе только для сущностей, в то время как для сервиса блобов вы можете управлять параллелизмом, как при доступе к контейнерам, так и к блобам.

Чтобы использовать оптимистичный параллелизм и проверить, была ли модифицирована сущность другим процессом с момента ее выборки из сервиса таблиц, вы можете использовать полученное во время выборки значение ETag.

План этого процесса представлен ниже:

  1. Извлечение сущности из сервиса хранилища таблиц. Ответ включает значение ETag — текущий идентификатор связанный с сущностью в хранилище.
  2. Когда вы обновляете сущность, включите полученный параметр ETag из предыдущего шага в условный заголовок запроса If-Match, который вы отправляете сервису.
  3. Сервис сравнивает значение ETag в запросе с текущим значением ETag сущности.
  4. Если текущее значение ETag сущности отличается от ETag в заголовке запроса If-Match, то сервис возвращает клиенту ошибку 412. Это указывает клиенту на то, что другой процесс обновил сущность, с тех пор, как клиент ее запрашивал.
  5. Если текущее значение ETag не отличается от значения ETag в заголовке запроса If-Match или If-Match заголовок содержит символ (*), то сервис выполнит запрошенную операцию и обновит текущее значение ETag сущности, чтобы показать, что произошло обновление данных.

Обратите внимание, что в отличие от сервиса блобов сервис таблиц требует чтобы клиент включал заголовок If-Match в запросы обновления. Однако, остается возможность принудительного безусловного обновления (стратегия «выигрывает последний») и обхода проверок при установке клиентом значения заголовка If-Match равном символу (*) в запросе.

Код ниже (C#) демонстрирует сущность customer, созданную или выбранную из имеющихся данных с обновленным адресом email. Первоначальная операция вставки или извлечения хранит значение ETag в объекте customer и, так как пример использует тот же экземпляр объекта во время исполнения операции замены, он автоматически отправляет значение ETag назад в сервис таблиц, позволяя сервису проверить нарушение при одновременном доступе.

Если другой процесс обновил сущность в хранилище таблиц, служба возвращает сообщение со статусом HTTP 412 (Precondition Failed).

Полный пример доступен здесь

try
{
     customer.Email = "updatedEmail@contoso.org";
     TableOperation replaceCustomer = TableOperation.Replace(customer);
     customerTable.Execute(replaceCustomer);
     Console.WriteLine("Replace operation succeeded.");
}
catch (StorageException ex)
{
     if (ex.RequestInformation.HttpStatusCode == 412)
          Console.WriteLine("Optimistic concurrency violation – entity has changed since it was retrieved.");
     else
          throw; 
}


Для явной блокировки проверки одновременного доступа, вы должны установить свойство ETag объекта employee в значение «*» перед проведением операции обновления.

customer.ETag = “*”;

В таблице показано как табличные операции используют значения ETag:
Операции Возвращает значение ETag Требует условные заголовки
Query Entities Yes No
Insert Entity Yes No
Update Entity Yes Yes
Merge Entity Yes Yes
Delete Entity No Yes
Insert or Replace Entity Yes No
Insert or Merge Entity Yes No

Обратите внимание, что операции InsertorReplaceEntity и InsertorMergeEntityне выполняют никаких проверок одновременного доступа, потому что они не отправляют значение ETag в сервис таблиц.

Как правило, разработчики, использующие таблицы, должны полагаться на оптимистичную стратегию одновременного доступа к данным при разработке масштабируемых приложений.

Если возникла необходимость в пессимистичной блокировке, то подходящим вариантом доступа к таблицам для разработчиков может стать выделение специального блоба для каждой таблицы и попытка его лизинга перед каждой операцией на таблице.

Этот подход требует чтобы приложение следило за тем, чтобы любой доступ к таблицам осуществлялся через предварительную попытку лизинга блоба.

Обратите внимание так же, что минимальная продолжительность лизинга составляет 15 секунд, что требует особого внимания при разработке масштабируемых решений.

Дополнительная информация:

Организация параллелизма в сервисе хранилища очередей


Для очередей существует один сценарий, при котором возникает необходимость использования стратегии параллельного доступа – это когда несколько клиентов одновременно извлекают сообщения из очереди. Когда сообщение извлечено из очереди, ответ включает в себя само сообщение и значение pop receipt value, которое требуется для последующего удаления сообщения.

Сообщение не удаляется из очереди автоматически, но, после того как оно было извлечено оно становится для клиентов не видимым на время определенное visibilitytimeout.

Клиент, получающий сообщение, ожидает удаления сообщения в период после того как оно было обработано и до момента, определенного элементом TimeNextVisible в ответе.

Для определения TimeNextVisible значение visibilitytimeout добавляется ко времени извлечения сообщения.

Сервис хранилища очередей не поддерживает оптимистичную или пессимистичную стратегию, поэтому клиенты, обрабатывающие сообщения, извлеченные из очереди, должны обеспечить идемпотентный способ обработки сообщений.

Для операций обновления, таких как SetQueueServiceProperties, SetQueueMetaData, SetQueueACL и UpdateMessage используется стратегия «выигрывает последний».

Дополнительная информация:

Организация параллелизма в сервисе хранилища файлов


Доступ к файловому сервису может быть осуществлён с помощью двух различных протоколов: SMB и REST. Сервис REST не поддерживает оптимистичную или пессимистичную блокировку, и все обновления будут производиться на основании стратегии «выигрывает последний».

Клиенты, использующие SMB, могут применять механизм блокировки на уровне файловой системы для управления доступом к разделяемым (shared) файлам, включая возможность пессимистичной блокировки.

Когда SMB-клиент открывает файл, он определяет параметр доступа к файлу и режим разделяемого доступа. В результате установки опции доступа к файлу в значение «Write» или «Read/Write» и режима разделяемого доступа в значение «None», файл будет заблокирован SMB-клиентом до его закрытия.

Если операция REST производится на файле, заблокированном SMB-клиентом, то REST сервис вернет ошибку 409 с кодом «Sharing Violation».

Когда SMB-клиент открывает файл для удаления, он помечает файл как «ожидающий удаления» до тех пор, пока все другие SMB-клиенты не закроют его. Пока файл отмечен как ожидающий удаления, любые REST-операции на этом файле будут возвращать ошибку 409 с кодом SMBDeletePending. Код ошибки 404 (Not Found) не будет возвращаться, так как существует возможность, что SMB-клиент уберет флаг ожидания удаления перед тем как закрыть файл. Другими словами, код ошибки 404 вернется только тогда, когда файл будет действительно удален.

Обратите внимание, что пока файл находится в состоянии ожидании удаления SMB-клиентом, он не будет включен в результаты выдачи List Files.

надо еще учитывать, что операции REST Delete File и Delete Directory исполняются атомарно и не приводят к установке состояния «ожидающий удаления».

Дополнительная информация:

Заключение


Облачное хранилище Microsoft Azure было спроектировано для удовлетворения потребностей сложных веб-приложений. При этом, от разработчиков не требуется жертвовать или переосмысливать ключевые шаблоны проектирования, такие как одновременный доступ к данным или обеспечение корректного состояния данных. Механизмы их обеспечения включены в само хранилище.

Полный пример приложения, использованного в статье:

Подробная информации об Azure Storage:

Полезные ссылки


Tags:
Hubs:
+10
Comments 0
Comments Leave a comment

Articles

Information

Website
www.microsoft.com
Registered
Founded
Employees
Unknown
Location
США