Circuit Breaker chroni aplikację przed kaskadowymi awariami przez automatyczne blokowanie requestów do niestabilnych serwisów.
CLOSED (normalny - requesty przechodzą)
↓ (błędy przekraczają próg)
OPEN (blokuje wszystkie requesty)
↓ (po resetTimeoutMs)
HALF_OPEN (testuje ograniczone requesty)
↓ sukces (successThreshold osiągnięty) → CLOSED
↓ błąd → OPEN
import rip.nerd.kitsunenet.interceptor.KNETCircuitBreakerInterceptor
// Prosty interceptor - jeden circuit dla wszystkich requestów
val interceptor = KNETCircuitBreakerInterceptor(
failureThreshold = 5, // Otwiera po 5 błędach
successThreshold = 2, // Zamyka po 2 sukcesach w HALF_OPEN
timeoutMs = 60_000, // Pozostaje OPEN przez 60s
failureStatusCodes = setOf(500, 502, 503, 504)
)
val client = KNETClient.builder()
.addInterceptor(interceptor)
.build()
// Sprawdź stan
interceptor.currentCircuitState() // CLOSED, OPEN, HALF_OPEN
interceptor.reset() // Resetuj do CLOSED
import rip.nerd.kitsunenet.util.KNETCircuitBreaker
// Circuit breaker z osobnymi obwodami per klucz (np. per host)
val circuitBreaker = KNETCircuitBreaker(
failureThreshold = 5,
successThreshold = 2,
resetTimeoutMs = 30_000,
halfOpenMaxRequests = 3
)
// Wykonaj przez circuit breaker
val result = circuitBreaker.execute("api.example.com") {
client.get("https://api.example.com/users")
}
when (result) {
is KNETCircuitBreaker.Result.Success -> obsluzOdpowiedz(result.value)
is KNETCircuitBreaker.Result.Failure -> obsluzBlad(result.error)
is KNETCircuitBreaker.Result.Rejected -> pokazSerwisNiedostepny()
}
// Bezpośrednie wykonanie requestu HTTP
val result = circuitBreaker.executeRequest(
client = client,
request = KNETRequest("https://api.example.com/users"),
key = "api.example.com" // Opcjonalnie: domyślnie hostname
)
// Sprawdź stan
val state = circuitBreaker.getState("api.example.com")
println("Stan: $state") // CLOSED, OPEN, HALF_OPEN
// Statystyki
val stats = circuitBreaker.getStats("api.example.com")
stats?.let {
println("Błędy: ${it.failureCount}")
println("Sukcesy: ${it.successCount}")
println("Od ostatniego błędu: ${it.timeSinceLastFailure}ms")
}
// Wszystkie stany
circuitBreaker.getAllStates().forEach { (host, state) ->
println("$host: $state")
}
// Ręczne sterowanie
circuitBreaker.forceOpen("api.example.com")
circuitBreaker.forceClose("api.example.com")
circuitBreaker.reset("api.example.com")
circuitBreaker.resetAll()
// Wbudowane konfiguracje
KNETCircuitBreaker.default // failureThreshold=5, resetTimeout=30s
KNETCircuitBreaker.aggressive() // failureThreshold=3, resetTimeout=10s
KNETCircuitBreaker.tolerant() // failureThreshold=10, resetTimeout=60s
// Circuit Breaker + Retry (zalecana kolejność)
val client = KNETClient.builder()
.addInterceptor(KNETCircuitBreakerInterceptor(failureThreshold = 5)) // 1. Circuit Breaker (zewnętrzny)
.addInterceptor(KNETRetryInterceptor(maxRetries = 2)) // 2. Retry (wewnętrzny)
.build()
// Przepływ:
// Request → Circuit Breaker → Retry → HTTP
// ↑ ↓ (przy błędzie)
// └─── zliczane ──┘
class ResilientApiClient(context: Context) {
private val circuitBreaker = KNETCircuitBreaker(
failureThreshold = 5,
resetTimeoutMs = 30_000
)
private val cache = KNETResponseCache.longLived()
private val client = KNETClient.builder()
.addInterceptor(KNETLoggingInterceptor.basic())
.addInterceptor(KNETRetryInterceptor.simple(maxRetries = 2))
.addInterceptor(KNETCacheInterceptor.mediumLived())
.build()
suspend fun getData(url: String): Data? {
val host = java.net.URL(url).host
val result = circuitBreaker.execute(host) {
client.get(url)
}
return when (result) {
is KNETCircuitBreaker.Result.Success -> result.value.json()
is KNETCircuitBreaker.Result.Rejected -> {
// Circuit otwarty - spróbuj z cache
cache.get(url)?.json()
}
is KNETCircuitBreaker.Result.Failure -> throw result.error
}
}
}