awesome-everything EN
↑ Обратно к восхождению

Архитектура бэкенда

На масштабе: состояние на инстанс, штормы ретраев и координированный сброс

Суть Всё до сих пор предполагало один процесс. На многих инстансах брейкер становится догадкой на инстанс без общего взгляда, ретраи множатся в усиление вызовов, half-open пробы стадятся, а стабилен лишь координированный сброс. Устойчивость как свойство флота.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на senior-высоте — в орбите
◷ 18 min

Каждый урок до сих пор тихо предполагал один процесс: один брейкер, один пул потоков, один счётчик. Прод — это пятьдесят инстансов за балансировщиком, и предположения ломаются неочевидно. У каждого инстанса свой брейкер со своим счётом, поэтому инстанс A может быть сработавшим, пока инстанс B всё ещё долбит умирающую зависимость — общего вердикта нет. Хуже того, у тебя почти никогда не один брейкер; под ним ретраи, а ретраи стакаются мультипликативно по цепи вызовов. У книги Google SRE есть канонический ужас: три слоя сервиса, каждый ретраит до четырёх раз, превращают один запрос пользователя в 4 × 4 × 4 = 64 вызова к нижнему сервису — усилитель запросов, обращающий маленькое колебание даунстрима в самоподдерживающийся шторм ретраев. Устойчивость на масштабе — не одно-процессная машина состояний прошлых пяти уроков; это свойство флота, и у флота есть режимы отказа, которых одиночный брейкер никогда не видит.

Состояние на инстанс: пятьдесят брейкеров, нет общего вердикта

Состояние брейкера живёт в процессе, что им владеет. С пятьюдесятью инстансами у тебя пятьдесят независимых брейкеров, каждый считает только вызовы, что он сделал. У этого прямые следствия. Инстанс A может увидеть достаточно отказов, чтобы сработать, пока инстанс B, отобравший более удачный срез трафика, остаётся closed и продолжает звать больную зависимость. Общего вердикта нет — зависимость получает не «брейкер», а пятьдесят мнений. Сразу после деплоя или скейл-апа свежий инстанс стартует с пустым окном и closed-брейкером, поэтому он обязан переоткрыть сбой с нуля, проваливая реальные вызовы, хотя сорок девять его собратьев уже знают.

Ты можешь централизовать состояние брейкера в общем хранилище (Redis, сервис координации), чтобы флот делил один счёт — но теперь проверка брейкера это сетевой вызов на горячем пути, общее хранилище — новая зависимость, что сама может упасть, и ты сменял локальное-но-устаревшее на общее-но-связанное. Большинство продакшен-систем держат состояние брейкера локальным намеренно и принимают размытость, потому что брейкер — это статистическое защитное устройство, а не точный выключатель: пятьдесят инстансов, каждый отбирающий зависимость, в сумме сойдутся на срабатывании во время реального сбоя. Локальное состояние — прагматичный дефолт; общее состояние — намеренный, дорогой выбор.

Усиление ретраев: множитель под брейкером

Опасное взаимодействие — не брейкер, это ретрай, наслоённый под ним. Ретраи множатся вдоль цепи вызовов. Если сервис A зовёт B зовёт C, и каждый ретраит 4× при отказе, один входящий запрос к A может стать 64 запросами у C. Когда C уже в беде, это худший возможный ответ: слой, который должен снизить нагрузку, вместо этого её множит, и ретраи каждого слоя кормят слой ниже. Это шторм ретраев (или усиление ретраев), и так короткий сбой даунстрима становится длительной, самонанесённой перегрузкой, что переживает исходную неисправность.

