Опирается на правила:
R-RES-OBS-1…R-RES-OBS-3иR-RES-OBS-X1из Resilience Style Guide → раздел 12. Observability.
Важно знать
- Micrometer metrics через
resilience4j-micrometerdependency. Автоматически экспортируетresilience4j_circuitbreaker_state,_calls,_retry_calls,_bulkhead_available_concurrent_calls.- OTel-spans на adapter-методах с атрибутами
circuit_breaker.stateиexternal.system. Даёт связку «slow trace → CB был half-open».- Structured logging. При каждом state-transition CB — лог уровня WARN с
system,prev_state,new_state,failure_rate. Не на каждый успешный call.- Отключение R4J metrics без причины — антипаттерн: без них SRE не увидит «CB Sber стабильно half-open» до жалоб клиентов.
- Минимальный grafana-дашборд:
circuitbreaker_state(timeseries по системам),circuitbreaker_callsrate,bulkhead_available, latency p95 outbound.- Алёрты: CB в
open> 5 минут;bulkhead_available < 2стабильно;retry_calls{kind=failed_with_retry}rate растёт.
Защита от отказов бесполезна, если о её срабатываниях никто не узнаёт. Resilience4j из коробки даёт всё необходимое для наблюдаемости через Micrometer и OTel; нужно только подключить и не отключить. Раскрытие раздела 12 гайда.
Micrometer-метрики через resilience4j-micrometer
R-RES-OBS-1: подключение dependency автоматически регистрирует метрики во всех MeterRegistry бинах (в т.ч. Prometheus).
// build.gradle.kts
dependencies {
implementation("io.github.resilience4j:resilience4j-spring-boot3:2.2.0")
implementation("io.github.resilience4j:resilience4j-micrometer:2.2.0")
implementation("io.micrometer:micrometer-registry-prometheus")
}
Что появляется в /actuator/prometheus:
# CircuitBreaker
resilience4j_circuitbreaker_state{name="sber",state="closed"} 1.0
resilience4j_circuitbreaker_state{name="sber",state="open"} 0.0
resilience4j_circuitbreaker_state{name="sber",state="half_open"} 0.0
resilience4j_circuitbreaker_calls_total{name="sber",kind="successful"} 1234
resilience4j_circuitbreaker_calls_total{name="sber",kind="failed"} 42
resilience4j_circuitbreaker_calls_total{name="sber",kind="ignored"} 0
resilience4j_circuitbreaker_failure_rate{name="sber"} 3.4
resilience4j_circuitbreaker_slow_call_rate{name="sber"} 1.2
# Retry
resilience4j_retry_calls_total{name="sber",kind="successful_without_retry"} 1100
resilience4j_retry_calls_total{name="sber",kind="successful_with_retry"} 80
resilience4j_retry_calls_total{name="sber",kind="failed_without_retry"} 12
resilience4j_retry_calls_total{name="sber",kind="failed_with_retry"} 30
# Bulkhead
resilience4j_bulkhead_available_concurrent_calls{name="sber"} 14
resilience4j_bulkhead_max_allowed_concurrent_calls{name="sber"} 16
Что отсюда строится в Grafana / дашборд:
- Per-system панель: текущий state CB, calls/s success vs failed, current failure rate.
- Bulkhead utilization:
(max - available) / max— насколько забит лимит. - Retry funnel: успешно с первой попытки / успешно с retry / провалилось.
OTel-spans с атрибутами
R-RES-OBS-2: при каждом adapter-вызове создаётся span с атрибутами CB-state.
@Component
@RequiredArgsConstructor
public class SberClientAdapter implements PaymentPort {
private final Tracer tracer;
private final CircuitBreakerRegistry cbRegistry;
@CircuitBreaker(name = "sber", fallbackMethod = "registerFallback")
@Bulkhead(name = "sber")
@Retry(name = "sber")
public RegisterResult register(RegisterCommand cmd) {
Span span = tracer.spanBuilder("sber.register")
.setAttribute("external.system", "sber")
.setAttribute("circuit_breaker.state",
cbRegistry.circuitBreaker("sber").getState().toString())
.startSpan();
try (Scope s = span.makeCurrent()) {
return doRegister(cmd);
} catch (Exception e) {
span.recordException(e);
throw e;
} finally {
span.end();
}
}
}
Эту обвязку можно упростить через @Observed аннотацию (Spring 6.1+) — Spring Observation API сделает то же автоматически:
@Observed(
name = "sber.register",
contextualName = "sber-register",
lowCardinalityKeyValues = {"external.system", "sber"}
)
@CircuitBreaker(name = "sber", fallbackMethod = "registerFallback")
public RegisterResult register(RegisterCommand cmd) { ... }
Что даёт связь с трейсингом:
- В Jaeger / Tempo видно «slow trace 5s → span
sber.registerсcircuit_breaker.state=half_open» — сразу понятно, что CB был на грани открытия. - Алёрт «slow outbound» можно decorировать атрибутами для root-cause анализа.
Structured logging при state-transition
R-RES-OBS-3: лог уровня WARN на переходах CB-state, не на каждый call.
@Configuration
@RequiredArgsConstructor
public class ResilienceLogConfig {
@EventListener
public void onCbEvent(CircuitBreakerOnStateTransitionEvent event) {
var t = event.getStateTransition();
log.warn("circuit breaker {} state changed: {} → {} (failure rate {}%)",
event.getCircuitBreakerName(),
t.getFromState(),
t.getToState(),
event.getCircuitBreaker().getMetrics().getFailureRate()
);
}
}
Что важно:
- Только transitions (
CLOSED → OPEN,OPEN → HALF_OPEN,HALF_OPEN → CLOSED). Не каждый успешный call. - WARN-уровень. ERROR — слишком крикливо; INFO — слишком тихо.
- Достаточно атрибутов: имя системы, prev/new state, failure rate. SRE сразу видит причину.
- Через
CircuitBreakerEventListener— Resilience4j публикует события автоматически, нужно только подписаться.
Аналогично можно подписаться на BulkheadOnCallRejectedEvent (WARN) и RetryOnRetryEvent (DEBUG/INFO).
Что запрещено
Отключение R4J metrics без причины
R-RES-OBS-X1: management.metrics.enable.resilience4j=false или resilience4j.circuitbreaker.configs.default.register-health-indicator=false без обоснования.
# ПЛОХО — отключили метрики
management.metrics.enable.resilience4j: false
Что произойдёт:
- На графиках Prometheus нет линий по
circuitbreaker_state. SRE не видит, что CB Sber стабильноhalf_open. - При деградации Sber единственный сигнал — жалоба клиента «у меня платёж не проходит». 30 минут на расследование вместо 30 секунд по графику.
- Алёрты типа «CB в
open> 5 минут» не работают — нет данных.
Корректно: оставить включённым (default). Если объём метрик беспокоит — отфильтровать на стороне Prometheus, не отключать в источнике.
Что запрещено — таблица
| Антипаттерн | Правило | Что взамен |
|---|---|---|
management.metrics.enable.resilience4j=false без причины | R-RES-OBS-X1 | Оставить включённым |
register-health-indicator=false для CB | R-RES-HC-4 | Включить, чтобы /actuator/health показывал CB-состояние |
| Лог на каждый успешный call | R-RES-OBS-3 | Только на state-transitions |
| ERROR-уровень для CB transition | R-RES-OBS-3 | WARN |
Span без атрибутов external.system и circuit_breaker.state | R-RES-OBS-2 | Атрибуты для root-cause analysis |
| Только metrics без алёртов | R-RES-OBS-1 | Алёрты на CB-state, bulkhead utilization, retry-failure rate |
Куда дальше
- Resilience → раздел 12. Observability — нормативные
R-RES-OBS-*. - Circuit Breaker — state transitions и их semantics.
- Bulkhead —
available_concurrent_callsметрика. - Retry —
retry_calls{kind=...}метрики. - Health checks — Actuator endpoints.
- Observability Style Guide — общий гайд по metrics, logs, tracing.
- Конфигурация —
register-health-indicatorper-instance.