Повторная попытка антипаттерна Storm
Если служба недоступна или занята, слишком частые попытки подключения со стороны клиентов могут привести к тому, что служба столкнется с трудностями при восстановлении и усугубит проблему. Это также не имеет смысла повторять навсегда, так как запросы обычно действительны только в течение определенного периода времени.
Описание проблемы
В облаке службы иногда сталкиваются с проблемами и становятся недоступными для клиентов или вынуждены регулировать или ограничивать доступ своих клиентов. Хотя это хорошая практика для клиентов — повторно пытаться подключиться к службам, важно, чтобы они не пытались слишком часто или слишком долго. Повторные попытки в течение короткого периода времени вряд ли будут успешными, так как службы, скорее всего, не успеют восстановиться. Кроме того, когда множество попыток подключения выполняются в процессе восстановления, службы могут быть подвержены еще большему стрессу, а повторяющиеся попытки подключения могут даже перегрузить службу и усугубить основную проблему.
В следующем примере показан сценарий, в котором клиент подключается к серверному API. Если запрос не удался, клиент немедленно повторяет попытку и продолжает попытки повторять запрос навсегда. Часто такое поведение является более тонким, чем в этом примере, но тот же принцип применяется.
public async Task<string> GetDataFromServer()
{
while(true)
{
var result = await httpClient.GetAsync(string.Format("http://{0}:8080/api/...", hostName));
if (result.IsSuccessStatusCode) break;
}
// ... Process result.
}
Устранение проблемы
Клиентские приложения должны следовать некоторым передовым методам, чтобы избежать возникновения шторма повторных попыток.
- Ограничьте количество повторных попыток и не продолжайте их в течение длительного времени. Хотя может показаться, что написать цикл
while(true)
легко, вы, вероятно, не захотите реально повторять его в течение продолжительного времени, так как ситуация, приведшая к инициированию запроса, скорее всего, изменилась. В большинстве приложений достаточно повторных попыток в течение нескольких секунд или минут. - Приостановка между повторными попытками. Если служба недоступна, повторная попытка немедленно вряд ли будет успешна. Постепенно увеличивайте время ожидания между попытками, например с помощью экспоненциальной стратегии обратного выхода.
- Грациозно обрабатывать ошибки. Если служба не отвечает, подумайте, имеет ли смысл прервать попытку и сообщить об ошибке пользователю или тому, кто вызывает ваш компонент. При разработке приложения учитывайте эти сценарии сбоев.
- Рекомендуется использовать шаблон автоматического останова, который предназначен специально для предотвращения повторных штормов.
- Если сервер предоставляет заголовок ответа
retry-after
, убедитесь, что вы не пытаетесь повторить попытку до истечения указанного периода времени. - При обмене данными со службами Azure используйте официальные пакеты SDK. Эти пакеты SDK обычно имеют встроенные политики повторных запросов и защиту от создания или участия в нагрузках на систему из-за многократных повторов. Если вы взаимодействуете со службой, которая не имеет пакета SDK или где пакет SDK не обрабатывает логику повторных попыток правильно, попробуйте использовать библиотеку, например Polly (для .NET) или повторных попыток (для JavaScript), чтобы правильно обрабатывать логику повторных попыток и избегать написания кода самостоятельно.
- Если вы работаете в среде, поддерживающей ее, используйте сетку службы (или другой слой абстракции) для отправки исходящих вызовов. Как правило, эти средства, такие как Dapr, поддерживают политики повторных попыток и автоматически следуют передовой практике, например, уменьшая частоту попыток после нескольких неудачных попыток. Такой подход означает, что вам не нужно самостоятельно писать код повторных попыток.
- Рассмотрите возможность пакетной обработки запросов и использование пула запросов, где это доступно. Многие SDK обрабатывают группировку запросов и пул подключений от вашего имени, что приведет к сокращению общего количества попыток исходящего подключения вашего приложения, но необходимо соблюдать осторожность, чтобы не переустанавливать эти подключения слишком часто.
Службы также должны защищать себя от повторных штормов.
- Добавьте уровень шлюза, чтобы вы могли отключить подключения во время инцидента. Это пример шаблона bulkhead. Azure предоставляет множество различных служб шлюза для различных типов решений, включая Front Door, шлюз приложенийи управление API.
- Ограничьте количество запросов на вашем шлюзе, чтобы гарантировать, что ваши внутренние компоненты смогут продолжать работать.
- Если вы ограничиваете пропускную способность, отправьте обратно заголовок
retry-after
, чтобы помочь клиентам понять, когда повторно установить свои подключения.
Соображения
- Клиенты должны учитывать тип возвращенной ошибки. Некоторые типы ошибок не указывают на сбой службы, но вместо этого указывают на то, что клиент отправил недопустимый запрос. Например, если клиентское приложение получает ответ об ошибке
400 Bad Request
, повторная попытка того же запроса, вероятно, не поможет, так как сервер сообщает о том, что запрос недопустим. - Клиенты должны учитывать логичное время для повторной попытки подключения. Время, для которого необходимо повторить попытку, будет обусловлено вашими бизнес-требованиями и может ли вы разумно распространить ошибку обратно пользователю или вызывающей стороне. В большинстве приложений достаточно повторных попыток в течение нескольких секунд или минут.
Как обнаружить проблему
С точки зрения клиента симптомы этой проблемы могут включать очень длинное время отклика или обработки, а также телеметрию, которая указывает на повторяющиеся попытки повторить подключение.
С точки зрения службы симптомы этой проблемы могут включать большое количество запросов от одного клиента в течение короткого периода времени или большое количество запросов от одного клиента при восстановлении от сбоев. Симптомы также могут включать трудности при восстановлении сервиса или непрерывные каскадные сбои сервиса сразу после устранения неисправности.
Пример диагностики
В следующих разделах показан один из подходов к обнаружению потенциального шторма повторных запросов как на стороне клиента, так и на стороне сервиса.
Идентификация на основе телеметрии клиента
Azure Application Insights записывает данные телеметрии из приложений и делает данные доступными для запроса и визуализации. Исходящие подключения отслеживаются как зависимости, и информация о них может быть доступна и визуализирована, чтобы определить, когда клиент выполняет большое количество исходящих запросов к той же службе.
Следующая диаграмма была взята из вкладки "Метрики" на портале Application Insights, отображающая сбои зависимостей, метрика, показывающая разделение по имени удаленной зависимости. Это иллюстрирует сценарий, в котором было большое количество (более 21 000) неудачных попыток подключения к зависимости в течение короткого времени.
Определение телеметрии сервера
Серверные приложения могут обнаруживать большое количество подключений из одного клиента. В следующем примере Azure Front Door выступает в качестве шлюза для приложения, а настроено для логирования всех запросов в рабочую область Log Analytics.
Следующий запрос Kusto можно выполнить в Log Analytics. Он определит IP-адреса клиента, которые отправили большое количество запросов приложению в течение последнего дня.
AzureDiagnostics
| where ResourceType == "FRONTDOORS" and Category == "FrontdoorAccessLog"
| where TimeGenerated > ago(1d)
| summarize count() by bin(TimeGenerated, 1h), clientIp_s
| order by count_ desc
При выполнении этого запроса во время шторма повторных попыток подключения отображается большое количество попыток подключения с одного IP-адреса.