Разработка устойчивых приложений без сервера с шаблонами прерывателя схемы
Введение: вызов устойчивости в бессерверных вычислениях
Бессерверные вычисления изменили то, как разработчики создают и развертывают приложения, абстрагируя управление инфраструктурой и предлагая автоматическое масштабирование. Платформы, такие как AWS Lambda, Azure Functions и Google Cloud Functions, позволяют командам сосредоточиться на бизнес-логике, платя только за фактическое использование. Однако эта модель вводит уникальные проблемы устойчивости. Функции являются безгосударственными, эфемерными и часто общаются между распределенными службами - одна неисправная зависимость может вызвать каскад тайм-аутов, повторных запросов и выпадов затрат. Холодные запуски, дросселирование и переходные сетевые сбои являются общими. Для поддержания надежности, не жертвуя преимуществами безсерверных, разработчики должны принять проверенные шаблоны для отказоустойчивости. Один из самых эффективных - шаблон прерывателя схемы.
Система Circuit Breaker действует как предохранительный клапан для вашего приложения. Она контролирует звонки в удаленные службы или ресурсы и предотвращает дальнейшие попытки, когда частота отказов превышает порог. Это защищает систему от перегрузки, позволяет восстановить время отказа служб и обеспечивает чистый запас для пользователей. В этой статье мы расширяем исходный контент, чтобы дать вам всеобъемлющее, действенное руководство по внедрению выключателей в приложениях без сервера, включая подробные объяснения, соображения, касающиеся платформы, примеры кода и лучшие практики.
Понимание шаблона разрушителя цепи в глубине
Модель выключателя схемы была популяризирована Майклом Найгардом в его книге Выпустить его! и позже формализована в облачных шаблонах. Она ведет себя как электрический выключатель: когда схема обнаруживает неисправность (например, короткая), она открывает и останавливает поток тока. В программном обеспечении состояния цепи:
- Закрытые: Запросы обычно поступают в службу нисходящего потока. Выключатель контролирует частоту отказов (например, ошибки HTTP 5xx, тайм-ауты). Если сбои превышают сконфигурированный порог в пределах заданного временного окна (например, 10 сбоев за 30 секунд), схема переходит в Открыть.
- Открытые: Запросы немедленно отклоняются (или используется обратная логика) без вызова неисправной службы. Это предотвращает потерю ресурсов и дает службе нисходящего потока окно восстановления. После периода тайм-аута (например, 30 секунд) схема переходит к Полуоткрытый.
- Полуоткрытый: Ограниченное количество пробных запросов допускается. Если они успешны (в рамках определенных критериев успеха), схема сбрасывается до Закрыт Если сбои сохраняются, она возвращается к Открыт, а тайм-аут часто сбрасывается или увеличивается.
Без этого кратковременное отключение может привести к тому, что все клиенты будут перезапускать одновременно, создавая громоотводное стадо, которое расширяет отключение. Модель Circuit Breaker также обеспечивает раннюю обратную связь с отказом клиентам, что позволяет изящно ухудшать работу - например, возвращать кэшированные данные или дружественное сообщение об ошибке вместо тайм-аута.
Основополагающая статья Мартина Фаулера о разрушителе цепи остается основополагающей ссылкой. Он объясняет, как шаблон интегрируется с другими моделями устойчивости, такими как Retry и Bulkhead.
Ключевые параметры для настройки
Каждая реализация выключателя выдает настраиваемые параметры, которые должны быть скорректированы в соответствии с поведением вашего приложения:
- Порог отказа: Количество последовательных отказов (или скорость по окну) для открытия цепи.
- Продолжительность тайм-аута: Как долго цепь остается открытой до перехода на полуоткрытую.
- Полуоткрытый пробный подсчет: Количество успешных запросов, необходимых для закрытия схемы.
- Классификация ошибок: Какие ответы считаются сбоями? Всего 5xx? Тайм-ауты? 4xx? (обычно только ошибки на стороне сервера).
- Восстановить тайм-аут: Необязательно инкрементный (экспоненциальный обратный выключатель) для предотвращения переключения.
Безсерверные приложения добавляют сложности: поскольку функции эфемерны, вы не можете полагаться на состояние в памяти для схемы. Если экземпляр Lambda выходит из строя, состояние цепи может быть потеряно. Таким образом, часто необходимо внешнее хранилище состояния (DynamoDB, Redis или управляемая служба).
Внедрение прерывателей цепи в безсерверных средах
Внедрение выключателя в архитектуре без сервера требует адаптации шаблона к ограничениям платформы. Мы рассмотрим три основных подхода: использование управляемых функций API, использование сторонних библиотек в вашем функциональном коде и использование служб оркестровки, таких как AWS Step Functions.
Подход 1: API Gateway-Level Throttling и Circuit Breaking
AWS API Gateway может выступать в качестве рудиментарного выключателя, препятствуя запросам на бэкэнд-функцию Lambda. Когда функция возвращает слишком много ошибок 5xx или превышает ограничения параллелизма, API Gateway может быть сконфигурирован для возврата обратного ответа (например, статического сообщения от пользовательского авторизатора или ответа интеграции). Однако это не настоящий государственный выключатель - он полагается на ограничение скорости, а не на мониторинг частоты отказов. Для простых сценариев этого может быть достаточно.
Пример: Установите план использования API Gateway с пределом лопастей и лимитом скорости, который отражает емкость вашего бэкэнда. Когда функция Lambda перегружена, API Gateway немедленно реагирует с , действуя как односторонний выключатель. Но это не различает дросселирование и фактические сбои в обслуживании.
Подход 2: Внеклассные разбивки по схемам с библиотеками
Наиболее гибкий подход заключается в встраивании библиотеки выключателей в ваши функции Lambda. Поскольку функции Lambda не имеют состояния и горизонтально масштабируются, состояние выключателя должно храниться внешне, чтобы каждое вызов могло проверить текущее состояние. Общий шаблон использует Amazon DynamoDB (или Redis с ElastiCache), чтобы сохранить состояние цепи через вызовы функций.
Для Node.js библиотека Opossum является широко используемым выключателем. Она поддерживает функции резервного копирования, тайм-аута и порога громкости. Вот упрощенная реализация, адаптированная для AWS Lambda:
const CircuitBreaker = require('opossum');
const AWS = require('aws-sdk');
const dynamo = new AWS.DynamoDB.DocumentClient();
const circuitBreakerState = {
state: 'CLOSED',
failureCount: 0,
lastFailureTime: null
};
// Persist state in DynamoDB after each transition
async function persistState(newState) {
await dynamo.put({
TableName: 'CircuitBreakerState',
Item: { serviceId: 'payment-service', ...newState }
}).promise();
}
async function loadState() {
const data = await dynamo.get({
TableName: 'CircuitBreakerState',
Key: { serviceId: 'payment-service' }
}).promise();
return data.Item || circuitBreakerState;
}
// The actual downstream call
async function callPaymentService(payload) {
const http = require('axios');
const response = await http.post('https://payment.example.com/charge', payload);
return response.data;
}
// Circuit breaker options
const options = {
errorThresholdPercentage: 50,
resetTimeout: 30000,
volumeThreshold: 10
};
// Create breaker with external state integration (simplified)
const breaker = new CircuitBreaker(callPaymentService, options);
breaker.fallback(() => ({ error: 'Payment service unavailable, order processed in offline mode' }));
exports.handler = async (event) => {
// Load state from DynamoDB and update breaker
const savedState = await loadState();
// Opossum doesn't natively restore state; you'd need to implement a wrapper.
// For brevity, assume the breaker is fresh per function invocation but uses external checks.
// In production, use a shared cache with TTL instead of per-invocation state load.
return breaker.fire(event.body);
};
Этот пример не полностью интегрирован для ясности. На практике вам нужно будет синхронизировать состояние выключателя во многих одновременных вызовах функций с использованием условных записей в DynamoDB (оптимистическая блокировка), чтобы избежать условий гонки. Для сценариев с высокой пропускной способностью экземпляр Redis (например, с использованием ElastiCache Serverless) часто более эффективен.
Подход 3: Функции шагов AWS - разбивка цепи на уровне оркестровки
Для многоступенчатых рабочих процессов (например, проверка электронной коммерции) AWS Step Functions может моделировать выключатель схемы как машину состояния. Состояние может проверять счетчик или флаг, хранящийся в таблице DynamoDB. Если количество отказов превышает порог, рабочий процесс перенаправляет на обратный путь (например, на электронную почту администратора, очередь для ручной обработки). Это обеспечивает выключатель более высокого уровня, который охватывает несколько вызовов службы.
Пример: Функция шага, которая вызывает две службы нисходящего потока. После сбоя она увеличивает счетчик DynamoDB. Перед каждым последующим вызовом Функция шага читает счетчик. Если он превышает 5, рабочий процесс сразу же принимает обратный путь. Это фактически выключатель на уровне рабочего процесса.
Бессерверные задачи и решения
- Холод начинается: Состояние выключателя цепи должно выдержать переработку экземпляра функции. Используйте внешнее состояние с коротким TTL для автоматического закрытия цепи после периода отсутствия активности.
- Конкурентность: Многие экземпляры функций могут одновременно проверять и обновлять состояние. Используйте оптимистичную блокировку (выражения состояния ДинамоДБ) или атомные схемы приращения/уменьшения.
- Стоимость: Каждый государственный чек добавляет затраты на чтение/запись.Состояние кэша в памяти с коротким истечением срока действия (например, 1 секунда) в одном и том же экземпляре функции для уменьшения показаний DynamoDB, но принимает возможную согласованность.
- Гранулярность тайм-аута: Функции Lambda имеют максимальное время вызова (15 минут). Тайм-ауты выключателя схемы должны быть намного короче (секунды), чтобы избежать удержания открытой функции.
Преимущества использования Circuit Breakers
Преимущества выходят далеко за рамки основ. Давайте рассмотрим каждое преимущество в безсерверном контексте:
Улучшенная устойчивость – предотвращение каскадных сбоев
Безсерверные цепи хрупки. Если служба А вызывает В, а В вызывает С, и С выходит из строя, сбой распространяется. Выключатель на вызове В к С заставит В открыть свою цепь после нескольких сбоев. Теперь запросы от А к В сразу же отклоняются с запасом, не позволяя В исчерпать свой предел параллелизма и стать узким местом. Это изолирует ошибку от ее происхождения.
Быстрое восстановление – самоисцеление без вмешательства
Когда цепь открыта, неисправная служба получает период отдыха. Никаких запросов не отправляется, что позволяет ей восстановиться (например, перезапустить, очистить утечку памяти или перенастроить). Полуоткрытое состояние периодически проверяет службу. Как только она успешно реагирует, цепь закрывается автоматически. Это самоисцеление жизненно важно для бессерверных, где отладка живых функций затруднена.
Улучшенный пользовательский опыт — благодатная деградация
Вместо того, чтобы показывать общую страницу «Ошибка сервера» или вращающийся загрузчик, вы можете вернуть устаревшие данные, упрощенную версию функции или дружественное сообщение. Например, служба рекомендаций продукта может использовать выключатель: при открытии страница продукта показывает «Рекомендации временно недоступны», а не полностью выходит из строя.
Экономия средств – избегание ненужных вызовов
Цена без сервера основана на запросах и продолжительности. Когда служба нисходящего потока выходит из строя, продолжая называть ее пустой тратой денег. Каждый вызов вашей функции, которая сразу выходит из строя (или приводит к тайм-ауту, ожидающему нисходящего потока), все еще стоит. Выключатель останавливает эти вызовы, снижая затраты во время окон сбоев.
Лучшие практики для развертывания разрушителей цепи
Внедрение выключателя не является универсальной деятельностью. Используйте эти методы для максимизации эффективности в среде без сервера.
Установите соответствующие пороги отказа и тайм-ауты
Например, если ваша служба нисходящего потока нацелена на 99,9% времени безотказной работы, порог 5 сбоев в минуту может быть слишком чувствительным (он может открываться во время незначительных всплесков). Начните с более высокого порога (например, 20% частоты ошибок в течение 1-минутного окна) и настройте с использованием данных мониторинга. Тайм-ауты должны быть немного длиннее, чем обычное время отклика службы нисходящего потока, но короче, чем общее время ожидания вашей функции.
Реализация механизмов Fallback
Каждый запрос с открытым замыканием должен иметь обратный эффект.
- Восстановление кэшированных данных (из ElastiCache, CloudFront или базы данных).
- Оформить запрос на более позднюю обработку (например, SQS DLQ).
- Возврат значения по умолчанию или статического ответа.
- Перенаправить на деградированную версию функции (например, отключить персонализацию).
Откаты должны быть идемпотентными, где это возможно, особенно для записей.
Мониторинг и состояние лог-схемы
Приборы выключателя для регистрации каждого состояния изменения и метрики отказа. Используйте CloudWatch Metrics (например, пользовательские метрики для открытого счета схемы, полуоткрытые испытания, использование запасных частей). Установите тревогу: если цепь остается открытой в течение длительного периода, уведомите операции. Также регистрируйте причину отказа - тайм-аут, код ошибки и т. Д. - чтобы помочь отладке.
Совместите с другими моделями устойчивости
- Повтор: Используйте схему повторного запуска внутри выключателя, но с экспоненциальным выключением и дрожанием.Сам выключатель не должен повторяться; вместо этого вызов клиента обернут политикой повторного запуска (например, встроенными повторными запросами AWS SDK).Выключатель открывается после того, как все повторные попытки не удались.
- Булхед: Изолируйте ресурсы по функции или сервису. Например, зарезервируйте предел параллелизма для критических и некритических функций. Когда цепь открывается, она снижает нагрузку на выходящий из строя сервис, не позволяя ему воздействовать на другие части системы.
- Timeout: Всегда устанавливайте тайм-аут на вызовах ниже по течению — короче окна отказа выключателя. Это предотвращает длительные висячие запросы от перекоса счета отказа.
- Конечные точки проверки здоровья: Используйте фоновый процесс (например, запланированное событие CloudWatch) для периодического вызова конечной точки проверки здоровья. Если проверка здоровья не удается, предварительно откройте цепь до того, как пользователи затронуты.
Испытайте свой выключатель на неудачу
Инженерия хаоса - ваш друг. Используйте такие инструменты, как AWS Fault Injection Simulator (FIS), чтобы вводить сбои в ваши службы нисходящего потока и наблюдать за поведением выключателя.
- Схема открывается в ожидаемые сроки.
- Резервы выполняются правильно.
- Схема восстанавливается (полуоткрытая, затем закрытая) после устранения неисправности.
- Никаких ложных срабатываний при нормальных пиках нагрузки не происходит.
Тестирование в условиях постановки, отражающих производство, имеет важное значение. Документируйте ожидаемое поведение и регулярно выполняйте сверла.
Используйте внешний государственный магазин с TTL
В бессерверной системе нельзя полагаться на локальную память при вызовах. Используйте DynamoDB, ElastiCache для Redis или сделанную службу, такую как Eureka. Установите Time-to-Live (TTL) в государственной записи, чтобы, если ваша функция неактивна в течение длительного периода, схема автоматически сбрасывалась на закрытую. Это предотвращает застойное открытое состояние от блокировки трафика после восстановления службы.
Заключение
Поскольку бессерверная система становится основой современных приложений, такие модели устойчивости, как Circuit Breaker, больше не являются обязательными — они необходимы для контроля затрат, времени безотказной работы и удовлетворенности пользователей.Понимая государственную машину, правильно реализуя ее в рамках ограничений вашей бессерверной платформы (API Gateway, функциональный код или функции шагов) и следуя лучшим практикам мониторинга и тестирования, вы можете создавать системы, которые изящно ухудшаются при сбое и восстанавливаются без ручного вмешательства.
Паттерн выключателя цепи - это лишь одна часть головоломки устойчивости. Объедините ее с повторами, переборками, проверками здоровья и всеобъемлющей наблюдаемостью, чтобы создать действительно надежные архитектуры без серверов. Начните с малого, внимательно следите и повторяйте.