Verteilte Algorithmen TI5005 Th. Letschert TH Mittelhessen Gießen University of Applied Sciences Aktoren Aktor-Modell Aktor-Implementierung: Akka-Aktoren
Aktoren Aktoren: Modell und Progammkonstrukt Computer science is about understanding and construction. R. Milner Aktoren als Programmkonstrukt Abgeschlossene reaktive Objekte: Zustand + Verhalten Aktoren haben einen jeweils eigenen Zustand es gibt keinen gemeinsamen Zustand Sie interagieren ausschließlich über asynchrone Nachrichten Jeder Aktor hat eine Mailbox eine Queue für alle eingehenden Nachrichten Nachrichtenverarbeitung Eintreffende Nachrichten aktivieren lokale Handler Handler werden aktiv wenn die Mailbox eine passende Nachricht enthält Aktor-Modell Variante der Modelle verteilte Systeme mit asynchroner Kommunikation mit sehr hoher Ausdruckskraft (dynamische Topologie; alles kann gesendet werden) Seite 2
Aktor-Modell Computer science is about understanding and construction. R. Milner Prinzipien Ein Aktor ist eine atomare Entität sie hat ein Verhalten d. h. er reagiert in einer bestimmten Weise auf eintreffende Nachrichten er kann als Teil seines Verhaltens Nachrichten erzeugen und an andere Aktoren versenden er kann sein Verhalten mit dem Eintreffen von Nachrichten ändern Nachrichten sind 1. unveränderliche Werte (Version A des Aktor-Modells: Wert vs Aktor) 2. selbst wieder Aktoren (Version B des Aktor-Modells: alles ist ein Aktor) Verwendung Theoretische Untersuchungen zu den Grundlagen der Informatik Was ist die Essenz verteilter und nebenläufiger Systeme Autoren: Carl Hewitt / Gul Agha / Irene Greif... Beschreibung Gul A. Agha. ACTORS: A Model of Concurrent Computation in Distributed Systems. Series in Articial Intelligence. The MIT Press, Cambridge, Massachusetts, 1986. Carl Hewitt on the actor model - live: http://ak.channel9.msdn.com/ch9/01ad/2ca509cc-d410-4663-8679-6ece29cd01ad/langnext2012hewittmeijerszyperkiactors_mid.mp4 Seite 3
Aktor-Modell Charakteristika des Aktor-Modells Asynchrone Kommunikation Senden und Empfangen sind entkoppelt Nachrichten können verloren gehen Aktive Elemente können nicht ausfallen Nachrichtenzustellung The completely unknown outer space Die Zustellung der Nachrichten ist außerhalb des Modells gesendete Nachrichten können in beliebiger Reihenfolge irgendwann zugestellt werden oder auch nicht Einzige Restriktion: Nur gesendete Nachrichten werden ausgeliefert Ein Aktor kann sein Verhalten mit dem Eintreffen einer Nachricht ändern Nachrichten sind 1. unveränderliche Werte (Version A des Aktor-Modells) 2. Aktoren (Version B des Aktor-Modells) Toplogie Aktornetze sind dynamisch keine Kanäle / Ports o.ä.; Nachrichten werden direkt an Aktoren gesendet auch Aktoren können (eventuell) versendet werden Seite 4
Aktoren Computer science is about understanding and construction. R. Milner Aktor-Implementierungen Programmiersprachen / Bibliotheken denen das Aktor-Modell zugrunde liegt, gibt es großer Vielfalt. http://en.wikipedia.org/wiki/actor_model Seite 5
Aktor-Systeme Aktoren in Scala / Akka Akka installieren, Akka-Aktoren statt Scala-Aktoren verwenden. Der Akka-Download enthält eine Dokumentation in der Datei AkkaScala.pdf. Scala-Aktoren sind überholt Akka-Aktoren zum Klassenpfad hinzufügen! Seite 6 scala.actors.actor trait Actor in package actors is deprecated: Use the akka.actor package instead.
Aktor-Systeme Aktoren in Scala / Akka Beispiel akka.actor.{actor, ActorSystem, Props class MyActor extends Actor { case s: String => println("actor received \"" + s + "\" from " + sender ) case _ => println("actor received unknown msg from " + sender ) Aktortyp als Ableitung von Actor Die receive-methode definiert das Verhalten als Reaktion auf empfangene Nachrichten. object Actor_example_1 extends App { val system = ActorSystem("MySystem") val myactor = system.actorof(props[myactor], name = "myactor") myactor! "Hello actor" Aktoren leben in einem ActorSystem Aktor-Instanzen werden mit einer FabrikMethode erzeugt. Mit! wird an einen Aktor gesendet. Thread.sleep(1000) system.shutdown Der main-thread sendet, er ist kein Actor, darum wird als Absender deadletter angegeben. Actor received "Hello actor" from Actor[akka://MySystem/deadLetters] deadletters Thread main sendet an Aktor myacvtor. Die Zustellung erfolgt indirekt via deadletters main myactor Seite 7
Akka-Aktoren Basis-Komponenten Actor Instanz einer Aktor-Klasse Reaktives Verhalten Daten komplett gekapselt (keinerlei Zugriff von außen möglich) Ausführung stets single-theaded (In der Regel) kein dezidierter Thread zugeordnet ActorRef Referenz auf eine Aktor-Instanz Aktoren werden immer über eine Referenz angesprochen ActorSystem Mechanismus zum Ausführen der Aktor-Logik vergleichbar mit Executor enthält Dispatcher Msg ActorRef ActorKlasse Mailbox Mailbox Nachrichteneingang des Aktors Actor Instanz ActorSystem Seite 8
Akka-Aktoren / System Ausführung Mailbox Nachrichten-Queue, üblicherweise pro Actor ActorKlasse Msg Aktor Instanz Zustand des Aktors Aktor-Klasse Actor Instanz Mailbox Code der Nachrichtenverarbeitung Msg State Code Dispatcher Dispatcher Bringt Nachricht und Aktor zusammen: Führt den der Nachrichtenverarbeitung aus Nutzt dazu einen Executor Dieser steht als ExecutionContext zur Verfügung z.b. zur Ausführung von Futures dispatch receive Executor ActorSystem Seite 9
Akka-Aktoren / System Aktor-System Aktoren sind hierarchisch angeordnet Jeder (bis auf einen) Aktor hat einen Aktor der ihn erzeugt hat Jeder Aktor kann andere Aktoren erzeugen seine Abkömmlinge / Kinder Aktoren überwachen und kontrollieren ihre Abkömmlinge Ein Aktor-System hat stets drei Guardian-Aktoren Root Guardian Actor erzeugt und beaufsichtigt die beiden anderen (User) Guardian Actor erzeugt und beaufsichtigt alle Aktoren des Benutzers System Guardian Actor für System-Aufgaben Ein Aktor-System muss einen eindeutigen Namen haben bietet Zugriff auf all seine Aktoren zum Senden von Nachrichten Stoppen von Aktoren kann konfiguriert werden kann gestoppt werden aus der Akka-Dokumentation Seite 10
Akka-Aktoren Nachrichten Senden und Empfangen Empfangen Aktoren können Nachrichten von jedem Typ empfangen Der Empfang wird üblicherweise über Pattern-Matching definiert und erfolgt asynchron Senden Tell-Syntax: <ActorRef>! Msg Die Nachricht wird in der Mailbox abgelegt Ask-Syntax: <ActorRef>? Msg Die Nachricht wird in der Mailbox abgelegt und Der Sender erhält ein Future-Objekt mit der (zukünftigen) Antwort des Empfängers Seite 11
Akka-Aktoren / Senden und Empfangen Tell-Beispiel akka.actor.{actor, ActorSystem, Props akka.actor.actorref case class AddRequest(x: Int, y: Int) case class AddResult(z: Int) DoIt AddRequest AddResult main compuerequester computeactor class ComputeActor extends Actor { case AddRequest(x, y) => sender! AddResult(x+y) case _ => println("actor received unexpected msg from " + sender ) case object DoIt Aktor 1 rechnet Aktor 2 lässt rechnen 2 Aktoren sind class ComputeRequester(computeActor : ActorRef) extends Actor { an einen Aktor case DoIt => computeactor! AddRequest(5, 6) case AddResult(v) => println("computerequester received " + v + " from " + sender ) case _ => println("actor received unexpected msg from " + sender ) notwendig, damit eine Antwort gesendet werden kann object Actor_ex extends App { val system = ActorSystem("MySystem") val computeactor = system.actorof(props[computeactor], name = "computeactor") val computerequester = system.actorof(props(classof[computerequester], computeactor), name = "computerequestor") Der Kunde wird mit einer Referenz auf den Server erzeugt. computerequester! DoIt Thread.sleep(1000) system.shutdown Seite 12
Akka-Aktoren / Senden und Empfangen Beispiel mit Ask (1): Asynchrone Verarbeitung des Ergebnisses akka.actor.{actor, ActorSystem, Props akka.pattern.ask akka.util.timeout scala.concurrent.duration._ scala.util.{success, Failure AddRequest Future main case class AddRequest(x: Int, y: Int) case class AddResult(z: Int) computeactor AddResult(11) class ComputeActor extends Actor { case AddRequest(x, y) => sender! AddResult(x+y) case _ => println("actor received unexpected msg from " + sender ) Aktor sendet die Antwort an sender mit tellsyntax (!). Die Variable sender ist mit dem Sender der zuletzt empfangenen Nachricht belegt. object Actor_ex extends App { val system = ActorSystem("MySystem") val computeactor = system.actorof(props[computeactor], name = "computeactor") implicit val timeout = Timeout(1 seconds) Die Anfrage wird mit der Ask-Syntax (?) gestellt. Dazu muss es ein Timeout definiert sein. val futureresult = computeactor? AddRequest(5,6) implicit val execcontext = system.dispatcher futureresult.oncomplete{ case Success(result) => println(result) case Failure(failure) println("failed because of " + failure) Thread.sleep(1000) system.shutdown Seite 13 Da das Ergebnis ein Future-Objekt ist, kann es asynchron von einem Executor verarbeitet werden. Jedes Aktor-System hat mit seinem Dispatcher einen passenden Executor.
Akka-Aktoren / Senden und Empfangen Beispiel mit Ask (2): Synchrone Verarbeitung des Ergebnisses akka.actor.{actor, ActorSystem, Props scala.concurrent.await akka.pattern.ask akka.util.timeout scala.concurrent.duration._ scala.util.{success, Failure case class AddRequest(x: Int, y: Int) case class AddResult(z: Int) class ComputeActor extends Actor { case AddRequest(x, y) => sender! AddResult(x+y) case _ => println("actor received unexpected msg from " + sender ) object Actor_ex extends App { val system = ActorSystem("MySystem") val computeactor = system.actorof(props[computeactor], name = "computeactor") implicit val timeout = Timeout(1 seconds) val futureresult = computeactor? AddRequest(5,6) val result = Await.result(futureResult, timeout.duration) println(result) Thread.sleep(1000) system.shutdown Seite 14 Da das Ergebnis ein Future-Objekt ist, kann es auch synchron mit Await.result abgewartet und dann verarbeitet werden.
Akka-Aktoren / Senden und Empfangen Ask: synchron / asynchron; blockierend / nicht blockierend object Actor_ex extends App {... val result = Await.result(futureResult, timeout.duration) println(result)... class MediatorActor(computeActor: ActorRef) extends Actor { implicit val timeout = Timeout(1 seconds) implicit val execcontext = context.dispatcher case AddRequest(x, y) => { val futureresult = computeactor? AddRequest(x+1,y-1) val lsender = sender futureresult.oncomplete{ case Success(result) => lsender! result case Failure(failure) println("failed because of " + failure) case _ => println("actor received unexpected msg from " + sender ) Seite 15 Auf das Ergebnis wird in einem Thread gewartet: OK Wir sind in einem Aktor, aber das Ergebnis wird asynchron erwartet und verarbeitet: OK
Akka-Aktoren / Senden und Empfangen Ask: synchron / asynchron; blockierend / nicht blockierend class MediatorActor(computeActor: ActorRef) extends Actor { implicit val timeout = Timeout(1 seconds) implicit val execcontext = context.dispatcher case AddRequest(x, y) => { val futureresult = computeactor? AddRequest(x+1,y-1) /* val lsender = sender futureresult.oncomplete{ case Success(result) => lsender! result case Failure(failure) println("failed because of " + failure) */ sender! Await.result(futureResult, timeout.duration) case _ => println("actor received unexpected msg from " + sender ) Seite 16 Wir sind in einem Aktor, aber das Ergebnis wird blockierend erwartet: Problematisch, sollte vermieden werden.
Akka-Aktoren / Erzeugen Aktor-Erzeugung Aktoren können auf zwei Arten erzeugt werden aus Aktor-Klassen via Default-Konstruktor: system.actorof(props[aktor-klasse],...) classof[a] ~ A.class in Java aus Aktor-Klassen via Konstruktor mit Parameter: system.actorof(props(classof[aktor-klasse], argumente),...) Beispiel:... class ComputeActor extends Actor { // Aktor mit default-konstruktor case AddRequest(x, y) => sender! AddResult(x+y) case _ => println("actor received unexpected msg from " + sender ) class ComputeRequester(computeActor : ActorRef) extends Actor { // Aktor mit Konstruktor case DoIt => computeactor! AddRequest(5, 6) case AddResult(v) => println("computerequester received " + v + " from " + sender ) case _ => println("actor received unexpected msg from " + sender ) object Actor_ex extends App { val system = ActorSystem("MySystem") val computeactor = system.actorof(props[computeactor], name = "computeactor") val computerequester = system.actorof( Props(classOf[ComputeRequester], computeactor), name = "computerequestor")... Seite 17
Akka-Aktoren / Erzeugen Aktor-System Aktoren gehören stets zu einem Aktor-System Eine Anwendung hat typischerweise genau ein Aktor-System Aktoren in Aktoren Aktor-Klassen dürfen nicht in anderen Aktor-Klassen definiert werden Aktoren dürfen andere Aktoren erzeugen class Parent extends Actor { childactor = context.actorof(props[child], name = "child")) Aktor-Erzeugung in einem Aktor: context statt system Seite 18
Akka-Aktoren / Erzeugen und Starten Beispiel Aktor in Aktor erzeugen akka.actor.{actor, ActorSystem, Props, ActorRef abstract sealed class Msg case object Create extends Msg case class Info(s: String) extends Msg class Child extends Actor { case Info(s) => println("child received \"" + s + "\" from " + sender ) case _ => println("what?" ) class Parent extends Actor { var child : Option[ActorRef] = None case Create => child match { case Some(a)=> println("create ignored, child exists") case None => child = Some(context.actorOf(Props[Child], name = "child")) case Info(s) => child match { case Some(a)=> a! Info(s) case None => println("no child to send to!") case _ => println("what?" ) object CreateExample_App extends App { val system = ActorSystem("MySystem") val parent = system.actorof(props[parent], name = "parent") parent! Create parent! Info("Hello") parent! Create Create ignored, child exists Child received "Hello" from Actor[akka://MySystem/user/parent#-2096290448] Seite 19
Akka-Aktoren Pfad Identifikation eines Aktors durch eine URL Jeder (Benutzer-) Aktor kann durch eine URL im Format: akka://actorsystem/user/aktorname-1/../aktorname-n adressiert werden. Beispiel: system.actorselection("akka://mysystem/user/master/slave_1")! "Hi" Sende an den Aktor mit dem Pfad akka://mysystem/user/master/slave_1 Dieser Aktor ist ein user-aktor (kein System-Aktor) im System mit dem Namen MySystem er wird beaufsichtigt vom Aktor mit dem Pfad akka://mysystem/user/master und heißt slave_1. Seite 20
Akka-Aktoren / Pfad Pfad Beispiel: akka.actor.actor akka.actor.actorsystem akka.actor.props akka.actor.actorref case class C(i: Int) class Slave(i: Int) extends Actor { case s:string => println(self + " received " + s) case _ => println(self + " received unknown msg") class Master extends Actor { var slave : Array[Option[ActorRef]] = Array(None, None, None) case C(i) => slave(i) = Some(context.actorOf(Props(classOf[Slave], i), name = "slave_"+i)) case msg: String => // forward msg to slaves slave.foreach(_.get! msg) object PathExample_App extends App { val system = ActorSystem("MySystem") val master = system.actorof(props[master], name = "master") Aktor-Erzeugung im context. Aktor-Erzeugung im system. // create slaves (0 until 3).foreach(master! C(_) ) // to slaves via master: master! "Hello Slave?" Aktor-Identifizierung via Aktor-Referenz // to slaves directly (0 until 3).foreach( i => system.actorselection("akka://mysystem/user/master/slave_"+i)! "Hi" ) Seite 21 Aktor-Identifizierung via Pfad
Akka-Aktoren / Context Context Jeder Aktor hat einen zugeordneten Context Aktor-Erzeugung Zugriff auf das Aktor-System In diesem Context können Aktoren erzeugt werden und auf das System zugegriffen werden akka.actor.{actor, ActorSystem, Props, ActorRef case object Msg class AnActor extends Actor { case Msg => println ("I'm part of system: " + context.system + " my path is " + self.path) val a1 = context.system.actorof(props[anactor], name= "AA1") // erzeugt akka://mysystem/user/aa1 val a2 = context.actorof(props[anactor], name= "AA2") // erzeugt akka://mysystem/user/a/aa2 a1! "Blub" a2! "Blubber" // -> akka://mysystem/user/aa1 // -> akka://mysystem/user/a/aa2 case m:string => println("actor " + self.path + " received msg " + m + " from " + sender ) object Context_ex extends App { val system = ActorSystem("MySystem") val a = system.actorof(props[anactor], name= "A") a! Msg Thread.sleep(1000) system.shutdown I'm part of system: akka://mysystem my path is akka://mysystem/user/a Actor akka://mysystem/user/a/aa2 received msg Blubber from Actor[akka://MySystem/user/A#-65580956] Actor akka://mysystem/user/aa1 received msg Blub from Actor[akka://MySystem/user/A#-65580956] Seite 22
Akka-Aktoren / Context Zugriff auf verwandte Aktoren Im Context können Aktoren auf ihre Eltern und Kinder zugreifen class AnActor extends Actor { case Msg => val a1 = context.actorof(props[anactor], name= "A1") val a2 = context.actorof(props[anactor], name= "A2") for (c <- context.children) { println("child " + c.path) println("parent: " + context.parent.path) case m:string => println("actor " + self.path + " received msg " + m + " from " + sender ) Seite 23
Akka-Aktoren / Erzeugen und Starten Erzeugung ~> Start Aktoren werden bei ihrer Erzeugung sofort gestartet. Eine besondere Start-Anweisung ist unnötig. Varianten der Erzeugung: Im System-Kontext: val system = ActorSystem("MySystem") val myactor = system.actorof(props[myactor], name = "myactor") Im System-Kontext mit Konstruktor-Parametern: val system = ActorSystem("HelloSystem") val myactor = system.actorof(props(classof[myactor],x), name = "myactor") Innerhalb eines Aktors: context.actorof(props[myactor], name = "myactor") bzw.: context.actorof(props(classof[myactor],x), name = "myactor") Seite 24
Akka-Aktoren / Aktor-DSL Actor-DSL Aktoren können mit der Aktor-DSL erzeugt und gestartet werden akka.actor.actordsl._ akka.actor.actorsystem object ActorDsl_App extends App { val system = ActorSystem("MySystem") val my_actor = actor(system)(new Act { become { case s:string => println("my_actor received "+s+" from "+sender) ) my_actor! "Hello" become : das ursprünglich leere Verhalten des Basis-Aktors wird durch ein neues Verhalten ersetzt. akka.actor.actordsl._ akka.actor.actorsystem object ActorDsl_App extends App { implicit val system = ActorSystem("MySystem") val my_actor = actor(new Act { become { case s:string => println("my_actor received "+s+" from "+sender) ) my_actor! "Hello" system ist hier impliziter Parameter Seite 25
Akka-Aktoren / Hooks Start- / Stop-Hooks prestart / whensarting wird vor dem Start des Aktors aufgerufen akka.actor.actor akka.actor.actorsystem akka.actor.actordsl._ akka.actor.props class HookedActor extends Actor { override def prestart { println("i'm going to be started") poststop / whenstopping wird nach dem Stopp aktiviert override def poststop { println("i was stoped") case _ => println("i've got a msg") object HooksExample_App extends App { implicit val system = ActorSystem("MySystem") val actor_1 = system.actorof(props[hookedactor], name = "actor_1") val actor_2 = actor(new Act { whenstarting { println("i'm going to be started - DSL version") whenstopping { println("i was stopped - DSL version") become { case _ => println("i've got a msg") ) system.shutdown Seite 26
Akka-Aktoren / Stoppen, Beenden Stoppen von Aktoren: Aktoren können mit Giftpillen und der Stop-Methode gestoppt werden System beenden: Das gesamte Aktorsystem kann mit shutdown herunter gefahren werden akka.actor.actorsystem akka.actor.actordsl._ akka.actor.poisonpill object Example_7 extends App { implicit val system = ActorSystem("MySystem") val actor_1 = actor(system)(new Act { override def poststop() { println("actor 1 got stopped") whenstarting { actor_2! "Hello" actor_2! PoisonPill actor_2! "Still alive?" ) Actor 1 tötet Aktor 2 mit einer Giftpille val actor_2 = actor(new Act { override def poststop() { println("actor 2 got stopped") become { case s:string => println("actor 2 received " + s + " from " + sender) ) Thread.sleep(1000) println("main stops actor 1") system.stop(actor_1) Actor 1 wird vom main-thread gestoppt system.shutdown Das Aktor-System wird herunter gefahren Seite 27
Akka-Aktoren / Stoppen, Beenden Stoppen von Aktoren: (1) Graceful Stop akka.actor.{actor, ActorSystem, Props, ActorRef, ActorSelection, Terminated akka.actor.actordsl._ akka.pattern.gracefulstop scala.concurrent.{await, Future akka.util.timeout scala.concurrent.duration._ class Helper(i: Int) extends Actor { case s:string => println(self + " received " + s) Der Aktor Helper 1 wird mit Anmut gestoppt werden. // worker with 3 helpers // helper 1 will be stopped by main class Worker extends Actor { var helper : Array[Option[ActorRef]] = Array(None, None, None) Der Aktor Worker override def prestart() { for ( i <- 0 until 3 ) { helper(i) = Some(context.actorOf(Props(classOf[Helper], i), name = "helper_"+i)) case msg: String => for (h <- context.children) { h! msg erzeugt 3 Helper. Alle Nachrichten werden an die Helper weiter geleitet. Seite 28
Akka-Aktoren / Stoppen, Beenden Stoppen von Aktoren: (2) Graceful Stop object GracefulExample_App extends App { val system = ActorSystem("MySystem") val worker = system.actorof(props[worker], name = "worker") worker! "Hi" Thread.sleep(100) val actorsel : ActorSelection = system.actorselection("akka://mysystem/user/worker/helper_1") implicit val timeout = Timeout(1 seconds) implicit val execcontext = system.dispatcher // Get reference to helper 1 from selection: val actorreffuture : Future[ActorRef] = actorsel.resolveone() val actorref : ActorRef = Await.result(actorRefFuture, 1 seconds) // stop helper 1 gracefully val stopped: Future[Boolean] = gracefulstop(actorref, 5 seconds) Await.result(stopped, 6 seconds) println(if (stopped.value.get.get) "Helper 1 was stopped" else "Helper 1 was not stoped") Thread.sleep(100) worker! "How are you?" Thread.sleep(1000) system.shutdown Actor[akka://MySystem/user/worker/helper_1#1385525542] received Hi Actor[akka://MySystem/user/worker/helper_0#1076368432] received Hi Actor[akka://MySystem/user/worker/helper_2#770406319] received Hi Helper 1 was stopped Actor[akka://MySystem/user/worker/helper_2#770406319] received How are you? Actor[akka://MySystem/user/worker/helper_0#1076368432] received How are you? Seite 29