Как использовать паттерн circuit breaker для отказоустойчивости при взаимодействии с внешними платежными сервисами
Что проверяют
Понимаете ли вы, что circuit breaker защищает ваш сервис от зависшего партнёра, а не делает платежи надёжнее сам по себе. Следом почти всегда спрашивают, что увидит пользователь при открытом размыкателе и как не списать деньги дважды.
Как отвечать
Начните с механики, у размыкателя три состояния:
- CLOSED: вызовы идут к провайдеру, ошибки и медленные ответы считаются в скользящем окне;
- OPEN: доля сбоев превысила порог, вызовы сразу отклоняются с
CallNotPermittedException, без ожидания таймаута; - HALF_OPEN: после паузы проходят несколько пробных вызовов, и по их итогу размыкатель закрывается или снова открывается.
В Java стандартом стал Resilience4j, Hystrix с 2018 года не развивается. В Spring Boot это @CircuitBreaker(name = "payments", fallbackMethod = "paymentFallback") и настройки в application.yml: failureRateThreshold, slidingWindowSize, minimumNumberOfCalls, waitDurationInOpenState, slowCallDurationThreshold. По умолчанию порог сбоев 50%, пауза в OPEN 60 секунд, и под платёжный API их подбирают заново.
Что отличает ответ именно про платежи:
- что считать сбоем: таймауты и 5xx да, «недостаточно средств» и отказ банка нет. Бизнес-ошибки уводят в
ignoreExceptions, иначе волна отказов по картам откроет размыкатель для всех; - идемпотентность: каждый платёж уходит с ключом идемпотентности. Таймаут не значит, что деньги не списались;
- fallback: не «оплата не прошла», а статус «в обработке», очередь и сверка по вебхуку или запросу статуса. Есть второй провайдер, переключаемся на него;
- связка:
TimeLimiterограничивает ожидание,Retryповторяет только идемпотентные вызовы,Bulkheadне даёт одному провайдеру занять весь пул. На каждого провайдера свой экземпляр размыкателя.
Где спотыкаются
Пишут @CircuitBreaker без name и без fallback. Имя экземпляра обязательно, а без fallback пользователь просто получает исключение.
Ставят Retry на создание платежа без ключа идемпотентности и получают двойные списания ровно тогда, когда провайдер тормозит.
Путают размыкатель с ретраем. Его задача обратная: перестать звать сервис, который не справляется.