Разработка высокодоступных приложений с геоизбыточностью

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

Служба хранилища Azure предлагает два варианта для геоизбыточной репликации данных: геоизбыточное хранилище (GRS) и хранилище, избыточное между зонами (GZRS). Чтобы использовать параметры геоизбыточности службы хранилища Azure, убедитесь, что ваша учетная запись хранения настроена для геоизбыточного хранилища с доступом на чтение (RA-GRS) или хранилище, геоизбыточное между зонами, с доступом на чтение (RA-GZRS). Если это не так, вы можете узнать подробнее о том, как изменить тип репликации учетной записи хранения.

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

Рекомендации по проектированию приложений

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

Необходимо учитывать эти основные моменты при проектировании приложения для обеспечения его доступности и отказоустойчивости с помощью RA-GRS или RA-GZRS:

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

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

  • Если основной регион становится недоступным, вы можете запустить отработку отказа учетной записи. При выполнении отработки отказа в дополнительный регион нужно изменить записи DNS, указывающие на основной регион, чтобы они указывали на дополнительный регион. После завершения отработки отказа для учетных записей GRS и RA-GRS восстанавливается доступ на запись. Дополнительные сведения см. в статье Аварийное восстановление и отработка отказа учетной записи хранения.

Работа с данными, согласованными по прошествии некоторого времени

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

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

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

Отдельная или совместная обработка служб

Хотя это маловероятно, возможна ситуация, когда одна служба (BLOB-объекты, запросы, таблицы или файлы) станет недоступна, в то время как другие службы будут по-прежнему работать в полнофункциональном режиме. Можно обрабатывать повторные попытки для каждой службы отдельно или универсальным образом сразу для всех служб хранилища.

Например, при использовании очередей и BLOB-объектов в приложении можно добавить отдельный код для обработки ошибок, допускающих повторение, для каждой службы. В это случае ошибка службы BLOB-объектов будет влиять только на часть приложения, которая работает с BLOB-объектами, а очереди по-прежнему будут работать в обычном режиме. Однако если вы решите обработать сразу все повторные попытки службы хранилища, то эта проблема затронет как запросы к BLOB-объектам, так и службы очередей, если какая-либо из этих служб возвратит ошибку, допускающую повтор.

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

Выполнение приложения в режиме только для чтения

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

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

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

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

Обработка обновлений в режиме только для чтения

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

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

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

  • Можно записывать обновления в учетную запись хранения в другом регионе. А когда возобновится работоспособность основного региона, можно объединить эти обновления с основными данными, в зависимости от их структуры. Например, если создаются отдельные файлы с меткой даты и времени в имени, их можно скопировать обратно в основной регион. Это решение может применяться к рабочим нагрузкам, таким как ведение журнала и данные Интернета вещей.

Обработка повторных попыток

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

Запросы на чтение

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

В этом примере обработка повторных попыток для хранилища BLOB-объектов настраивается в классе BlobClientOptions и будет применяться к объекту BlobServiceClient, создаваемому с помощью этих параметров конфигурации. Эта конфигурация предполагает использование сначала основного, а затем дополнительного региона. При этом повторные попытки запросов на чтение из основного региона перенаправляются в дополнительный регион. Этот подход лучше всего подходит, если ожидается, что сбои в основном регионе будут временными.

string accountName = "<YOURSTORAGEACCOUNTNAME>";
Uri primaryAccountUri = new Uri($"https://{accountName}.blob.core.windows.net/");
Uri secondaryAccountUri = new Uri($"https://{accountName}-secondary.blob.core.windows.net/");

// Provide the client configuration options for connecting to Azure Blob storage
BlobClientOptions blobClientOptions = new BlobClientOptions()
{
    Retry = {
        // The delay between retry attempts for a fixed approach or the delay
        // on which to base calculations for a backoff-based approach
        Delay = TimeSpan.FromSeconds(2),

        // The maximum number of retry attempts before giving up
        MaxRetries = 5,

        // The approach to use for calculating retry delays
        Mode = RetryMode.Exponential,

        // The maximum permissible delay between retry attempts
        MaxDelay = TimeSpan.FromSeconds(10)
    },

    // If the GeoRedundantSecondaryUri property is set, the secondary Uri will be used for 
    // GET or HEAD requests during retries.
    // If the status of the response from the secondary Uri is a 404, then subsequent retries
    // for the request will not use the secondary Uri again, as this indicates that the resource 
    // may not have propagated there yet.
    // Otherwise, subsequent retries will alternate back and forth between primary and secondary Uri.
    GeoRedundantSecondaryUri = secondaryAccountUri
};

// Create a BlobServiceClient object using the configuration options above
BlobServiceClient blobServiceClient = new BlobServiceClient(primaryAccountUri, new DefaultAzureCredential(), blobClientOptions);

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

string accountName = "<YOURSTORAGEACCOUNTNAME>";
Uri primaryAccountUri = new Uri($"https://{accountName}.blob.core.windows.net/");
Uri secondaryAccountUri = new Uri($"https://{accountName}-secondary.blob.core.windows.net/");

