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

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

Что его срабатывает: доля отказов, окна и порог объёма

Суть Брейкер не должен срабатывать на единичный отказ и не оставаться closed сквозь обвал. Срабатывает доля отказов по скользящему окну, ограниченная минимальным порогом объёма, чтобы он игнорировал шум — и медленный вызов засчитан как отказ, ведь медленный реально вредит.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на middle-высоте — в небе
◷ 15 min

Наивный брейкер срабатывает на первый упавший вызов. И вот единичный таймаут в 3 часа ночи — разовый сетевой сбой — размыкает брейкер, и теперь каждый пользователь быстро-отказан на всё остывание из-за одной транзиентной ошибки. Затяни в другую сторону — и брейкер, которому нужно «50 отказов», никогда не сработает на низкотрафиковом эндпоинте, который видит лишь 10 запросов в минуту, поэтому он сидит closed сквозь полный обвал. Условие срабатывания — не одно число; это доля по окну с порогом объёма, и сделать эти два правильно — разница между брейкером, который защищает, и тем, что либо кричит «волки», либо спит сквозь пожар.

Доля, не счёт

Условие срабатывания — это доля отказов, не абсолютное число отказов: failureRateThreshold у resilience4j по умолчанию 50%, errorThresholdPercentage у Hystrix — 50. Как только доля падающих вызовов в текущем окне пересекает порог, брейкер размыкается. Использование доли, а не сырого счёта, делает брейкер масштабонезависимым — 50% отказов осмысленны хоть при 10 вызовах в секунду, хоть при 10 000, тогда как «50 отказов» означают совсем разное при этих двух объёмах.

Окно: count-based против time-based

Доля измеряется по скользящему окну недавних вызовов, и есть два способа определить «недавние»:

  • Count-based — последние N вызовов. По умолчанию у resilience4j slidingWindowType = COUNT_BASED с slidingWindowSize = 100, так что он судит по последним 100 вызовам. Просто и предсказуемо, но на тихом эндпоинте эти 100 вызовов могут растянуться надолго, поэтому окно реагирует медленно.
  • Time-based — все вызовы за последние T секунд, обычно разбитые на корзины. Hystrix использует 10 с катящееся окно, делённое на 10 односекундных корзин, катящееся вперёд каждую секунду. Это реагирует за ограниченное стенное время независимо от трафика, чего обычно и хочешь для латентно-чувствительного брейкера.

В любом случае окно скользит: старые вызовы стареют и выпадают, поэтому брейкер отражает недавнее здоровье зависимости, а не её пожизненное среднее. Зависимость, упавшая час назад и с тех пор в порядке, не должна держать брейкер разомкнутым.

Порог объёма останавливает ложные срабатывания

Доля сама по себе опасна при низком трафике: 1 отказ из 1 вызова — это 100%, что сработало бы мгновенно на единичном сбое. Фикс — минимальный порог объёма: брейкер должен увидеть как минимум M вызовов в окне, прежде чем ему вообще позволено сработать. requestVolumeThreshold у Hystrix по умолчанию 20; minimumNumberOfCalls у resilience4j — 100. Ниже порога брейкер остаётся closed независимо от доли, потому что горстка отказов — не статистически осмысленный сигнал. Это самая важная защита от брейкера, который флапает на шуме.

Медленный — это тоже отказ

Самое тонкое правило: вызов, который успешен медленно, часто должен считаться отказом. Из первого урока: медленный — опасное состояние, зависимость, отвечающая за 5 с, наносит столько же урона, сколько и та, что ошибается. Поэтому зрелые брейкеры отслеживают долю медленных вызовов отдельно: slowCallDurationThreshold у resilience4j определяет, что значит «медленный», а slowCallRateThreshold (по умолчанию 100%) — доля медленных вызовов, которая срабатывает брейкер независимо от прямых ошибок. Без этого зависимость, которая никогда не ошибается, но ползёт, держала бы брейкер closed, пока голодит твои потоки — ровно тот режим отказа, ради предотвращения которого брейкер и куплен.

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

Зачем вообще завязывать брейкер на минимальный объём — разве 100% доля отказов не всегда реальная проблема? Потому что при низком объёме «доля» — это статистический шум, не измерение. Один упавший вызов из одного — 100%, но это говорит почти ни о чём: это может быть транзиентная потеря пакета, единичная медленная пауза GC у даунстрима, разовый сбой деплоя. Срабатывание на этом наказывает каждого следующего пользователя остыванием из-за выборки размером в один. Минимальный порог объёма — это требование уверенности: действуй на долю отказов лишь когда видел достаточно вызовов, чтобы доля стала осмысленной. Это та же причина, по которой ты не заключаешь, что монета смещена, после одного броска. Структура издержек асимметрична и информирует числа — ложное срабатывание на здоровой зависимости это самонанесённый отказ, тогда как подождать ещё несколько вызовов до срабатывания стоит лишь этих нескольких вызовов ожидания на зависимости, которая, если правда сломана, продолжит падать и пересечёт порог в момент. Так что порог покупает реальную защиту от флапанья почти без цены в скорости реакции, поэтому у каждого продакшен-брейкера он есть, и поэтому его тюнинг под низкотрафиковые эндпоинты важнее самого порога доли.

НастройкаHystrix по умолчаниюresilience4j по умолчаниюЧто контролирует
Порог доли отказов50%50%Доля отказов, срабатывающая брейкер
Тип окнаTime-based (10 с)Count-based (последние 100)Как определены «недавние»
Размер окна10 с / 10 корзин100 вызововПромежуток, по которому мерится доля
Минимальный объём20 вызовов100 вызововПорог, до которого срабатывание не дозволено
Обработка медленныхТаймаут → отказ (1 с)slowCallRate 100% / длительность 60 сСчитает медленный как отказ
Викторина

Низкотрафиковый эндпоинт видит один таймаут в 3 часа ночи, и брейкер размыкается на всё остывание, быстро-отказывая всем из-за единичной транзиентной ошибки. Какая настройка это предотвращает?

Викторина

Почему брейкер должен считать медленный-но-успешный вызов отказом?

Вспомните перед уходом
  1. 01
    Почему брейкер срабатывает на долю отказов по скользящему окну, а не на сырой счёт отказов, и какие два типа окна?
  2. 02
    Что делает минимальный порог объёма, и почему брейкер должен считать медленные вызовы отказами?
Итог

Условие срабатывания — сердце тюнинга брейкера, и это никогда не одно число. Брейкер срабатывает на долю отказов — resilience4j и Hystrix оба по умолчанию 50% — потому что доля масштабонезависима там, где сырой счёт нет. Эта доля измеряется по скользящему окну недавних вызовов, либо count-based (последние N, resilience4j по умолчанию 100), либо time-based (последние T секунд в корзинах, Hystrix 10 с, делённые на десять односекундных корзин), и окно скользит, так что считается только недавнее здоровье. Минимальный порог объёма — Hystrix requestVolumeThreshold 20, resilience4j minimumNumberOfCalls 100 — держит брейкер closed, пока он не увидел достаточно вызовов для осмысленности доли, важнейшая защита от флапанья на шуме. И поскольку медленный — опасное состояние зависимости, медленный-но-успешный вызов должен считаться отказом по своей доле медленных вызовов, иначе ползущая зависимость держит брейкер closed, голодя твои потоки. Брейкер теперь срабатывает правильно — но он всё ещё делит один пул потоков на все зависимости, поэтому один больной даунстрим может осушить бюджет до того, как брейкер вообще среагирует. Следующий урок добавляет bulkheads, чтобы это изолировать.

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

Trademarks belong to their respective owners. Editorial reference only.