0Pricing
Kotlin Academy · Урок

Проектирование устойчивых архитектур сопрограмм

Объединяйте supervisorScope, логику повторных попыток и обработчики для параллелизма промышленного уровня.

«Проектирование устойчивых архитектур сопрограмм» — бесплатный урок Kotlin Academy на CoddyKit. Это урок 4 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Kotlin Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Kotlin Academy содержит 4 уроков всего.

Что делает корутины устойчивыми?

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

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") }

Шаблон области видимости уровня приложения

Создайте одну область видимости CoroutineScope уровня приложения с SupervisorJob + handler. Передайте её сервисам через внедрение зависимостей, чтобы все они использовали один жизненный цикл.

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()
}

Повторные попытки с экспоненциальной задержкой

Оберните сетевые вызовы в цикл повторных попыток с экспоненциальным увеличением задержки, чтобы корректно обрабатывать временные сбои.

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)
}

Шаблон размыкателя цепи

Размыкатель цепи прекращает вызовы отказавшего сервиса после N сбоев и снова разрешает их после периода охлаждения, предотвращая каскадные сбои.

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
        }
    }
}

Тайм-аут для каждого внешнего вызова

Всегда оборачивайте внешние операции ввода-вывода в withTimeout или withTimeoutOrNull. Никогда не позволяйте зависшему соединению блокировать корутину на неопределённый срок.

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")
}

Структурированная область видимости для каждой функции

Создайте отдельную область видимости CoroutineScope для каждой функции или экрана с собственной SupervisorJob. Отменяйте её при удалении функции.

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
}

Безопасность ресурсов с конструкцией использования

Используйте use { } для ресурсов AutoCloseable. Сочетайте это с отменой корутин: ресурсы закрываются даже при отмене корутины.

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()
}

Корректное завершение работы

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

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")
}

Наблюдаемость: CoroutineName

Помечайте корутины с помощью CoroutineName для отладки. Имена отображаются в трассировках стека и могут использоваться в обработчиках исключений для ведения структурированных журналов.

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()
    }
}

Избегайте GlobalScope

Никогда не используйте GlobalScope в рабочем коде. Он создаёт корутины без родителя, управления жизненным циклом и структурированной отмены. Вместо этого используйте альтернативы с заданной областью видимости.

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() }

Тестирование устойчивости

Используйте runTest вместе с TestCoroutineScheduler, чтобы моделировать сбои, тайм-ауты и повторные попытки в модульных тестах без реальных задержек.

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 delays

Быстрая проверка

Какая комбинация лежит в основе устойчивой долгоживущей области видимости корутин?

Повторение

Устойчивые архитектуры объединяют SupervisorJob для изоляции, retryWithBackoff для временных ошибок, withTimeout для зависших операций ввода-вывода, очистку с NonCancellable и CoroutineExceptionHandler для наблюдаемости.

Часто задаваемые вопросы

Урок «Проектирование устойчивых архитектур сопрограмм» бесплатный?

Да — полный текст урока «Проектирование устойчивых архитектур сопрограмм» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Kotlin Academy, подпишись на CoddyKit PRO. Курс Kotlin Academy содержит 4 уроков всего.

Чему я научусь в уроке «Проектирование устойчивых архитектур сопрограмм»?

Объединяйте supervisorScope, логику повторных попыток и обработчики для параллелизма промышленного уровня. Ты практикуешь Kotlin Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать Kotlin Academy?

Предыдущий опыт не требуется. Kotlin Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 4 из 4.

Сколько времени занимает урок «Проектирование устойчивых архитектур сопрограмм»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке Kotlin Academy?

Да. Каждый урок Kotlin Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. SupervisorJob и Job: изоляция сбоев
  2. CoroutineExceptionHandler: глобальный обработчик неперехваченных исключений
  3. Распространение исключений в async/await
  4. Проектирование устойчивых архитектур сопрограмм
← Назад к Kotlin Academy