Kotlin Academy · บทเรียน

การออกแบบสถาปัตยกรรม Coroutine ที่ทนทาน

ผสาน supervisorScope ตรรกะการลองใหม่ และตัวจัดการ เพื่อการทำงานพร้อมกันระดับใช้งานจริง

บทเรียน 4 จาก 413 ขั้นตอน

การออกแบบสถาปัตยกรรม Coroutine ที่ทนทาน เป็นบทเรียน Kotlin Academy ฟรีบน CoddyKit นี่คือบทเรียนที่ 4 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 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
        }
    }
}

กำหนดเวลาหมดอายุให้การเรียกภายนอกทุกครั้ง

ห่อการทำงาน I/O ภายนอกด้วย 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()

ใช้ 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 สำหรับ I/O ที่ค้างอยู่ และใช้ finally/NonCancellable สำหรับการล้างทรัพยากร รวมถึงใช้ CoroutineExceptionHandler เพื่อการสังเกตการณ์

เริ่มต้นได้ฟรี

เรียนรู้ Kotlin ด้วย AI tutor — ฟรี

เขียนและเรียกใช้โค้ดจริงในเบราว์เซอร์ของคุณ รับความช่วยเหลือทันทีจาก AI tutor 24/7 และเรียนรู้ต่อจากที่คุณหยุดบนเว็บหรือในแอป

คอร์ส
51
บทเรียน
203

คำถามที่พบบ่อย

บทเรียน “การออกแบบสถาปัตยกรรม Coroutine ที่ทนทาน” ฟรีหรือไม่

ใช่ — ข้อความเต็มของ “การออกแบบสถาปัตยกรรม Coroutine ที่ทนทาน” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส Kotlin Academy ให้อัปเกรดเป็น CoddyKit PRO คอร์ส Kotlin Academy มีบทเรียนทั้งหมด 4 บทเรียน

คุณจะเรียนรู้อะไรในบทเรียน “การออกแบบสถาปัตยกรรม Coroutine ที่ทนทาน”

ผสาน supervisorScope ตรรกะการลองใหม่ และตัวจัดการ เพื่อการทำงานพร้อมกันระดับใช้งานจริง คุณปฏิบัติ Kotlin Academy ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน

คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน Kotlin Academy หรือไม่

ไม่จำเป็นต้องมีประสบการณ์มาก่อน Kotlin Academy บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 4 จากทั้งหมด 4 บทเรียน

บทเรียน “การออกแบบสถาปัตยกรรม Coroutine ที่ทนทาน” ใช้เวลานานแค่ไหน

บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย

ฉันเขียนและรันโค้ดในบทเรียน Kotlin Academy นี้ได้ไหม

ได้ บทเรียน Kotlin Academy ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

บทเรียนทั้งหมดในหลักสูตรนี้

  1. SupervisorJob กับ Job: การแยกความล้มเหลว
  2. CoroutineExceptionHandler: ตัวจัดการข้อผิดพลาดที่ไม่ถูกดักจับระดับ Global
  3. การส่งต่อ Exception ของ async/await
  4. การออกแบบสถาปัตยกรรม Coroutine ที่ทนทาน
← กลับไปที่ Kotlin Academy