Projektowanie odpornych architektur korutyn
Połącz supervisorScope, logikę ponawiania i handlery, aby tworzyć współbieżność gotową do użycia produkcyjnego.
Projektowanie odpornych architektur korutyn to bezpłatna lekcja Kotlin Academy na CoddyKit. To lekcja 4 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej Kotlin Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs Kotlin Academy zawiera 4 lekcji w sumie.
Co sprawia, że korutyny są odporne?
Odporne architektury korutyn prawidłowo obsługują niepowodzenia: izolują błędy, ponawiają próby po przejściowych problemach, anulują nieaktualne zadania i deterministycznie zwalniają zasoby.
import kotlinx.coroutines.*
// Resilience pillars:
// 1. Failure isolation (SupervisorJob)
// 2. Retry with backoff (retry operator / loop)
// 3. Timeout guards (withTimeout)
// 4. Clean teardown (finally + NonCancellable)
// 5. Observability (CoroutineExceptionHandler)
fun main() = runBlocking { println("Design for failure from the start") }Wzorzec zakresu na poziomie aplikacji
Należy utworzyć pojedynczy obiekt CoroutineScope na poziomie aplikacji z SupervisorJob + handler. Należy wstrzyknąć go do usług, aby wszystkie współdzieliły ten sam cykl życia.
import kotlinx.coroutines.*
object AppCoroutineScope {
private val handler = CoroutineExceptionHandler { _, e ->
println("[AppScope] Uncaught: ${e.message}")
}
val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default + handler)
fun cancel() = scope.cancel()
}Ponawianie prób z wykładniczym opóźnieniem
Wywołania sieciowe należy opakować w pętlę ponawiania prób z wykładniczym wydłużaniem opóźnienia, aby prawidłowo obsługiwać przejściowe niepowodzenia.
import kotlinx.coroutines.*
suspend fun <T> retryWithBackoff(
times: Int = 3,
initialDelay: Long = 100,
block: suspend () -> T
): T {
var delay = initialDelay
repeat(times - 1) { attempt ->
try { return block() }
catch (e: Exception) {
if (e is CancellationException) throw e
println("Attempt ${attempt+1} failed, retrying in ${delay}ms")
delay(delay)
delay *= 2
}
}
return block()
}
fun main() = runBlocking {
var n = 0
val result = retryWithBackoff {
if (n++ < 2) throw RuntimeException("transient")
"success"
}
println(result)
}Wzorzec circuit breaker
Mechanizm circuit breaker przestaje wywoływać zawodną usługę po N niepowodzeniach i ponownie otwiera obwód po okresie chłodzenia — zapobiegając kaskadowym awariom.
import kotlinx.coroutines.*
class CircuitBreaker(val maxFailures: Int, val cooldownMs: Long) {
private var failures = 0
private var openUntil = 0L
suspend fun <T> call(block: suspend () -> T): T {
if (System.currentTimeMillis() < openUntil) throw RuntimeException("Circuit open")
return try {
val result = block()
failures = 0
result
} catch (e: Exception) {
if (e is CancellationException) throw e
if (++failures >= maxFailures) openUntil = System.currentTimeMillis() + cooldownMs
throw e
}
}
}Limit czasu dla każdego wywołania zewnętrznego
Każdą zewnętrzną operację wejścia-wyjścia należy zawsze opakować w withTimeout lub withTimeoutOrNull. Nie należy dopuścić, aby zawieszone połączenie blokowało korutynę bezterminowo.
import kotlinx.coroutines.*
suspend fun fetchWithTimeout(url: String): String? = withTimeoutOrNull(3000) {
// ktor: client.get(url).body()
delay(100) // simulate
"response from $url"
}
fun main() = runBlocking {
val result = fetchWithTimeout("https://api.example.com")
println(result ?: "Timed out")
}Strukturalny zakres dla każdej funkcji
Należy utworzyć osobny obiekt CoroutineScope dla każdej funkcji lub ekranu, z własnym obiektem SupervisorJob. Należy go anulować po zniszczeniu funkcji.
import kotlinx.coroutines.*
class FeatureController {
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
fun start() {
scope.launch { /* load data */ }
scope.launch { /* subscribe to updates */ }
}
fun destroy() = scope.cancel() // cancels all feature coroutines
}Bezpieczne zarządzanie zasobami z use()
Należy używać use { } dla zasobów AutoCloseable. W połączeniu z anulowaniem korutyny zasoby zostaną zamknięte nawet po anulowaniu korutyny.
import kotlinx.coroutines.*
class Connection : AutoCloseable {
override fun close() = println("Connection closed")
suspend fun fetch(): String { delay(100); return "data" }
}
fun main() = runBlocking {
val job = launch {
Connection().use { conn ->
println(conn.fetch())
} // close() called even on cancellation
}
delay(50)
job.cancelAndJoin()
}Kontrolowane zamykanie
Należy zaimplementować kontrolowane zamykanie: przestać przyjmować nowe zadania, zaczekać na zakończenie korutyn będących w toku, a następnie anulować zakres.
import kotlinx.coroutines.*
class WorkQueue {
private val scope = CoroutineScope(SupervisorJob())
private val jobs = mutableListOf<Job>()
fun submit(block: suspend () -> Unit) {
jobs += scope.launch { block() }
}
suspend fun shutdown() {
jobs.forEach { it.join() } // wait for all
scope.cancel() // then cancel scope
}
}
fun main() = runBlocking {
val q = WorkQueue()
repeat(3) { i -> q.submit { delay(50); println("Task $i done") } }
q.shutdown()
println("Queue shut down cleanly")
}Obserwowalność: CoroutineName
Należy oznaczać korutyny za pomocą CoroutineName na potrzeby debugowania. Nazwy pojawiają się w śladach stosu i mogą być używane w handlerach wyjątków do strukturalnego rejestrowania.
import kotlinx.coroutines.*
fun main() = runBlocking {
val handler = CoroutineExceptionHandler { ctx, e ->
println("[${ctx[CoroutineName]?.name}] Failed: ${e.message}")
}
CoroutineScope(SupervisorJob() + handler).apply {
launch(CoroutineName("UserLoader")) { throw RuntimeException("DB error") }
launch(CoroutineName("Analytics")) { delay(100); println("Analytics ok") }
delay(200); cancel()
}
}Należy unikać GlobalScope
Nigdy nie należy używać GlobalScope w środowisku produkcyjnym. Tworzy on korutyny bez rodzica, zarządzania cyklem życia i strukturalnego anulowania. Należy zamiast niego używać alternatyw opartych na zakresach.
import kotlinx.coroutines.*
// BAD — GlobalScope leaks coroutines:
// GlobalScope.launch { delay(Long.MAX_VALUE) }
// GOOD — scoped, cancellable:
val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
scope.launch { delay(100); println("Scoped") }
fun main() = runBlocking { delay(200); scope.cancel() }Testowanie odporności
Należy używać runTest wraz z TestCoroutineScheduler, aby symulować niepowodzenia, limity czasu i ponawianie prób w testach jednostkowych bez rzeczywistych opóźnień.
import kotlinx.coroutines.*
import kotlinx.coroutines.test.*
fun testRetry() = runTest {
var attempts = 0
val result = retryWithBackoff(3, 100) {
if (attempts++ < 2) throw RuntimeException("fail")
"ok"
}
println(result) // "ok"
}
// In test, virtual time advances instantly through delaysSzybkie sprawdzenie
Jakie połączenie stanowi podstawę odpornego, długotrwałego zakresu korutyn?
Podsumowanie
Odporne architektury łączą SupervisorJob do izolowania niepowodzeń, retryWithBackoff do obsługi przejściowych błędów, withTimeout do obsługi zablokowanych operacji wejścia-wyjścia, finally/NonCancellable do sprzątania oraz CoroutineExceptionHandler do zapewnienia obserwowalności.
Ucz się Kotlin dzięki korepetycjom AI — za darmo
Pisz i uruchamiaj kod w przeglądarce, otrzymuj natychmiastową pomoc od korepetytora AI dostępnego 24/7 i kontynuuj naukę w sieci lub w aplikacji.
- Kursy
- 51
- Lekcje
- 203
Często zadawane pytania
Czy lekcja „Projektowanie odpornych architektur korutyn” jest bezpłatna?
Tak — pełny tekst „Projektowanie odpornych architektur korutyn” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu Kotlin Academy, przejdź na CoddyKit PRO. Kurs Kotlin Academy zawiera 4 lekcji w sumie.
Co nauczysz się w „Projektowanie odpornych architektur korutyn”?
Połącz supervisorScope, logikę ponawiania i handlery, aby tworzyć współbieżność gotową do użycia produkcyjnego. Ćwiczysz Kotlin Academy z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.
Czy potrzebuję doświadczenia, aby zacząć Kotlin Academy?
Nie wymagamy żadnego doświadczenia. Kotlin Academy w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 4 z 4.
Ile czasu zajmuje lekcja „Projektowanie odpornych architektur korutyn”?
Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.
Czy mogę pisać i uruchamiać kod w tej lekcji Kotlin Academy?
Tak. Każda lekcja Kotlin Academy zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.
Wszystkie lekcje w tym kursie
- SupervisorJob a Job: izolowanie błędów
- CoroutineExceptionHandler: globalny handler nieprzechwyconych wyjątków
- Propagacja wyjątków w async/await
- Projektowanie odpornych architektur korutyn