2012-12-10 4 views
26

Betrachten Sie ein Szenario, in dem ich ein System implementiere, das eingehende Aufgaben mit Akka verarbeitet. Ich habe einen primären Akteur, der Aufgaben empfängt und sie an einige Arbeiterakteure weiterleitet, die die Aufgaben bearbeiten.Was kostet das Erstellen von Schauspielern in Akka?

Mein erster Instinkt besteht darin, dies zu implementieren, indem der Dispatcher für jede eingehende Aufgabe einen Akteur erstellt. Nachdem der Arbeiterakteur die Aufgabe bearbeitet hat, wird er gestoppt.

Dies scheint für mich die sauberste Lösung zu sein, da es dem Prinzip "Eine Aufgabe, ein Akteur" folgt. Die andere Lösung wäre die Wiederverwendung von Akteuren - dies beinhaltet jedoch die zusätzliche Komplexität der Bereinigung und einige Pool-Verwaltung.

Ich weiß, dass Schauspieler in Akka billig sind. Aber ich frage mich, ob mit der wiederholten Erstellung und Löschung von Schauspielern inhärente Kosten verbunden sind. Gibt es versteckte Kosten für die Datenstrukturen, die Akka für die Buchhaltung von Schauspielern verwendet?

Die Last sollte in der Größenordnung von mehreren hundert oder hundert Tasks pro Sekunde liegen - denken Sie daran, dass es sich um einen Produktions-Webserver handelt, der pro Anfrage einen Akteur erstellt.

Die richtige Antwort liegt natürlich in der Profilierung und Feinabstimmung des Systems basierend auf der Art der eingehenden Last. Aber ich fragte mich, ob jemand mir etwas aus eigener Erfahrung erzählen könnte?

SPÄTER EDIT:

ich mehr Details über die Aufgabe zur Hand gegeben sollten:

  • Nur aktive Aufgaben N kann zu einem bestimmten Zeitpunkt ausgeführt werden. Wie @drexin darauf hingewiesen hat - das wäre mit Routern leicht lösbar. Die Ausführung von Aufgaben ist jedoch kein einfacher Lauf und es wird Art der Sache gemacht.
  • Aufgaben erfordern möglicherweise Informationen von anderen Akteuren oder Diensten und müssen daher möglicherweise warten und einschlafen. Dadurch geben sie einen Ausführungs-Slot frei. Der Slot kann von einem anderen wartenden Akteur eingenommen werden, der nun die Möglichkeit zum Laufen hat. Sie könnten eine Analogie mit der Art und Weise machen, wie Prozesse auf einer CPU geplant werden.
  • Jeder Arbeiterakteur muss einen Status bezüglich der Ausführung der Aufgabe behalten.

Hinweis: Ich schätze alternative Lösungen für mein Problem, und ich werde sie auf jeden Fall in Betracht ziehen. Ich möchte aber auch eine Antwort auf die Hauptfrage bezüglich der intensiven Erstellung und Löschung von Akteuren in Akka geben.

+0

Haben Sie irgendwelche vorgeschlagenen Lösungen verwenden? Wir haben genau das gleiche Problem ... – FrEaKmAn

+0

hi !! Hast du eine Antwort gefunden? Wie hast du das Problem gelöst? – naaka

+1

Mein Verständnis ist, dass Schauspieler relativ billig zu erstellen sind.Sie sollten zuerst versuchen, ein Aktorsystem zu entwerfen, mit dem Sie den einfachsten und verständlichsten Code schreiben können. Wenn dies bedeutet, dass eine vorübergehende Aufgabe mit einem eigenen lokalen Zustand in einem kurzlebigen Akteur eingekapselt wird - tun Sie es. Messen Sie dann, und wenn die Leistung nicht gut genug ist, versuchen Sie, das System zu optimieren und die Anzahl der Aktor-Kreationen zu reduzieren, mit dem möglichen Nachteil, dass die Aktor-Logik komplizierter wird. –

Antwort

18

Sie sollten keinen Akteur für jede Anfrage erstellen, Sie sollten lieber einen Router verwenden, um die Nachrichten an eine dynamische Anzahl von Akteuren zu senden. Dafür sind Router da. Lesen Sie diesen Teil der Dokumentation für weitere Informationen: http://doc.akka.io/docs/akka/2.0.4/scala/routing.html

edit:

Erstellen von Top-Level-Akteure (system.actorOf) teuer ist, weil jeder Top-Level-Akteur einen Fehler Kernel als auch initialisieren wird und die sind teuer. Das Erstellen von Kinddarstellern (in einem Schauspieler context.actorOf) ist viel billiger.

Dennoch empfehle ich Ihnen, dies zu überdenken, denn abhängig von der Häufigkeit der Erstellung und Löschung von Akteuren werden Sie auch bedingten Druck auf den GC ausüben.

