Merge pull request #164 from jboner/wip-1169-actor-system-doc-rk

received okay by mail
This commit is contained in:
Roland Kuhn 2011-12-15 07:20:15 -08:00
commit 06a13d3eb1
7 changed files with 380 additions and 38 deletions

View file

@ -0,0 +1,114 @@
.. _actor-systems:
Actor Systems
=============
Actors are objects which encapsulate state and behavior, they communicate
exclusively by exchanging messages which are placed into the recipients
mailbox. In a sense, actors are the most strigent form of object-oriented
programming, but it serves better to view them as persons: while modeling a
solution with actors, envision a group of people and assign sub-tasks to them,
arrange their functions into an organizational structure and think about how to
escalate failure (all with the benefit of not actually dealing with people,
which means that we need not concern ourselves with their emotional state or
moral issues). The result can then serve as a mental scaffolding for building
the software implementation.
Hierarchical Structure
----------------------
Like in an economic organization, actors naturally form hierarchies. One actor,
which is to oversee a certain function in the program might want to split up
its task into smaller, more manageable pieces. For this purpose it starts child
actors which it supervises. While the details of supervision are explained
:ref:`here <supervision>`, we shall concentrate on the underlying concepts in
this section. The only prerequisite is to know that each actor has exactly one
supervisor, which is the actor which created it.
The quintessential feature of actor systems is that tasks are split up and
delegated until they become small enough to be handled in one piece. In doing
so, not only is the task itself clearly structured, but the resulting actors
can be reasoned about in terms of which messages they should process, how they
should react nominally and how failure should be handled. If one actor does not
have the means for dealing with a certain situation, it sends a corresponding
failure message to its supervisor, asking for help. The recursive structure
then allows to handle failure at the right level.
Compare this to layered software design which easily devolves into defensive
programming with the aim of not leaking any failure out: if the problem is
communicated to the right person, a better solution can be found than if
trying to keep everything “under the carpet”.
Now, the difficulty in designing such a system is how to decide who should
supervise what. There is of course no single best solution, but there are a few
guide lines which might be helpful:
- If one actor manages the work another actor is doing, e.g. by passing on
sub-tasks, then the manager should supervise the child. The reason is that
the manager knows which kind of failures are expected and how to handle
them.
- If one actor carries very important data (i.e. its state shall not be lost
if avoidable), this actor should source out any possibly dangerous sub-tasks
to children it supervises and handle failures of these children as
appropriate. Depending on the nature of the requests, it may be best to
create a new child for each request, which simplifies state management for
collecting the replies. This is known as the “Error Kernel Pattern” from
Erlang.
- If one actor depends on another actor for carrying out its duty, it should
watch that other actors liveness and act upon receiving a termination
notice. This is different from supervision, as the watching party has no
influence on the supervision strategy, and it should be noted that a
functional dependency alone is not a criterion for deciding where to place a
certain child actor in the hierarchy.
There are of course always exceptions to these rules, but no matter whether you
follow the rules or break them, you should always have a reason.
Configuration Container
-----------------------
The actor system as a collaborating ensemble of actors is the natural unit for
managing shared facilities like scheduling services, configuration, logging,
etc. Several actor systems with different configuration may co-exist within the
same JVM without problems, there is no global shared state within Akka itself.
Couple this with the transparent communication between actor systems—within one
node or across a network connection—to see that actor systems themselves can be
used as building blocks in a functional hierarchy.
Actor Best Practices
--------------------
#. Actors should be like nice co-workers: do their job efficiently without
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.
#. Do not pass between actors mutable objects which you actually mutate. In
order to ensure that, prefer immutable messages. If the encapsulation of
actors is broken by exposing their mutable state to the outside, you are
back in normal Java concurrency land with all the drawbacks.
#. Actors are made to be containers for behavior and state, embracing this
means to not routinely send behavior within messages (which may be tempting
using Scala closures). One of the risks is to accidentally share mutable
state between actors, and this violation of the actor model unfortunately
breaks all the properties which make programming in actors such a nice
experience.
What you should not concern yourself with
-----------------------------------------
An actor system manages the resources it is configured to use in order to run
the actors which it contains. There may be millions of actors within one such
system, after all the mantra is to view them as abundant and they weigh in at
an overhead of only roughly 300 bytes per instance. Naturally, the exact order
in which messages are processed in large systems is not controllable by the
application author, but this is also not intended. Take a step back and relax
while Akka does the heavy lifting under the hood.