// Create a BlobServiceClient object pointed at the secondary Uri
// Use blobServiceClientSecondary only when issuing read requests, as secondary storage is read-only
BlobServiceClient blobServiceClientSecondary = new BlobServiceClient(secondaryAccountUri, new DefaultAzureCredential(), blobClientOptions);

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

Запросы на обновление

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

Шаблон автоматического выключения может также применяться для запросов на обновление. Для обработки ошибок запроса на обновление можно задать пороговое значение в коде, например 10 последовательных сбоев, и отслеживать количество сбоев запросов к основному региону. После достижения порогового значения можно переключить приложение в режим только для чтения, чтобы запросы на обновление в основном регионе больше не выдавались.

Как реализовать шаблон автоматического выключателя

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

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

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

В качестве альтернативного подхода можно реализовать собственный компонент мониторинга в приложении. Этот компонент может непрерывно проверять связь с основной конечной точкой хранилища с помощью тривиальных запросов на чтение (например, чтение небольшого BLOB-объекта) для определения его работоспособности. Данный подход может потребовать какое-то количество ресурсов, но не значительное. При обнаружении проблемы, для которой достигнуто пороговое значение неудачных попыток, выполняется переключение на запросы чтения только из дополнительного региона и режим доступа только для чтения. В этом сценарии, когда при проверке связи конечная точка основного хранилища восстановит работоспособность, можно будет переключиться обратно на основной регион и снова разрешить обновления.

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

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

Обработка данных, согласованных в конечном счете

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

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

Время Транзакция Репликация Last Sync Time Результат
T0 Транзакция А:
вставка сущности
сотрудника в основном регионе.
Транзакция А вставлена в основной регион,
но еще не реплицирована.
T1 Транзакция А
реплицирована в
дополнительный регион.
T1 Транзакция А реплицирована в дополнительный регион.
Значение Last Sync Time обновлено.
T2 Транзакция Б:
Update
обновление сущности сотрудника
в основном регионе.
T1 Транзакция Б записана в основной регион,
но еще не реплицирована.
T3 Транзакция В:
Update
administrator
сущности роли в
primary
T1 Транзакция В записана в основной регион,
но еще не реплицирована.
T4 Транзакция В
реплицирована в
дополнительный регион.
T1 Транзакция В реплицирована в дополнительный регион.
Значение Last Sync Time не обновлено, так как
транзакция Б еще не реплицирована.
T5 Чтение сущностей
из дополнительного региона.
T1 Вы получаете устаревшее значение для
сущности employee, так как транзакция Б
еще не реплицирована. Вы получаете новое значение для
сущности administrator role, так как
транзакция В реплицирована. Значение Last Sync Time
все еще не обновлено, так как транзакция Б
не реплицирована. Можно считать сущность
administrator role несогласованной,
так как ее дата и время превышают
значение Last Sync Time.
T6 Транзакция Б
реплицирована в
дополнительный регион.
T6 T6 — всех транзакции вплоть до В
реплицированы, значение Last Sync Time
обновлено.

В этом примере предполагается, что клиент переключается на чтение из дополнительного региона на этапе T5. На этом этапе он может успешно считать сущность administrator role, но она содержит число администраторов, не согласованное с числом сущностей employee, которые на данный момент помечены как администраторы в дополнительном регионе. Клиент может отобразить это значение с риском того, что информация является несогласованной. Кроме того, клиент может попытаться определить, что сущность administrator role является потенциально несогласованной, так как обновления были выполнены не по порядку, и затем информировать пользователей об этом.

Чтобы определить, содержит ли учетная запись хранения потенциально несогласованные данные, клиент может проверить значение свойства Время последней синхронизации. Время последней синхронизации указывает время, когда данные в дополнительном регионе были согласованными в последний раз и когда служба применила все транзакции, предшествующие этому моменту времени. В примере, показанном выше, после того, как служба вставляет сущность employee в дополнительный регион, время последней синхронизации (Last Sync Time) принимает значение T1. Значение T1 сохраняется до момента обновления службой сущности employee в дополнительном регионе, после чего Last Sync Time принимает значение T6. Если клиент получает время последней синхронизации при чтении сущности на этапе T5, то может сравнить его с меткой времени сущности. Если метка времени сущности превышает время последней синхронизации, то сущность является потенциально несогласованной, и вы можете выполнить соответствующее действие. Чтобы использовать это поле, необходимо знать, когда было завершено последнее обновление в основном регионе.

Чтобы узнать, как проверить время последней синхронизации, см. раздел Проверка свойства "Время последней синхронизации" для учетной записи хранения.

Тестирование

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

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

static function OnBeforeResponse(oSession: Session) {
    ...
    if ((oSession.hostname == "\[YOURSTORAGEACCOUNTNAME\].table.core.windows.net")
      && (oSession.PathAndQuery.StartsWith("/employeedata?$filter"))) {
        oSession.responseCode = 502;
    }
}

Вы можете расширить этот пример, перехватывая более широкий диапазон запросов и изменяя responseCode только некоторых из них, чтобы точнее имитировать реальную ситуацию. Дополнительные сведения о настройке сценариев Fiddler см. в разделе Modifying a Request or Response (Изменение запроса или ответа) в документации по Fiddler.

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


Дальнейшие действия

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