スーパービジョンと耐障害性
Akkaのスーパービジョン戦略を学び、障害から適切に復旧できる堅牢なシステムを構築します。
「スーパービジョンと耐障害性」はCoddyKit上の無料Scala for Backend Engineering & Functional Programmingレッスンです。 これはレッスン3/3です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはScala for Backend Engineering & Functional Programming学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 Scala for Backend Engineering & Functional Programmingコースには全3レッスンが含まれています。
レジリエントなシステムを構築する
並行アプリケーションでは、問題が発生する可能性があります。アクターがクラッシュしたり、ネットワーク呼び出しが失敗したり、データベースに接続できなかったりすることがあります。
フォールトトレランスとは、システムの一部に障害が発生しても、システムが正しく動作し続ける能力です。Akkaには、これを実現するための強力な仕組みが用意されています。
親子スーパービジョン
Akkaのアクターは、家系図のような階層で構成されます。あるアクターが別のアクターを作成すると、作成した側が親になります。
- 親は子を監督する責任を負います。
- 子アクターに障害が発生すると、親は通知を受け取ります。
- その後、親が子の障害への対処方法を決定します。これをスーパービジョン戦略と呼びます。
障害への対応を決定する
アクターに障害が発生した場合、親は4つのディレクティブのいずれかを発行できます。
Resume:障害を無視して、アクターがメッセージの処理を続行します。Restart:アクターを停止して再作成した後、処理を再開します。内部状態はリセットされます。Stop:アクターを完全に終了します。Escalate:障害を親のスーパーバイザー(祖父母アクター)に引き継ぎます。
OneForOne戦略
OneForOneStrategyは、よく使われるスーパービジョン戦略です。これは、ディレクティブが障害を起こした子アクターにのみ適用されることを意味します。
たとえば、1つの子アクターがクラッシュした場合、親はその子だけを再起動し、他の子アクターには影響を与えないようにできます。
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戦略
場合によっては、1つの子の障害によって、すべての兄弟アクターも影響を受けたり、正しく機能できなくなったりすることがあります。
AllForOneStrategyは、障害を起こした子だけでなく、スーパーバイザーのすべての子に同じディレクティブを適用します。1つの子に障害が発生すると、選択されたディレクティブの影響がすべての子に及びます。
AllForOneの動作
親がAllForOneStrategyを使用するように変更してみましょう。1つの子の障害が兄弟アクターにどのような影響を与えるかに注目してください。
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という2つの子アクターを監督する親アクターを考えてみましょう。親は、すべての例外に対してRestartディレクティブを適用するAllForOneStrategyを使用しています。
childAが例外をスローすると、何が起こるでしょうか。
振り返り:フォールトトレランス
よくできました。このレッスンでは、Akkaの強力なスーパービジョンの仕組みについて学びました。
- スーパービジョン階層:親が子を監督します。
- ディレクティブ:
Resume、Restart、Stop、Escalate OneForOneStrategy:障害が発生した子にのみディレクティブを適用します。AllForOneStrategy:すべての子にディレクティブを適用します。- カスタム判定関数:障害処理をきめ細かく制御します。
これらのツールは、Akka Actorsで堅牢でレジリエントな並行アプリケーションを構築するために不可欠です。
よくある質問
「スーパービジョンと耐障害性」レッスンは無料ですか?
はい。「スーパービジョンと耐障害性」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、Scala for Backend Engineering & Functional Programmingコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 Scala for Backend Engineering & Functional Programmingコースには全3レッスンが含まれています。
「スーパービジョンと耐障害性」で何を学びますか?
Akkaのスーパービジョン戦略を学び、障害から適切に復旧できる堅牢なシステムを構築します。 ブラウザで直接実行するハンズオンコードでScala for Backend Engineering & Functional Programmingを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。
Scala for Backend Engineering & Functional Programmingを始めるのに経験は必要ですか?
事前経験は必要ありません。CoddyKitのScala for Backend Engineering & Functional Programmingは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン3/3です。
「スーパービジョンと耐障害性」レッスンにはどのくらい時間がかかりますか?
ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。
このScala for Backend Engineering & Functional Programmingレッスンでコードを書いて実行できますか?
はい。すべてのScala for Backend Engineering & Functional Programmingレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。
このコースのすべてのレッスン
- Akkaアクターモデルの基礎
- アクターシステムの設計
- スーパービジョンと耐障害性