edit2:

Und am wichtigsten ist, sind Schauspieler keine Threads! Selbst wenn Sie 1 Million Actors erstellen, werden sie nur so viele Threads ausführen, wie der Pool hat. Abhängig von der Durchsatzeinstellung in der Konfiguration verarbeitet jeder Akteur n Nachrichten, bevor der Thread erneut in den Pool freigegeben wird.

Beachten Sie, dass das Blockieren eines Threads (einschließlich des Schlafens) es NICHT in den Pool zurückgibt!

+0

Ja, Router sind in vielen Fällen eine gute Wahl, aber ich denke nicht, dass sie in dieser Situation funktionieren. Ich habe meine Frage ausführlich erklärt, warum. –

+0

aktualisiert meine Antwort – drexin

+0

Update-Nummer 2 – drexin

8

Ein Akteur, der unmittelbar nach seiner Erstellung eine Nachricht erhält und direkt nach dem Senden des Ergebnisses stirbt, kann durch eine Zukunft ersetzt werden. Futures sind leichter als Schauspieler.

Sie können pipeTo verwenden, um das zukünftige Ergebnis zu erhalten, wenn es fertig ist. Zum Beispiel in Ihrem Schauspieler startet die Berechnungen:

def receive = { 
    case t: Task => future { executeTask(t) }.pipeTo(self) 
    case r: Result => processTheResult(r) 
} 

wo executeTask ist Ihre Funktion ein Task Aufnahme ein Result zurückzukehren.

Allerdings würde ich Akteure aus einem Pool über einen Router wiederverwenden, wie in @drexin Antwort erklärt.

+0

Die Arbeiter kommunizieren mit anderen Akteuren im System. Sie laufen nicht nur einen isolierten Code –

+0

Ich detailliert mein Problem ein wenig. Die Verwendung von Futures ist keine Lösung für mich. –

+0

Ich habe Ihre Bearbeitung gelesen und sehe keine Probleme mit Futures. Sie können eine maximale Anzahl von Futures gleichzeitig definieren, indem Sie einen benutzerdefinierten 'ExecutionContext' definieren. Mit den monadischen Operatoren können Sie mehrere Terminkontrakte zusammenstellen (z. B. Futures zu anderen Serviceergebnissen) und einen Zustand weiterleiten. – paradigmatic

0

Ich habe mit 10000 Remote-Akteure aus etwas main Kontext von einem root-Akteur getestet, das gleiche Schema wie in Modul prod ein einzelner Akteur wurde erstellt. MBP 2,5 GHz x2:

  • in main: haupt? Wurzel // Hauptwurzel einen Schauspieler
  • in Haupt erstellen fragt: actorOf (Kind) // erstellen ein Kind
  • in root: watch (Kind) // beobachtet Lifecycle Nachrichten
  • in root: root? Kind // Warte auf Antwort (Verbindungsprüfung)
  • in Kind: Kind! root // Antwort (Verbindung ok)
  • im root: root! Haupt // erstellt benachrichtigen

Code:

def start(userName: String) = { 
    logger.error("HELLOOOOOOOO ") 
    val n: Int = 10000 
    var t0, t1: Long = 0 
    t0 = System.nanoTime 
    for (i <- 0 to n) { 
    val msg = StartClient(userName + i) 
    Await.result(rootActor ? msg, timeout.duration).asInstanceOf[ClientStarted] match { 
    case succ @ ClientStarted(userName) => 
     // logger.info("[C][SUCC] Client started: " + succ) 
    case _ => 
     logger.error("Terminated on waiting for response from " + i + "-th actor") 
     throw new RuntimeException("[C][FAIL] Could not start client: " + msg) 
    } 
    } 
    t1 = System.nanoTime 
    logger.error("Starting of a single actor of " + n + ": " + ((t1 - t0)/1000000.0/n.toDouble) + " ms") 
} 

Das Ergebnis:

Starting of a single actor of 10000: 0.3642917 ms 

Es wurde eine Meldung besagt, dass zwischen "HELOOOOOOOO" und „Starten von einem einzigen "Slf4jEventHandler gestartet" ", so scheint das Experiment noch realistischer (?)

Dispatcher war ein Standard (ein PinnedDispatcher, der jeweils einen neuen Thread und jedes Mal), und es schien, als ob das ganze Zeug ist das gleiche wie Thread.start() war, seit langer Zeit seit Java 1 - 500K-1M Zyklen oder so ^)

Deshalb habe ich den gesamten Code innerhalb der Schleife geändert, zu einem new java.lang.Thread().start()

Das Ergebnis:

Starting of a single actor of 10000: 0.1355219 ms