0Pricing
Scala for Backend Engineering & Functional Programming · บทเรียน

การควบคุมดูแลและความทนทานต่อความผิดพลาด

สำรวจกลยุทธ์การควบคุมดูแลของ Akka เพื่อสร้างระบบที่ยืดหยุ่นและกู้คืนจากความล้มเหลวได้อย่างเหมาะสม

การควบคุมดูแลและความทนทานต่อความผิดพลาด เป็นบทเรียน Scala for Backend Engineering & Functional Programming ฟรีบน CoddyKit นี่คือบทเรียนที่ 3 จากทั้งหมด 3 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน Scala for Backend Engineering & Functional Programming และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส Scala for Backend Engineering & Functional Programming มีบทเรียนทั้งหมด 3 บทเรียน

การสร้างระบบที่ทนทาน

ในแอปพลิเคชันที่ทำงานพร้อมกัน อาจเกิดข้อผิดพลาดได้ แอกเตอร์อาจหยุดทำงาน การเรียกเครือข่ายอาจล้มเหลว หรืออาจไม่สามารถเข้าถึงฐานข้อมูลได้

การทนต่อข้อผิดพลาดคือความสามารถของระบบในการทำงานต่อไปได้อย่างถูกต้อง แม้บางส่วนของระบบจะล้มเหลว Akka มีกลไกอันทรงพลังเพื่อให้บรรลุเป้าหมายนี้

การกำกับดูแลระหว่างแม่กับลูก

แอกเตอร์ของ Akka ถูกจัดระเบียบเป็นลำดับชั้น คล้ายกับแผนผังครอบครัว เมื่อแอกเตอร์หนึ่งสร้างแอกเตอร์อีกตัว แอกเตอร์นั้นจะกลายเป็นแอกเตอร์แม่

  • แอกเตอร์แม่มีหน้าที่กำกับดูแลแอกเตอร์ลูก
  • หากแอกเตอร์ลูกล้มเหลว แอกเตอร์แม่จะได้รับการแจ้งเตือน
  • จากนั้นแอกเตอร์แม่จะตัดสินใจว่าจะจัดการกับความล้มเหลวของแอกเตอร์ลูกอย่างไร ซึ่งเรียกว่ากลยุทธ์การกำกับดูแล

การตัดสินใจเมื่อเกิดความล้มเหลว

เมื่อแอกเตอร์ล้มเหลว แอกเตอร์แม่สามารถออกคำสั่งได้หนึ่งในสี่แบบ:

  • Resume: แอกเตอร์ประมวลผลข้อความต่อไปโดยไม่สนใจความล้มเหลว
  • Restart: แอกเตอร์ถูกหยุด สร้างขึ้นใหม่ แล้วจึงกลับมาประมวลผลต่อ สถานะภายในจะถูกตั้งค่าใหม่
  • Stop: แอกเตอร์ถูกยุติการทำงานอย่างถาวร
  • Escalate: ความล้มเหลวถูกส่งต่อไปยังผู้กำกับดูแลของแอกเตอร์แม่ (แอกเตอร์ปู่)

กลยุทธ์ OneForOne

OneForOneStrategyเป็นกลยุทธ์การกำกับดูแลที่ใช้กันทั่วไป หมายความว่าคำสั่งจะมีผลเฉพาะกับแอกเตอร์ลูกที่ล้มเหลวเท่านั้น

ตัวอย่างเช่น หากแอกเตอร์ลูกตัวหนึ่งหยุดทำงาน แอกเตอร์แม่อาจตัดสินใจเริ่มต้นแอกเตอร์ลูกตัวนั้นใหม่เพียงตัวเดียว โดยไม่ส่งผลกระทบต่อแอกเตอร์ลูกตัวอื่น

OneForOne ในการใช้งานจริง

มาดู OneForOneStrategy กัน เราจะสร้างแอกเตอร์แม่ที่เริ่มต้นแอกเตอร์ลูกใหม่เมื่อเกิด ArithmeticException แต่จะหยุดแอกเตอร์ลูกเมื่อเกิดข้อยกเว้นชนิดอื่น

import akka.actor._
import akka.actor.SupervisorStrategy._
import scala.concurrent.duration._

object OneForOneExample extends App {
  class Child extends Actor {
    override def preStart(): Unit = {
      println(s"Child ${self.path.name}: Started!")
    }
    override def postStop(): Unit = {
      println(s"Child ${self.path.name}: Stopped!")
    }
    override def preRestart(reason: Throwable, message: Option[Any]): Unit = {
      println(s"Child ${self.path.name}: Pre-restart due to ${reason.getMessage}")
    }
    override def postRestart(reason: Throwable): Unit = {
      println(s"Child ${self.path.name}: Post-restart!")
    }
    def receive = {
      case "divideByZero" => throw new ArithmeticException("Oops, dividing by zero!")
      case "nullPointer"  => throw new NullPointerException("Something is null!")
      case msg            => println(s"Child ${self.path.name}: Received $msg")
    }
  }

