clarify need to carefully managing blocking, see #2616
This commit is contained in:
parent
ef80428b02
commit
c27389ca1b
7 changed files with 86 additions and 10 deletions
|
|
@ -89,10 +89,9 @@ Actor Best Practices
|
|||
bothering everyone else needlessly and avoid hogging resources. Translated
|
||||
to programming this means to process events and generate responses (or more
|
||||
requests) in an event-driven manner. Actors should not block (i.e. passively
|
||||
wait while occupying a Thread) on some external entity, which might be a
|
||||
lock, a network socket, etc. The blocking operations should be done in some
|
||||
special-cased thread which sends messages to the actors which shall act on
|
||||
them.
|
||||
wait while occupying a Thread) on some external entity—which might be a
|
||||
lock, a network socket, etc.—unless it is unavoidable; in the latter case
|
||||
see below.
|
||||
|
||||
#. Do not pass mutable objects between actors. In order to ensure that, prefer
|
||||
immutable messages. If the encapsulation of actors is broken by exposing
|
||||
|
|
@ -112,6 +111,53 @@ Actor Best Practices
|
|||
performance) and it also reduces the number of blocking calls made, since
|
||||
the creation of top-level actors involves synchronous messaging.
|
||||
|
||||
Blocking Needs Careful Management
|
||||
---------------------------------
|
||||
|
||||
In some cases it is unavoidable to do blocking operations, i.e. to put a thread
|
||||
to sleep for an indeterminate time, waiting for an external event to occur.
|
||||
Examples are legacy RDBMS drivers or messaging APIs, and the underlying reason
|
||||
in typically that (network) I/O occurs under the covers. When facing this, you
|
||||
may be tempted to just wrap the blocking call inside a :class:`Future` and work
|
||||
with that instead, but this strategy is too simple: you are quite likely to
|
||||
find bottle-necks or run out of memory or threads when the application runs
|
||||
under increased load.
|
||||
|
||||
The non-exhaustive list of adequate solutions to the “blocking problem”
|
||||
includes the following suggestions:
|
||||
|
||||
- Do the blocking call within an actor (or a set of actors managed by a router
|
||||
[:ref:`Java <routing-java>`, :ref:`Scala <routing-scala>`]), making sure to
|
||||
configure a thread pool which is either dedicated for this purpose or
|
||||
sufficiently sized.
|
||||
|
||||
- Do the blocking call within a :class:`Future`, ensuring an upper bound on
|
||||
the number of such calls at any point in time (submitting an unbounded
|
||||
number of tasks of this nature will exhaust your memory or thread limits).
|
||||
|
||||
- Do the blocking call within a :class:`Future`, providing a thread pool with
|
||||
an upper limit on the number of threads which is appropriate for the
|
||||
hardware on which the application runs.
|
||||
|
||||
- Dedicate a single thread to manage a set of blocking resources (e.g. a NIO
|
||||
selector driving multiple channels) and dispatch events as they occur as
|
||||
actor messages.
|
||||
|
||||
The first possibility is especially well-suited for resources which are
|
||||
single-threaded in nature, like database handles which traditionally can only
|
||||
execute one outstanding query at a time and use internal synchronization to
|
||||
ensure this. A common pattern is to create a router for N actors, each of which
|
||||
wraps a single DB connection and handles queries as sent to the router. The
|
||||
number N must then be tuned for maximum throughput, which will vary depending
|
||||
on which DBMS is deployed on what hardware.
|
||||
|
||||
.. note::
|
||||
|
||||
Configuring thread pools is a task best delegated to Akka, simply configure
|
||||
in the ``application.conf`` and instantiate through an :class:`ActorSystem`
|
||||
[:ref:`Java <dispatcher-lookup-java>`, :ref:`Scala
|
||||
<dispatcher-lookup-scala>`]
|
||||
|
||||
What you should not concern yourself with
|
||||
-----------------------------------------
|
||||
|
||||
|
|
|
|||
|
|
@ -5,10 +5,6 @@ package docs.dispatcher;
|
|||
|
||||
//#imports
|
||||
import akka.actor.*;
|
||||
import akka.actor.ActorRef;
|
||||
import akka.actor.Props;
|
||||
import akka.actor.UntypedActor;
|
||||
import akka.actor.UntypedActorFactory;
|
||||
//#imports
|
||||
|
||||
//#imports-prio
|
||||
|
|
@ -37,6 +33,7 @@ import org.junit.After;
|
|||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import scala.Option;
|
||||
import scala.concurrent.ExecutionContext;
|
||||
|
||||
import com.typesafe.config.ConfigFactory;
|
||||
|
||||
|
|
@ -75,6 +72,14 @@ public class DispatcherDocTestBase {
|
|||
.withDispatcher("my-pinned-dispatcher"));
|
||||
//#defining-pinned-dispatcher
|
||||
}
|
||||
|
||||
public void compileLookup() {
|
||||
//#lookup
|
||||
// this is scala.concurrent.ExecutionContext
|
||||
// for use with Futures, Scheduler, etc.
|
||||
final ExecutionContext ex = system.dispatchers().lookup("my-dispatcher");
|
||||
//#lookup
|
||||
}
|
||||
|
||||
@Test
|
||||
public void priorityDispatcher() throws Exception {
|
||||
|
|
|
|||
|
|
@ -13,6 +13,15 @@ Default dispatcher
|
|||
Every ``ActorSystem`` will have a default dispatcher that will be used in case nothing else is configured for an ``Actor``.
|
||||
The default dispatcher can be configured, and is by default a ``Dispatcher`` with a "fork-join-executor", which gives excellent performance in most cases.
|
||||
|
||||
.. _dispatcher-lookup-java:
|
||||
|
||||
Looking up a Dispatcher
|
||||
-----------------------
|
||||
|
||||
Dispatchers implement the :class:`ExecutionContext` interface and can thus be used to run :class:`Future` invocations etc.
|
||||
|
||||
.. includecode:: code/docs/dispatcher/DispatcherDocTestBase.java#lookup
|
||||
|
||||
Setting the dispatcher for an Actor
|
||||
-----------------------------------
|
||||
|
||||
|
|
|
|||
|
|
@ -186,6 +186,13 @@ class DispatcherDocSpec extends AkkaSpec(DispatcherDocSpec.config) {
|
|||
//#defining-pinned-dispatcher
|
||||
}
|
||||
|
||||
"looking up a dispatcher" in {
|
||||
//#lookup
|
||||
// for use with Futures, Scheduler, etc.
|
||||
implicit val executionContext = system.dispatchers.lookup("my-dispatcher")
|
||||
//#lookup
|
||||
}
|
||||
|
||||
"defining priority dispatcher" in {
|
||||
//#prio-dispatcher
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,15 @@ Default dispatcher
|
|||
Every ``ActorSystem`` will have a default dispatcher that will be used in case nothing else is configured for an ``Actor``.
|
||||
The default dispatcher can be configured, and is by default a ``Dispatcher`` with a "fork-join-executor", which gives excellent performance in most cases.
|
||||
|
||||
.. _dispatcher-lookup-scala:
|
||||
|
||||
Looking up a Dispatcher
|
||||
-----------------------
|
||||
|
||||
Dispatchers implement the :class:`ExecutionContext` interface and can thus be used to run :class:`Future` invocations etc.
|
||||
|
||||
.. includecode:: code/docs/dispatcher/DispatcherDocSpec.scala#lookup
|
||||
|
||||
Setting the dispatcher for an Actor
|
||||
-----------------------------------
|
||||
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ class CreationActor extends Actor {
|
|||
case result: MathResult ⇒ result match {
|
||||
case MultiplicationResult(n1, n2, r) ⇒
|
||||
println("Mul result: %d * %d = %d".format(n1, n2, r))
|
||||
case DivisionResult(n1, n2, r) ⇒
|
||||
case DivisionResult(n1, n2, r) ⇒
|
||||
println("Div result: %.0f / %d = %.2f".format(n1, n2, r))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ class LookupActor extends Actor {
|
|||
def receive = {
|
||||
case (actor: ActorRef, op: MathOp) ⇒ actor ! op
|
||||
case result: MathResult ⇒ result match {
|
||||
case AddResult(n1, n2, r) ⇒
|
||||
case AddResult(n1, n2, r) ⇒
|
||||
println("Add result: %d + %d = %d".format(n1, n2, r))
|
||||
case SubtractResult(n1, n2, r) ⇒
|
||||
println("Sub result: %d - %d = %d".format(n1, n2, r))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue