Знакомясь с новыми (для себя) технологиями и подходами, невольно начинаешь оценивать их применимость на своей текущей или прошлой работе. Готовясь к техническим интервью, помимо прочего я разбирал архитектурный шаблон под названием Circuit Breaker.
Представьте достаточно типовую ситуацию: вы вызываете стороннее API, и тут сторонний сервис падает. Если вы вызываете его редко или вообще разово, то ничего страшного, попробуем как-нибудь ещё раз и потом. Но ситуация становится критической, когда вызовы идут часто, например, небольшими порциями или вообще по активному потоку событий из вашей системы. Часто ошибка не доступности сервиса не будет отдана мгновенно, придётся повисеть на таймауте вашего соединения или дождаться 502 Gateway Timeout от промежуточного сервиса. На выходе получаем резкое увеличение времени обработки одного события, поток исполнения, висящий на бесполезных ожиданиях и ползующие вверх метрики.
А когда сервис таки попытается подняться, вы его ещё и повышенной нагрузкой от накопившийся очереди событий накроете, что может привести к повторному падению ещё непрочуховшегося сервиса.
Что делать?
Такую ситуацию и призван решить шаблон Circuit Breaker.
https://learn.microsoft.com/en-us/azure/architecture/patterns/circuit-breaker
Он назван в честь автоматического выключателя (автомата), призванного разомкнуть короткое замыкание в электрической цепи.
В его основе лежит простенькая машина состояний, через которую должен пройти код, прежде чем вызвать сторонний сервис. Состояний всего три:
1) Закрыт - в этом состоянии CB находится после начала своей работы. Выполнение запроса действительно идёт на сторонний сервис. Если сторонний сервис начинает возвращать ошибки с превышением некоего порога (количество ошибок в диапазон времени или просто по счётчику ошибок), CB переводится в состояние "Открыт". Дополнительно запускается таймер, после которого CB переводится в состояние "Приоткрыт".
2) Открыт - в этом состоянии вместо вызовов реального внешнего сервиса CB возвращает вызывающему коду заранее заготовленную ошибку. Выигрыш здесь в быстроте обработки каждого события (просто бросить исключение намного быстрее, чем дожидаться таймаута от сервиса или соединения), плюс вы даёте возможность стороннему спокойно восстановится.
3) Приоткрыт - в этом состоянии CB позволяет небольшому и ограниченному количеству запросов пойти на реальный сервис, тогда как большая часть запросов все ещё "быстро" получает заранее подготовленную ошибку. Если все (или какой-то процент) запросы, пропущенные до реального сервиса, выполняются успешно, CB переводится в состояние "Закрыт", а счётчик ошибок сбрасывается. Если же внешний сервис продолжает возвращать ошибки, то CB переводится в состояние "Открыт" и снова запускается таймер на перевод в состояние "Приоткрыт".
Разные реализации Circuit Breaker могут использовать различные счётчики и метрики для своей работы, подстраиваясь под специфику внешнего сервиса. Например, таймеры на перевод в приоткрытое состояние имеет смысл устанавливать в соответствии с ожидаемым временем восстановления внешнего сервиса. Да и понимать, какие ошибки "нормальные", а какие свидетельствуют о недоступности сервиса, тоже необходимо для нормального применения шаблона.
Как это реализовать в 1С? Так как для реализации нужно хранить некое состояние (как входящие параметры, так и внутренние счётчики), то имеет смысл в качестве рабочего объекта использовать обработку. А дальше зависит от потребностей.
Если вы постоянно вызываете сервис в один поток в рамках долгоживущего сеанса, то вполне подойдёт хранение промежуточных данных (счетчиков, подготовленных текстов исключений и т.д.) прямо в реквизитах обработки.
Если же обработка распределенная и многопоточная, то промежуточные данные придётся хранить в служебном регистре сведений (или в быстром внешнем key-value хранилище, например Redis), аккуратно расставив блокировки на данные, к которым будет осуществляться конкурентный доступ. >>
Post #341
2.43K