  class Parent extends Actor {
    override val supervisorStrategy: SupervisorStrategy =
      OneForOneStrategy(maxNrOfRetries = 10, withinTimeRange = 1.minute) {
        case _: ArithmeticException => Restart
        case _: NullPointerException => Stop
        case _                      => Escalate
      }

    val child = context.actorOf(Props[Child], "myChild")

    def receive = {
      case msg => child ! msg
    }
  }

  val system = ActorSystem("OneForOneSystem")
  val parent = system.actorOf(Props[Parent], "myParent")

  parent ! "Hello"
  parent ! "divideByZero" // Should cause a restart
  Thread.sleep(100)
  parent ! "Hello again"
  Thread.sleep(1000) // Give time for restart to complete

  parent ! "nullPointer"  // Should cause a stop
  Thread.sleep(100)
  parent ! "Hello after null" // This message will not be processed by child
  Thread.sleep(2000)

  system.terminate()
}

สังเกตการทำงานของ OneForOne

คุณได้เรียกใช้โค้ดก่อนหน้านี้หรือไม่ มาดูกันว่าเกิดอะไรขึ้น:

  • เมื่อส่ง "divideByZero" แอกเตอร์ลูกทำให้เกิด ArithmeticException แอกเตอร์แม่ใช้คำสั่ง Restart คุณจะเห็นบันทึก "Pre-restart" และ "Post-restart"
  • จากนั้นแอกเตอร์ลูกประมวลผล "Hello again"
  • เมื่อส่ง "nullPointer" แอกเตอร์ลูกทำให้เกิด NullPointerException แอกเตอร์แม่ใช้คำสั่ง Stop แอกเตอร์ลูกจึงถูกยุติการทำงาน
  • มีการส่ง "Hello after null" แต่แอกเตอร์ลูกหยุดทำงานไปแล้ว จึงไม่สามารถประมวลผลได้

กลยุทธ์ AllForOne

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

AllForOneStrategyใช้คำสั่งเดียวกันกับแอกเตอร์ลูกทั้งหมดของผู้กำกับดูแล ไม่ใช่เฉพาะแอกเตอร์ที่ล้มเหลว หากแอกเตอร์ลูกตัวหนึ่งล้มเหลว แอกเตอร์ลูกทั้งหมดจะได้รับผลกระทบจากคำสั่งที่เลือก

AllForOne ในการใช้งานจริง

มาปรับเปลี่ยนแอกเตอร์แม่ให้ใช้ AllForOneStrategy กัน สังเกตว่าความล้มเหลวของแอกเตอร์ลูกตัวหนึ่งส่งผลต่อแอกเตอร์ลูกที่เป็นพี่น้องอย่างไร

import akka.actor._
import akka.actor.SupervisorStrategy._
import scala.concurrent.duration._

object AllForOneExample extends App {
  class Child extends Actor {
    override def preStart(): Unit = {
      println(s"Child ${self.path.name}: Started!")
    }
    override def postStop(): Unit = {
      println(s"Child ${self.path.name}: Stopped!")
    }
    override def preRestart(reason: Throwable, message: Option[Any]): Unit = {
      println(s"Child ${self.path.name}: Pre-restart due to ${reason.getMessage}")
    }
    override def postRestart(reason: Throwable): Unit = {
      println(s"Child ${self.path.name}: Post-restart!")
    }
    def receive = {
      case "fail" => throw new RuntimeException("Child failed!")
      case msg    => println(s"Child ${self.path.name}: Received $msg")
    }
  }

  class Parent extends Actor {
    override val supervisorStrategy: SupervisorStrategy =
      AllForOneStrategy(maxNrOfRetries = 10, withinTimeRange = 1.minute) {
        case _: RuntimeException => Restart
        case _                   => Escalate
      }

    val childA = context.actorOf(Props[Child], "childA")
    val childB = context.actorOf(Props[Child], "childB")

    def receive = {
      case "failA" => childA ! "fail"
      case "msgB"  => childB ! "Hello from B"
      case msg     => println(s"Parent received: $msg")
    }
  }

  val system = ActorSystem("AllForOneSystem")
  val parent = system.actorOf(Props[Parent], "myParent")

  parent ! "msgB" // Child B receives a message
  Thread.sleep(100)

  parent ! "failA" // Child A fails, causing all children to restart
  Thread.sleep(1000)

  parent ! "msgB" // Child B (restarted) receives another message
  Thread.sleep(2000)

  system.terminate()
}

การปรับแต่งตัวตัดสินใจ

คุณสามารถสร้างตรรกะการกำกับดูแลที่ปรับแต่งได้อย่างละเอียดโดยใช้ฟังก์ชันตัวตัดสินใจ ฟังก์ชันนี้รับ Throwable (ข้อยกเว้น) และส่งคืน Directive

วิธีนี้ช่วยให้คุณใช้กฎที่ซับซ้อนโดยอิงจากชนิดของข้อยกเว้น ข้อความที่ทำให้เกิดข้อยกเว้น หรือแม้แต่สถานะของผู้กำกับดูแล