View file

@ -0,0 +1,142 @@
.. _actors-general:
What is an Actor?
=================
The previous section about :ref:`actor-systems` explained how actors form
hierarchies and are the smallest unit when building an application. This
section looks at one such actor in isolation, explaining the concepts you
encounter while implementing it. For more an in depth reference with all the
details please refer to :ref:`actors-scala` and :ref:`untyped-actors-java`.
An actor is a container for `State`_, `Behavior`_, a `Mailbox`_, `Children`_
and a `Fault Handling Strategy`_. All of this is encapsulated behind an `Actor
Reference`_. Finally, this happens `When and Actor Terminates`_.
Actor Reference
---------------
As detailed below, an actor object needs to be shielded from the outside in
order to benefit from the actor model. Therefore, actors are represented to the
outside using actor references, which are objects that can be passed around
freely and without restriction. This split into inner and outer object enables
transparency for all the desired operations: restarting an actor without
needing to update references elsewhere, placing the actual actor object on
remote hosts, sending messages to actors in completely different applications.
But the most important aspect is that it is not possible to look inside an
actor and get hold of its state from the outside, unless the actor unwisely
publishes this information itself.
State
-----
Actor objects will typically contain some variables which reflect possible
states the actor may be in. This can be an explicit state machine (e.g. using
the :ref:`fsm` module), or it could be a counter, set of listeners, pending
requests, etc. These data are what make an actor valuable, and they must be
protected from corruption by other actors. The good news is that Akka actors
conceptually each have their own light-weight thread, which is completely
shielded from the rest of the system. This means that instead of having to
synchronize access using locks you can just write your actor code without
worrying about concurrency at all.
Behind the scenes Akka will run sets of actors on sets of real threads, where
typically many actors share one thread, and subsequent invocations of one actor
may end up being processed on different threads. Akka ensures that this
implementation detail does not affect the single-threadedness of handling the
actors state.
Because the internal state is vital to an actors operations, having
inconsistent state is fatal. Thus, when the actor fails and is restarted by its
supervisor, the state will be created from scratch, like upon first creating
the actor. This is to enable the ability of self-healing of the system.
Behavior
--------
Every time a message is processed, it is matched against the current behavior
of the actor. Behavior means a function which defines the actions to be taken
in reaction to the message at that point in time, say forward a request if the
client is authorized, deny it otherwise. This behavior may change over time,
e.g. because different clients obtain authorization over time, or because the
actor may go into an “out-of-service” mode and later come back. These changes
are achieved by either encoding them in state variables which are read from the
behavior logic, or the function itself may be swapped out at runtime, see the
``become`` and ``unbecome`` operations. However, the initial behavior defined
during construction of the actor object is special in the sense that a restart
of the actor will reset its behavior to this initial one.
Mailbox
-------
An actors purpose is the processing of messages, and these messages were sent
to the actor from other actors (or from outside the actor system). The piece
which connects sender and receiver is the actors mailbox: each actor has
exactly one mailbox to which all senders enqueue their messages. Enqueuing
happens in the time-order of send operations, which means that messages sent
from different actors may not have a defined order at runtime due to the
apparent randomness of distributing actors across threads. Sending multiple
messages to the same target from the same actor, on the other hand, will
enqueue them in the same order.
There are different mailbox implementations to choose from, the default being a
FIFO: the order of the messages processed by the actor matches the order in
which they were enqueued. This is usually a good default, but applications may
need to prioritize some messages over others. In this case, a priority mailbox
will enqueue not always at the end but at a position as given by the message
priority, which might even be at the front. While using such a queue, the order
of messages processed will naturally be defined by the queues algorithm and in
general not be FIFO.
An important feature in which Akka differs from some other actor model
implementations is that the current behavior must always handle the next
dequeued message, there is no scanning the mailbox for the next matching one.
Failure to handle a message will typically be treated as a failure, unless this
behavior is overridden.
Children
--------
Each actor is potentially a supervisor: if it creates children for delegating
sub-tasks, it will automatically supervise them. The list of children is
maintained within the actors context and the actor has access to it.
Modifications to the list are done by creating (``context.actorOf(...)``) or
stopping (``context.stop(child)``) children and these actions are reflected
immediately. The actual creation and termination actions happen behind the
scenes in an asynchronous way, so they do not “block” their supervisor.
Fault Handling Strategy
-----------------------
The final piece of an actor is its strategy for handling faults of its
children. To keep it simple and robust, this is declared outside of the actors
code and has no access to the actors state. Fault handling is then done
transparently by Akka, applying one of the strategies described in
:ref:`supervision` for each incoming failure. As this strategy is fundamental
to how an actor system is structured, it cannot be changed once an actor has
been created.
Considering that there is only one such strategy for each actor, this means
that if different strategies apply to the various children of an actor, the
children should be grouped beneath intermediate supervisors with matching
strategies, preferring once more the structuring of actor systems according to
the splitting of tasks into sub-tasks.
When an Actor Terminates
------------------------
Once an actor terminates, i.e. fails in a way which is not handled by a
restart, stops itself or is stopped by its supervisor, it will free up its
resources, draining all remaining messages from its mailbox into the systems
“dead letter mailbox”. The mailbox is then replaced within the actor reference
with a that system mailbox, redirecting all new messages “into the drain”. This
is done on a best effort basis, though, so do not rely on it in order to
construct “guaranteed delivery”.
The reason for not just silently dumping the messages was inspired by our
tests: we register the TestEventListener on the event bus to which the dead
letters are forwarded, and that will log a warning for every dead letter
received—this has been very helpful for deciphering test failures more quickly.
It is conceivable that this feature may also be of use for other purposes.

