5

Ich bin ein wenig verwirrt.Play Framework 2.X und blockierende Datenbank Anruf

Vom documentation:

Play-Standard-Thread-Pool - Dies ist der Standard-Thread-Pool, in der alle Anwendungscode im Play-Framework ausgeführt wird, ohne etwas iteratees Code. Es ist ein Akka Dispatcher und kann konfiguriert werden durch Konfiguration Akka, unten beschrieben. Standardmäßig hat es einen Thread pro Prozessor.

Ist es Nutzen bringt einen blockierende Datenbank-Aufruf in einem Future, den Anruf an die Future wurde selbst von einem async Controller (Rückkehr it), um eingewickelt wickeln Sie den Standardthreadpool Umgang mit anderen Benutzern zu lassen Anfragen?

Es würde nur den blockierenden Code in einem anderen Thread (von einem dedizierten ExecutionContext) verschieben, aber lassen Sie die Action nicht blockiert.

Ich stieß auf this post, aber ich bin nicht mit der angegebenen Antwort zufrieden.
Wenn ich die Blockierung der Datenbank innerhalb des Standardthreadpools zulassen würde, würde das möglicherweise nicht verhindern, dass andere Benutzeranforderungen bearbeitet werden, die in der Zwischenzeit nicht von der Datenbank abhängen?

Hinweis: Meine Datenbank (Neo4j) hat keinen Async-Treiber.

Antwort

8

Es gibt verschiedene Möglichkeiten, blockierende Anrufe zu behandeln. Ich kann nicht sagen, welches das Beste ist, da es sehr wahrscheinlich von bestimmten Anwendungsfällen abhängen würde und eine Menge Benchmarking erfordern würde.

Standardmäßig verarbeitet Play Anfragen mit einem Thread-Pool mit einem Thread pro CPU-Kern. Wenn Sie Ihre Play-App beispielsweise auf einer Quad-Core-CPU ausführen, kann sie nur 4 gleichzeitige Anfragen verarbeiten, wenn sie blockierende Aufrufe an die Datenbank verwenden. Ja, alle anderen eingehenden Anfragen müssen warten, bis einer der Threads freigegeben wurde.

Die einfachste Lösung ist die Anzahl der Threads Wiedergabe zu erhöhen verwendet, um Anforderungen in dem Standard-Thread-Pool (in application.conf) zu verarbeiten:

play { 
    akka { 
    akka.loggers = ["akka.event.slf4j.Slf4jLogger"] 
    loglevel = WARNING 
    actor { 
     default-dispatcher = { 
      fork-join-executor { 
      parallelism-min = 300 
      parallelism-max = 300 
      } 
     } 
    } 
    } 
} 

Die nächste Option ist diejenige, die Sie in Ihrem Frage- erwähnen -loading blockierende Datenbankaufrufe zu einem anderen ExecutionContext. Sie können wie so einen separaten Thread-Pool innerhalb application.conf konfigurieren:

database-io { 
    fork-join-executor { 
     parallelism-factor = 10.0 
    } 
} 

Dieser eine 10 Threads pro CPU-Kern im Pool genannt database-io und können wie so innerhalb Wiedergabe zugegriffen werden schaffen:

val dbExecutor: ExecutionContext = Akka.system.dispatchers.lookup("database-io") 

val something = Future(someBlockingCallToDb())(dbExecutor) 

Dadurch kann der Standard-Thread-Pool mehr Anfragen verarbeiten, während er auf das Ende der Future wartet. Eine dritte Option wäre die Verwendung eines Actor, um die Datenbankaufrufe zu behandeln, aber das ist komplizierter und über den Rahmen dieser Frage hinaus.

Unterm Strich ist, ja, einen größeren Thread-Pool oder eine anderes ExecutionContext zum Blockieren Anrufe verwenden, wie Sie nie im Standard-Thread-Pool blockieren möchten, wenn Sie ihm helfen können.

Dies ist alles in der Play Documentation for Thread Pools umrissen. (neueste Version)

+0

Schöne Antwort :) In der Tat könnten engagierte Schauspieler hinter einem Router den Trick auch machen. Aber ich verstehe nicht, warum der andere Beitrag evoziert: "Sie haben nichts, um nicht zu blockieren, dann gibt es keinen Grund, Ihren Controller asynchron zu machen". Wenn ich die "ExecutionContext-Lösung" verwende, muss ich einen asynchronen Controller haben, stimmst du zu? – Mik378

+0

'Aktion' s sind standardmäßig async. 'Action.async' ist nur eine Annehmlichkeit für den Umgang mit' Future's. Es gab zu viele Negative in der zitierten Aussage, um wirklich klar zu sein. Ich würde bei dem separaten "ExecutionContext" bleiben. –

+0

Ja, ich weiß es. Die Wiedergabe ist zu 100% asynchron. Async ist nur ein Weg, um die Zukunft leichter zu bewältigen. Aber wenn ich die klassische 'Aktion' verwende, müsste ich den Aufruf an die Datenbank mit einer Zukunft machen, dann' Await.result' (um das Ergebnis zu erhalten), dann das Ergebnis angeben (wie 'Ok (myResult)'), da 'Action' kein' Future [Result] 'zurückgibt, sondern intern einfach eine Zukunft verwendet. Und'Await' würde blockieren. – Mik378