การกำกับดูแลขั้นสูง

ต่อไปนี้คือวิธีกำหนดตัวตัดสินใจแบบกำหนดเองภายในกลยุทธ์การกำกับดูแล เพื่อควบคุมการทำงานได้อย่างละเอียด

import akka.actor._
import akka.actor.SupervisorStrategy._
import scala.concurrent.duration._

object CustomDeciderExample extends App {
  class Worker extends Actor {
    def receive = {
      case "criticalError" => throw new IllegalStateException("Critical state!")
      case "minorError"    => throw new IllegalArgumentException("Bad input!")
      case msg             => println(s"Worker: Received $msg")
    }
  }

  class Supervisor extends Actor {
    override val supervisorStrategy: SupervisorStrategy =
      OneForOneStrategy(maxNrOfRetries = 3, withinTimeRange = 10.seconds) {
        case _: IllegalArgumentException => Resume // Minor error, just resume
        case _: IllegalStateException    => Restart // Critical, restart
        case _: Exception                => Stop    // Unknown, stop
        case _                           => Escalate // Others, escalate
      }

    val worker = context.actorOf(Props[Worker], "myWorker")

    def receive = {
      case msg => worker ! msg
    }
  }

  val system = ActorSystem("CustomDeciderSystem")
  val supervisor = system.actorOf(Props[Supervisor], "mySupervisor")

  supervisor ! "Hello"
  supervisor ! "minorError" // Should Resume
  Thread.sleep(100)
  supervisor ! "Hello again" // Worker should still be active
  Thread.sleep(500)

  supervisor ! "criticalError" // Should Restart
  Thread.sleep(100)
  supervisor ! "After critical" // Worker should be restarted
  Thread.sleep(1500)

  system.terminate()
}

ตรวจสอบความรู้ของคุณ

พิจารณาแอกเตอร์แม่ที่กำกับดูแลแอกเตอร์ลูกสองตัว ได้แก่ childA และ childB แอกเตอร์แม่ใช้ AllForOneStrategy พร้อมคำสั่ง Restart สำหรับข้อยกเว้นทั้งหมด

หาก childA ทำให้เกิดข้อยกเว้น จะเกิดอะไรขึ้น

ทบทวน: การทนต่อข้อผิดพลาด

ทำได้ดีมาก! ในบทเรียนนี้ คุณได้เรียนรู้เกี่ยวกับกลไกการกำกับดูแลอันทรงพลังของ Akka:

  • ลำดับชั้นการกำกับดูแล: แอกเตอร์แม่กำกับดูแลแอกเตอร์ลูก
  • คำสั่ง: Resume, Restart, Stop, Escalate
  • OneForOneStrategy: ใช้คำสั่งเฉพาะกับแอกเตอร์ลูกที่ล้มเหลว
  • AllForOneStrategy: ใช้คำสั่งกับแอกเตอร์ลูกทั้งหมด
  • ตัวตัดสินใจแบบกำหนดเอง: ใช้เพื่อควบคุมการจัดการความล้มเหลวได้อย่างละเอียด

เครื่องมือเหล่านี้จำเป็นต่อการสร้างแอปพลิเคชันที่ทำงานพร้อมกันและแข็งแกร่งทนทานด้วยแอกเตอร์ของ Akka

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

บทเรียน “การควบคุมดูแลและความทนทานต่อความผิดพลาด” ฟรีหรือไม่

ใช่ — ข้อความเต็มของ “การควบคุมดูแลและความทนทานต่อความผิดพลาด” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส Scala for Backend Engineering & Functional Programming ให้อัปเกรดเป็น CoddyKit PRO คอร์ส Scala for Backend Engineering & Functional Programming มีบทเรียนทั้งหมด 3 บทเรียน

คุณจะเรียนรู้อะไรในบทเรียน “การควบคุมดูแลและความทนทานต่อความผิดพลาด”

สำรวจกลยุทธ์การควบคุมดูแลของ Akka เพื่อสร้างระบบที่ยืดหยุ่นและกู้คืนจากความล้มเหลวได้อย่างเหมาะสม คุณปฏิบัติ Scala for Backend Engineering & Functional Programming ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน

คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน Scala for Backend Engineering & Functional Programming หรือไม่

ไม่จำเป็นต้องมีประสบการณ์มาก่อน Scala for Backend Engineering & Functional Programming บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 3 จากทั้งหมด 3 บทเรียน

บทเรียน “การควบคุมดูแลและความทนทานต่อความผิดพลาด” ใช้เวลานานแค่ไหน

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

ฉันเขียนและรันโค้ดในบทเรียน Scala for Backend Engineering & Functional Programming นี้ได้ไหม

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

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

  1. พื้นฐานโมเดลแอกเตอร์ของ Akka
  2. การออกแบบระบบแอกเตอร์
  3. การควบคุมดูแลและความทนทานต่อความผิดพลาด
← กลับไปที่ Scala for Backend Engineering & Functional Programming