Сеньорные фиксы слоисты и конкретны:

  • Ретрай на одном уровне, не на каждом. Гайд SRE — ретраить близко к отказу или наверху, но не на каждом слое разом — стакнутые ретраи и есть то, что производит 64×. Выбери слой; пусть остальные отказывают быстро.
  • Ограничь абсолютную долю ретраев, не только счёт на вызов. Паттерн Google — бюджет на процесс — «60 ретраев в минуту» как лимит на весь сервер — чтобы ретраи не масштабировались с трафиком в шторм.
  • Экспоненциальный бэкофф с джиттером. Ретраи с фиксированным интервалом от многих клиентов ресинхронизируются в волны; библиотека билдеров AWS показывает, что добавление случайного джиттера к экспоненциальному бэкоффу разводит ретраи и есть то, что реально ломает волну. Одного бэкоффа мало — без джиттера каждый клиент отступает на одну и ту же величину, и все возвращаются вместе.
  • Брейкеры сидят над ретраями. Брейкер — это выключатель на уровне цепи: раз он open, ретраи под ним не выстреливают вообще, потому что весь вызов короткозамкнут. Брейкер — то, что делает политику ретраев безопасной: он ограничивает шторм.

Стадность: half-open и восстановление это события флота

У состояния half-open из второго урока есть распределённый режим отказа. Когда пятьдесят инстансов сработали примерно в один момент, их таймеры остывания тоже истекают примерно в один момент, поэтому все пятьдесят входят в half-open и выстреливают свои пробные вызовы вместе — синхронизированное громовое стадо на зависимость, что едва-едва вернулась. Фикс — тот же примитив, что и бэкофф ретраев: джиттери тайминг проб, чтобы пробы флота размазались по окну вместо приземления в один всплеск. Токен-бакет-троттлинг ретраев у AWS — родственная идея на стороне ретраев: каждый клиент держит токен-бакет, что разрешает ретраи лишь пока есть токены, поэтому отказ масштаба флота не может перевестись в ретрай-всплеск масштаба флота.

Почему это работает

Почему джиттер так важен, что и ретраям, и half-open пробам он нужен — разве экспоненциальный бэкофф уже не разводит нагрузку во времени? Бэкофф разводит попытки отдельного клиента по растущим интервалам, но он ничего не делает с корреляцией между клиентами. Представь сбой: зависимость икает и, в те же несколько миллисекунд, тысяча клиентов все получают ошибку и все стартуют идентичное расписание бэкоффа — ждать 1 с, потом 2 с, потом 4 с. Поскольку они стартовали вместе и расписание детерминировано, они и ретраят вместе: громовая волна на t=1 с, ещё одна на t=3 с, ещё на t=7 с. Зависимость, пытаясь восстановиться, бьётся синхронизированными всплесками, что выглядят ровно как исходная перегрузка, поэтому она никогда не получает тихого момента слить свой бэклог. Каждый клиент ведёт себя идеально; флот патологичен. Джиттер ломает корреляцию: вместо ожидания ровно 1 с каждый клиент ждёт случайную длительность до 1 с, поэтому те же тысяча ретраев размазываются по всему интервалу как ровное давление, что зависимость реально может поглотить. Та же логика применима к half-open пробам — пятьдесят брейкеров, сработавших вместе, без джиттера разомкнутся вместе и устадятся. Глубокий урок: в распределённой системе враг редко поведение какого-то одного клиента; это синхронизация между клиентами, и лекарство почти всегда — намеренно впрыснуть случайность, чтобы их рассинхронизировать. Поэтому в каждой зрелой реализации ретраев и брейкеров джиттер вшит, а не прикручен.

Координированный сброс: пол должен согласоваться

Сброс нагрузки из прошлого урока тоже меняет форму на флоте. Если каждый инстанс сбрасывает независимо по своей нагрузке, балансировщик, маршрутизирующий неравномерно, может иметь одни инстансы, что сбрасывают жёстко, пока другие простаивают — флот сбрасывает не те запросы. Хуже, инстанс, сбросивший запрос, не заставляет работу исчезнуть; если клиент ретраит, сброшенный запрос приземляется на другой инстанс, поэтому некоординированный сброс может просто перетасовывать нагрузку по флоту вместо её снижения. Стабильная версия — сброс, на котором флот согласен: сбрасывай по приоритету (роняй низкоприоритетный трафик везде первым, согласованно), сбрасывай детерминированно по сигналу, что каждый инстанс может посчитать одинаково, и пари это с осведомлённой о дедлайне очередью из прошлого урока, чтобы запрос, сброшенный у парадной двери, не ретраился в чёрный ход. Принцип, что связывает весь урок: в распределённой системе каждое защитное устройство на инстанс — брейкер, ретрай, сброс — нуждается в истории уровня флота, иначе локально-корректные решения инстансов складываются в глобально-неверный исход.