View file

@ -2,9 +2,10 @@ Actor References, Paths and Addresses
=====================================
This chapter describes how actors are identified and located within a possibly
distributed actor system. It ties into the central idea that actor systems form
intrinsic supervision hierarchies as well as that communication between actors
is transparent with respect to their placement across multiple network nodes.
distributed actor system. It ties into the central idea that
:ref:`actor-systems` form intrinsic supervision hierarchies as well as that
communication between actors is transparent with respect to their placement
across multiple network nodes.
What is an Actor Reference?
---------------------------
@ -84,7 +85,7 @@ actors in the hierarchy from the root up. Examples are::
Here, ``akka`` is the default remote protocol for the 2.0 release, and others
are pluggable. The interpretation of the host & port part (i.e.
``serv.example.com:5678`` in the example) depends on the transport mechanism
used, but it should abide by the URI structural rules.
used, but it must abide by the URI structural rules.
Logical Actor Paths
^^^^^^^^^^^^^^^^^^^
@ -144,7 +145,7 @@ Creating Actors
An actor system is typically started by creating actors above the guardian
actor using the :meth:`ActorSystem.actorOf` method and then using
:meth:`ActorContext.actorOf` from within the created actors to spawn the actor
tree. These methods return a reference to the newly created actors. Each actor
tree. These methods return a reference to the newly created actor. Each actor
has direct access to references for its parent, itself and its children. These
references may be sent within messages to other actors, enabling those to reply
directly.
@ -159,10 +160,10 @@ attempting to observe its livelyhood will traverse the actor hierarchy of the
actor system from top to bottom by passing messages from parent to child until
either the target is reached or failure is certain, i.e. a name in the path
does not exist (in practice this process will be optimized using caches, but it
still has added cost compared to using the physical actor path, can for example
to obtained from the sender reference included in replies from that actor). The
messages passed are handled automatically by Akka, so this process is not
visible to client code.
still has added cost compared to using the physical actor path, which can for
example to obtained from the sender reference included in replies from that
actor). The messages passed are handled automatically by Akka, so this process
is not visible to client code.
Absolute vs. Relative Paths
```````````````````````````
@ -177,12 +178,20 @@ example send a message to a specific sibling::
context.actorFor("../brother") ! msg
Absolute paths may of course also be looked up on `context` in the usual way, i.e.
.. code-block:: scala
context.actorFor("/user/serviceA") ! msg
will work as expected.
Querying the Logical Actor Hierarchy
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Since the actor system forms a file-system like hierarchy, matching on paths is
possible in the same was as supported by Unix shells: you may replace (parts
of) path element names with wildcards (`"*"` and `"?"`) to formulate a
of) path element names with wildcards (`«*»` and `«?»`) to formulate a
selection which may match zero or more actual actors. Because the result is not
a single actor reference, it has a different type :class:`ActorSelection` and
does not support the full set of operations an :class:`ActorRef` does.
@ -261,8 +270,13 @@ other actors are found. The next level consists of the following:
those which are used in the implementation of :meth:`ActorRef.ask`.
- ``"/remote"`` is an artificial path below which all actors reside whose
supervisors are remote actor references
Future extensions:
- ``"/service"`` is an artificial path below which actors can be presented by
means of configuration, i.e. deployed at system start-up or just-in-time
(triggered by look-up) or “mounting” other actors by path—local or remote—to
give them logical names.
(triggered by look-up)
- ``"/alias"`` is an artificial path below which other actors may be “mounted”
(as in the Unix file-system sense) by path—local or remote—to give them
logical names.

View file

@ -4,8 +4,11 @@ General
.. toctree::
:maxdepth: 2
actor-systems
actors
supervision
addressing
remoting
jmm
message-send-semantics
configuration
addressing
supervision

View file

@ -0,0 +1,64 @@
.. _remoting:
Transparent Remoting
====================
The previous section describes how actor paths are used to make possible
transparent remoting. This special feature deserves some extra explanation,
because it is a term which was used quite differently in the context of
programming languages, platforms and technologies.
Distributed by Default
----------------------
Everything in Akka is designed to work in a distributed setting: all
interactions of actors use purely message passing and everything is
asynchronous. This effort has been undertaken to ensure that all functions are
available equally when running within a single JVM or on a cluster of hundreds
of machines. The key for enabling this is to go from remote to local by way of
optimization instead of trying to go from local to remote by way of
generalization. See `this classic paper
<http://labs.oracle.com/techrep/1994/abstract-29.html>`_ for a detailed
discussion on why the second approach is bound to fail.
Ways in which Transparency is Broken
------------------------------------
What is true of Akka need not be true of the application which uses it, since
designing for distributed execution poses some restrictions on what is
possible. The most obvious one is that all messages sent over the wire must be
serializable. While being a little less obvious this includes closures which
are used as actor factories (i.e. within :class:`Props`) if the actor is to be
created on a remote node.
Another consequence is that everything needs to be aware of all interactions
being fully asynchronous, which in a computer network might mean that it may
take several minutes for a message to reach its recipient (depending on
configuration). It also means that the probability for a message to be lost is
much higher than within one JVM, where it is close to zero (still: no hard
guarantee!).
How is Remoting Used?
---------------------
We took the idea of transparency to the limit in that there is no API for the
remoting layer of Akka: it is purely driven by configuration. Just write your
application according to the principles outlined in the previous sections, then
specify remote deployment of actor sub-trees in the configuration file. This
way, your application can be scaled out without having to touch the code.
Marking Points for Scaling Up with Routers
------------------------------------------
In addition to being able to run different parts of an actor system on
different nodes of a cluster, it is also possible to scale up onto more cores
by multiplying actor sub-trees which support parallelization (think for example
a search engine processing different queries in parallel). The clones can then
be routed to in different fashions, e.g. round-robin. The only thing necessary
to achieve this is that the developer needs to declare a certain actor as
“withRouter”, the in its stead a router actor will be created which will spawn
up a configurable number of children of the desired type and route to them in
the configured fashion. Once such a router has been declared, its configuration
can be freely overridden from the configuration file, including mixing it with
the transparent remote deployment of (some of) the children. Read more about
this in :ref:`routing`.

View file

@ -1,5 +1,7 @@
Supervision
===========
.. _supervision:
Supervision and Monitoring
==========================
This chapter outlines the concept behind supervision, the primitives offered
and their semantics. For details on how that translates into real code, please
@ -8,12 +10,13 @@ refer to the corresponding chapters for Scala and Java APIs.
What Supervision Means
----------------------
Supervision describes a dependency relationship between actors: the supervisor
delegates tasks to subordinates and therefore must respond to their failures.
When a subordinate detects a failure (i.e. throws an exception), it suspends
itself and all its subordinates and sends a message to its supervisor,
signaling failure. Depending on the nature of the work to be supervised and
the nature of the failure, the supervisor has four basic choices:
As described in :ref:`actor-systems` supervision describes a dependency
relationship between actors: the supervisor delegates tasks to subordinates and
therefore must respond to their failures. When a subordinate detects a failure
(i.e. throws an exception), it suspends itself and all its subordinates and
sends a message to its supervisor, signaling failure. Depending on the nature
of the work to be supervised and the nature of the failure, the supervisor has
four basic choices:
#. Resume the subordinate, keeping its accumulated internal state
#. Restart the subordinate, clearing out its accumulated internal state
@ -27,7 +30,8 @@ three: resuming an actor resumes all its subordinates, restarting an actor
entails restarting all its subordinates, similarly stopping an actor will also
stop all its subordinates. It should be noted that the default behavior of an
actor is to stop all its children before restarting, but this can be overridden
using the :meth:`preRestart` hook.
using the :meth:`preRestart` hook; the recursive restart applies to all
children left after this hook has been executed.
Each supervisor is configured with a function translating all possible failure
causes (i.e. exceptions) into one of the four choices given above; notably,
@ -46,7 +50,7 @@ makes the formation of actor supervision hierarchies explicit and encourages
sound design decisions. It should be noted that this also guarantees that
actors cannot be orphaned or attached to supervisors from the outside, which
might otherwise catch them unawares. In addition, this yields a natural and
clean shutdown procedure for (parts of) actor applications.
clean shutdown procedure for (sub-trees of) actor applications.
What Restarting Means
---------------------
@ -90,12 +94,11 @@ it may react to the other actors termination, in contrast to supervision whic
reacts to failure.
Lifecycle monitoring is implemented using a :class:`Terminated` message to be
received by the behavior of the monitoring actor, where the default behavior is
to throw a special :class:`DeathPactException` if not otherwise handled. One
important property is that the message will be delivered irrespective of the
order in which the monitoring request and targets termination occur, i.e. you
still get the message even if at the time of registration the target is already
dead.
received by the monitoring actor, where the default behavior is to throw a
special :class:`DeathPactException` if not otherwise handled. One important
property is that the message will be delivered irrespective of the order in
which the monitoring request and targets termination occur, i.e. you still get
the message even if at the time of registration the target is already dead.
Monitoring is particularly useful if a supervisor cannot simply restart its
children and has to stop them, e.g. in case of errors during actor
@ -104,6 +107,6 @@ them or schedule itself to retry this at a later time.
Another common use case is that an actor needs to fail in the absence of an
external resource, which may also be one of its own children. If a third party
terminates a child by way of the ``stop()`` method or sending a
terminates a child by way of the ``system.stop(child)`` method or sending a
:class:`PoisonPill`, the supervisor might well be affected.

View file

@ -1,3 +1,5 @@
.. _fsm:
###
FSM
###