Режим отказаОдин процессПо всему флотуФикс
Состояние брейкераОдин счёт, один вердикт50 счётов, нет общего взглядаПринять локальную размытость (или платить за общее состояние)
РетраиОграничены на вызовМножатся 4×4×4 = 64 вниз по цепиРетрай на одном слое; ограничь долю (60/мин); брейкер сверху
Проба восстановленияОдна half-open струйка50 брейкеров стадятся разомДжиттери тайминг проб
БэкоффРазводит одного клиентаКлиенты ресинхронизируются в волныЭкспоненциальный бэкофф с джиттером
Сброс нагрузкиСброс локальной перегрузкиТасует нагрузку между инстансамиКоординированный, по приоритету, по дедлайну
Викторина

Сервис A зовёт B зовёт C, каждый ретраит до 4 раз при отказе. C начинает буксовать. Почему это делает положение C драматически хуже, а не лучше?

Викторина

Пятьдесят инстансов срабатывают брейкерами почти в один момент во время сбоя. Почему джиттер на half-open пробе (и на бэкоффе ретраев) существен, а не просто приятен?

Расставь шаги по порядку

Расставь, как маленькое колебание даунстрима становится штормом ретраев на весь флот без верных защит:

  1. 1 Даунстрим-зависимость кратко замедляется под обычной нагрузкой
  2. 2 Ретраи каждого слоя выстреливают, множа один запрос во много (4×4×4 = 64)
  3. 3 Усиленная нагрузка припирает зависимость, обращая колебание в длительный сбой
  4. 4 Клиенты, что упали вместе, ретраят вместе синхронизированными волнами, поддерживая шторм
Вспомните перед уходом
  1. 01
    Почему состояние брейкера на инстанс это и ограничение, и прагматичный дефолт, и что такое усиление ретраев?
  2. 02
    Каковы сеньорные фиксы шторма ретраев и стадности, и почему джиттер существен?
Итог

Первые пять уроков построили брейкер для одного процесса; этот масштабирует его на флот, где одно-процессные интуиции тихо отказывают. Состояние брейкера на инстанс — пятьдесят инстансов держат пятьдесят независимых счётов без общего вердикта, поэтому один срабатывает, пока другой продолжает звать, а новый инстанс переоткрывает сбой в одиночку; локальное состояние прагматичный дефолт, ведь брейкер статистичен, а общее состояние покупает общий взгляд ценой сетевого вызова на горячем пути и новой зависимости. Реальная угроза флота живёт под брейкером в слое ретраев, что множится вдоль цепи в гугловские 4×4×4 = 64 — шторм ретраев, укрощаемый лишь ретраем на одном слое, ограничением абсолютной доли чем-то вроде 60 в минуту, экспоненциальным бэкоффом с джиттером и держанием брейкера над ретраями, чтобы open-цепь короткозамкнула стек. Восстановление само стадится: брейкеры, сработавшие вместе, размыкаются вместе, поэтому half-open пробам нужен тот же джиттер, что и ретраям, ведь распределённый враг это синхронизация между клиентами, а не поведение какого-то одного. И сброс нагрузки работает лишь координированным, по приоритету и по дедлайну, иначе инстансы просто тасуют нагрузку между собой. Дуга юнита завершена — медленная зависимость, машина состояний, пороги, bulkheads, фолбэки и теперь флот — а следующий юнит поворачивает от удержания сервиса живым под нагрузкой к чистому его опусканию: graceful shutdown.

Связанные уроки
Продолжить восхождение ↑Circuit breakers: тест с выбором ответа
хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources3
expand
  1. 01
  2. 02
  3. 03

Trademarks belong to their respective owners. Editorial reference only.