=per #15429 Rewrite persistence documentation and samples for 2.3.4 changes
(cherry picked from commit 02351e32f110a8c4a249f0f3f84bae5898d1a836) Conflicts: akka-samples/akka-sample-persistence-java-lambda/tutorial/index.html akka-samples/akka-sample-persistence-java/tutorial/index.html akka-samples/akka-sample-persistence-scala/build.sbt akka-samples/akka-sample-persistence-scala/src/main/scala/sample/persistence/ConversationRecoveryExample.scala akka-samples/akka-sample-persistence-scala/src/main/scala/sample/persistence/PersistentActorExample.scala akka-samples/akka-sample-persistence-scala/src/main/scala/sample/persistence/ProcessorChannelExample.scala akka-samples/akka-sample-persistence-scala/src/main/scala/sample/persistence/ProcessorChannelRemoteExample.scala akka-samples/akka-sample-persistence-scala/src/main/scala/sample/persistence/SnapshotExample.scala akka-samples/akka-sample-persistence-scala/src/main/scala/sample/persistence/StreamExample.scala akka-samples/akka-sample-persistence-scala/tutorial/index.html
This commit is contained in:
parent
062d304b73
commit
d6ffdf521c
35 changed files with 1091 additions and 2276 deletions
|
|
@ -86,7 +86,7 @@ public class PersistenceDocTest {
|
||||||
};
|
};
|
||||||
|
|
||||||
static Object o2 = new Object() {
|
static Object o2 = new Object() {
|
||||||
abstract class MyProcessor1 extends UntypedProcessor {
|
abstract class MyProcessor1 extends UntypedPersistentActor {
|
||||||
//#recover-on-start-disabled
|
//#recover-on-start-disabled
|
||||||
@Override
|
@Override
|
||||||
public void preStart() {}
|
public void preStart() {}
|
||||||
|
|
@ -98,7 +98,7 @@ public class PersistenceDocTest {
|
||||||
//#recover-on-restart-disabled
|
//#recover-on-restart-disabled
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class MyProcessor2 extends UntypedProcessor {
|
abstract class MyProcessor2 extends UntypedPersistentActor {
|
||||||
//#recover-on-start-custom
|
//#recover-on-start-custom
|
||||||
@Override
|
@Override
|
||||||
public void preStart() {
|
public void preStart() {
|
||||||
|
|
@ -107,7 +107,7 @@ public class PersistenceDocTest {
|
||||||
//#recover-on-start-custom
|
//#recover-on-start-custom
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class MyProcessor3 extends UntypedProcessor {
|
abstract class MyProcessor3 extends UntypedPersistentActor {
|
||||||
//#deletion
|
//#deletion
|
||||||
@Override
|
@Override
|
||||||
public void preRestart(Throwable reason, Option<Object> message) {
|
public void preRestart(Throwable reason, Option<Object> message) {
|
||||||
|
|
@ -119,7 +119,7 @@ public class PersistenceDocTest {
|
||||||
//#deletion
|
//#deletion
|
||||||
}
|
}
|
||||||
|
|
||||||
class MyProcessor4 extends UntypedProcessor implements ProcessorMethods {
|
class MyProcessor4 extends UntypedPersistentActor implements ProcessorMethods {
|
||||||
//#persistence-id-override
|
//#persistence-id-override
|
||||||
@Override
|
@Override
|
||||||
public String persistenceId() {
|
public String persistenceId() {
|
||||||
|
|
@ -127,15 +127,25 @@ public class PersistenceDocTest {
|
||||||
}
|
}
|
||||||
//#persistence-id-override
|
//#persistence-id-override
|
||||||
@Override
|
@Override
|
||||||
public void onReceive(Object message) throws Exception {}
|
public void onReceiveRecover(Object message) throws Exception {}
|
||||||
|
@Override
|
||||||
|
public void onReceiveCommand(Object message) throws Exception {}
|
||||||
}
|
}
|
||||||
|
|
||||||
class MyProcessor5 extends UntypedProcessor {
|
class MyProcessor5 extends UntypedPersistentActor {
|
||||||
//#recovery-completed
|
//#recovery-completed
|
||||||
public void onReceive(Object message) throws Exception {
|
|
||||||
|
@Override
|
||||||
|
public void onReceiveRecover(Object message) {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onReceiveCommand(Object message) throws Exception {
|
||||||
if (message instanceof RecoveryCompleted) {
|
if (message instanceof RecoveryCompleted) {
|
||||||
recoveryCompleted();
|
recoveryCompleted();
|
||||||
getContext().become(active);
|
} else if (message instanceof String) {
|
||||||
|
// ...
|
||||||
} else {
|
} else {
|
||||||
unhandled(message);
|
unhandled(message);
|
||||||
}
|
}
|
||||||
|
|
@ -146,17 +156,6 @@ public class PersistenceDocTest {
|
||||||
// ...
|
// ...
|
||||||
}
|
}
|
||||||
|
|
||||||
Procedure<Object> active = new Procedure<Object>() {
|
|
||||||
@Override
|
|
||||||
public void apply(Object message) {
|
|
||||||
if (message instanceof Persistent) {
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
unhandled(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
//#recovery-completed
|
//#recovery-completed
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ persistence is that only changes to an actor's internal state are persisted but
|
||||||
allows for very high transaction rates and efficient replication. Stateful actors are recovered by replaying stored
|
allows for very high transaction rates and efficient replication. Stateful actors are recovered by replaying stored
|
||||||
changes to these actors from which they can rebuild internal state. This can be either the full history of changes
|
changes to these actors from which they can rebuild internal state. This can be either the full history of changes
|
||||||
or starting from a snapshot which can dramatically reduce recovery times. Akka persistence also provides point-to-point
|
or starting from a snapshot which can dramatically reduce recovery times. Akka persistence also provides point-to-point
|
||||||
communication channels with at-least-once message delivery semantics.
|
communication with at-least-once message delivery semantics.
|
||||||
|
|
||||||
.. warning::
|
.. warning::
|
||||||
|
|
||||||
|
|
@ -40,29 +40,28 @@ Akka persistence is a separate jar file. Make sure that you have the following d
|
||||||
Architecture
|
Architecture
|
||||||
============
|
============
|
||||||
|
|
||||||
* *Processor* (deprecated, use *PersistentActor* instead): A processor is a persistent, stateful actor. Messages sent
|
* *AbstractPersistentActor*: Is a persistent, stateful actor. It is able to persist events to a journal and can react to
|
||||||
to a processor are written to a journal before its ``onReceive`` method is called. When a processor is started or
|
|
||||||
restarted, journaled messages are replayed to that processor, so that it can recover internal state from these messages.
|
|
||||||
|
|
||||||
* *PersistentActor*: Is a persistent, stateful actor. It is able to persist events to a journal and can react to
|
|
||||||
them in a thread-safe manner. It can be used to implement both *command* as well as *event sourced* actors.
|
them in a thread-safe manner. It can be used to implement both *command* as well as *event sourced* actors.
|
||||||
When a persistent actor is started or restarted, journaled messages are replayed to that actor, so that it can
|
When a persistent actor is started or restarted, journaled messages are replayed to that actor, so that it can
|
||||||
recover internal state from these messages.
|
recover internal state from these messages.
|
||||||
|
|
||||||
* *View*: A view is a persistent, stateful actor that receives journaled messages that have been written by another
|
* *View*: A view is a persistent, stateful actor that receives journaled messages that have been written by another
|
||||||
processor. A view itself does not journal new messages, instead, it updates internal state only from a processor's
|
persistent actor. A view itself does not journal new messages, instead, it updates internal state only from a persistent actor's
|
||||||
replicated message stream.
|
replicated message stream.
|
||||||
|
|
||||||
|
* *Streams*: Messages written by a persistent actor can be published in compliance with the `Reactive Streams`_ specification.
|
||||||
|
Only those messages that are explicitly requested from downstream persistent actors are actually pulled from a persistent actor's
|
||||||
|
journal.
|
||||||
|
|
||||||
* *Channel*: Channels are used by processors and views to communicate with other actors. They prevent that replayed
|
* *AbstractPersistentActorAtLeastOnceDelivery*: To send messages with at-least-once delivery semantics to destinations, also in
|
||||||
messages are redundantly delivered to these actors and provide at-least-once message delivery semantics, also in
|
|
||||||
case of sender and receiver JVM crashes.
|
case of sender and receiver JVM crashes.
|
||||||
|
|
||||||
* *Journal*: A journal stores the sequence of messages sent to a processor. An application can control which messages
|
* *Journal*: A journal stores the sequence of messages sent to a persistent actor. An application can control which messages
|
||||||
are journaled and which are received by the processor without being journaled. The storage backend of a journal is
|
are journaled and which are received by the persistent actor without being journaled. The storage backend of a journal is
|
||||||
pluggable. The default journal storage plugin writes to the local filesystem, replicated journals are available as
|
pluggable. The default journal storage plugin writes to the local filesystem, replicated journals are available as
|
||||||
`Community plugins`_.
|
`Community plugins`_.
|
||||||
|
|
||||||
* *Snapshot store*: A snapshot store persists snapshots of a processor's or a view's internal state. Snapshots are
|
* *Snapshot store*: A snapshot store persists snapshots of a persistent actor's or a view's internal state. Snapshots are
|
||||||
used for optimizing recovery times. The storage backend of a snapshot store is pluggable. The default snapshot
|
used for optimizing recovery times. The storage backend of a snapshot store is pluggable. The default snapshot
|
||||||
storage plugin writes to the local filesystem.
|
storage plugin writes to the local filesystem.
|
||||||
|
|
||||||
|
|
@ -70,34 +69,87 @@ Architecture
|
||||||
development of event sourced applications (see section :ref:`event-sourcing-java-lambda`)
|
development of event sourced applications (see section :ref:`event-sourcing-java-lambda`)
|
||||||
|
|
||||||
.. _Community plugins: https://gist.github.com/krasserm/8612920#file-akka-persistence-plugins-md
|
.. _Community plugins: https://gist.github.com/krasserm/8612920#file-akka-persistence-plugins-md
|
||||||
|
.. _Reactive Streams: http://www.reactive-streams.org/
|
||||||
|
|
||||||
.. _processors-lambda-java:
|
.. _event-sourcing-java-lambda:
|
||||||
|
|
||||||
Processors
|
Event sourcing
|
||||||
==========
|
==============
|
||||||
|
|
||||||
A processor can be implemented by extending ``AbstractProcessor`` class and setting
|
The basic idea behind `Event Sourcing`_ is quite simple. A persistent actor receives a (non-persistent) command
|
||||||
the “initial behavior” in the constructor by calling the :meth:`receive` method
|
which is first validated if it can be applied to the current state. Here, validation can mean anything, from simple
|
||||||
|
inspection of a command message's fields up to a conversation with several external services, for example.
|
||||||
|
If validation succeeds, events are generated from the command, representing the effect of the command. These events
|
||||||
|
are then persisted and, after successful persistence, used to change the actor's state. When the persistent actor
|
||||||
|
needs to be recovered, only the persisted events are replayed of which we know that they can be successfully applied.
|
||||||
|
In other words, events cannot fail when being replayed to a persistent actor, in contrast to commands. Event sourced
|
||||||
|
actors may of course also process commands that do not change application state, such as query commands, for example.
|
||||||
|
|
||||||
.. includecode:: ../../../akka-samples/akka-sample-persistence-java-lambda/src/main/java/doc/LambdaPersistenceDocTest.java#definition
|
.. _Event Sourcing: http://martinfowler.com/eaaDev/EventSourcing.html
|
||||||
|
|
||||||
Processors only write messages of type ``Persistent`` to the journal, others are received without being persisted.
|
Akka persistence supports event sourcing with the ``AbstractPersistentActor`` abstract class. An actor that extends this
|
||||||
When a processor's behavior is called with a ``Persistent`` message it can safely assume that this message
|
class uses the ``persist`` method to persist and handle events. The behavior of an ``AbstractPersistentActor``
|
||||||
has been successfully written to the journal. If a journal fails to write a ``Persistent`` message then the processor
|
is defined by implementing ``receiveRecover`` and ``receiveCommand``. This is demonstrated in the following example.
|
||||||
is stopped, by default. If a processor should continue running on persistence failures it must handle
|
|
||||||
``PersistenceFailure`` messages. In this case, a processor may want to inform the sender about the failure,
|
|
||||||
so that the sender can re-send the message, if needed.
|
|
||||||
|
|
||||||
An ``AbstractProcessor`` itself is an ``Actor`` and can therefore be instantiated with ``actorOf``.
|
.. includecode:: ../../../akka-samples/akka-sample-persistence-java-lambda/src/main/java/sample/persistence/PersistentActorExample.java#persistent-actor-example
|
||||||
|
|
||||||
.. includecode:: ../../../akka-samples/akka-sample-persistence-java-lambda/src/main/java/doc/LambdaPersistenceDocTest.java#usage
|
The example defines two data types, ``Cmd`` and ``Evt`` to represent commands and events, respectively. The
|
||||||
|
``state`` of the ``ExampleProcessor`` is a list of persisted event data contained in ``ExampleState``.
|
||||||
|
|
||||||
|
The persistent actor's ``receiveRecover`` method defines how ``state`` is updated during recovery by handling ``Evt``
|
||||||
|
and ``SnapshotOffer`` messages. The persistent actor's ``receiveCommand`` method is a command handler. In this example,
|
||||||
|
a command is handled by generating two events which are then persisted and handled. Events are persisted by calling
|
||||||
|
``persist`` with an event (or a sequence of events) as first argument and an event handler as second argument.
|
||||||
|
|
||||||
|
The ``persist`` method persists events asynchronously and the event handler is executed for successfully persisted
|
||||||
|
events. Successfully persisted events are internally sent back to the persistent actor as individual messages that trigger
|
||||||
|
event handler executions. An event handler may close over persistent actor state and mutate it. The sender of a persisted
|
||||||
|
event is the sender of the corresponding command. This allows event handlers to reply to the sender of a command
|
||||||
|
(not shown).
|
||||||
|
|
||||||
|
The main responsibility of an event handler is changing persistent actor state using event data and notifying others
|
||||||
|
about successful state changes by publishing events.
|
||||||
|
|
||||||
|
When persisting events with ``persist`` it is guaranteed that the persistent actor will not receive further commands between
|
||||||
|
the ``persist`` call and the execution(s) of the associated event handler. This also holds for multiple ``persist``
|
||||||
|
calls in context of a single command.
|
||||||
|
|
||||||
|
The easiest way to run this example yourself is to download `Typesafe Activator <http://www.typesafe.com/platform/getstarted>`_
|
||||||
|
and open the tutorial named `Akka Persistence Samples in Java with Lambdas <http://www.typesafe.com/activator/template/akka-sample-persistence-java-lambda>`_.
|
||||||
|
It contains instructions on how to run the ``PersistentActorExample``.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
It's also possible to switch between different command handlers during normal processing and recovery
|
||||||
|
with ``context().become()`` and ``context().unbecome()``. To get the actor into the same state after
|
||||||
|
recovery you need to take special care to perform the same state transitions with ``become`` and
|
||||||
|
``unbecome`` in the ``receiveRecover`` method as you would have done in the command handler.
|
||||||
|
|
||||||
|
Identifiers
|
||||||
|
-----------
|
||||||
|
|
||||||
|
A persistent actor must have an identifier that doesn't change across different actor incarnations. It defaults to the
|
||||||
|
``String`` representation of persistent actor's path without the address part and can be obtained via the ``persistenceId``
|
||||||
|
method.
|
||||||
|
|
||||||
|
.. includecode:: ../../../akka-samples/akka-sample-persistence-java-lambda/src/main/java/doc/LambdaPersistenceDocTest.java#persistence-id
|
||||||
|
|
||||||
|
Applications can customize a persistent actor's id by specifying an actor name during persistent actor creation as shown in
|
||||||
|
section :ref:`event-sourcing-java-lambda`. This changes that persistent actor's name in its actor hierarchy and hence influences only
|
||||||
|
part of the persistent actor id. To fully customize a persistent actor's id, the ``persistenceId`` method must be overridden.
|
||||||
|
|
||||||
|
.. includecode:: ../../../akka-samples/akka-sample-persistence-java-lambda/src/main/java/doc/LambdaPersistenceDocTest.java#persistence-id-override
|
||||||
|
|
||||||
|
Overriding ``persistenceId`` is the recommended way to generate stable identifiers.
|
||||||
|
|
||||||
|
.. _recovery-java-lambda:
|
||||||
|
|
||||||
Recovery
|
Recovery
|
||||||
--------
|
--------
|
||||||
|
|
||||||
By default, a processor is automatically recovered on start and on restart by replaying journaled messages.
|
By default, a persistent actor is automatically recovered on start and on restart by replaying journaled messages.
|
||||||
New messages sent to a processor during recovery do not interfere with replayed messages. New messages will
|
New messages sent to a persistent actor during recovery do not interfere with replayed messages. New messages will
|
||||||
only be received by a processor after recovery completes.
|
only be received by a persistent actor after recovery completes.
|
||||||
|
|
||||||
Recovery customization
|
Recovery customization
|
||||||
^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
@ -106,7 +158,7 @@ Automated recovery on start can be disabled by overriding ``preStart`` with an e
|
||||||
|
|
||||||
.. includecode:: ../../../akka-samples/akka-sample-persistence-java-lambda/src/main/java/doc/LambdaPersistenceDocTest.java#recover-on-start-disabled
|
.. includecode:: ../../../akka-samples/akka-sample-persistence-java-lambda/src/main/java/doc/LambdaPersistenceDocTest.java#recover-on-start-disabled
|
||||||
|
|
||||||
In this case, a processor must be recovered explicitly by sending it a ``Recover`` message.
|
In this case, a persistent actor must be recovered explicitly by sending it a ``Recover`` message.
|
||||||
|
|
||||||
.. includecode:: ../../../akka-samples/akka-sample-persistence-java-lambda/src/main/java/doc/LambdaPersistenceDocTest.java#recover-explicit
|
.. includecode:: ../../../akka-samples/akka-sample-persistence-java-lambda/src/main/java/doc/LambdaPersistenceDocTest.java#recover-explicit
|
||||||
|
|
||||||
|
|
@ -115,7 +167,7 @@ If not overridden, ``preStart`` sends a ``Recover`` message to ``self()``. Appli
|
||||||
|
|
||||||
.. includecode:: ../../../akka-samples/akka-sample-persistence-java-lambda/src/main/java/doc/LambdaPersistenceDocTest.java#recover-on-start-custom
|
.. includecode:: ../../../akka-samples/akka-sample-persistence-java-lambda/src/main/java/doc/LambdaPersistenceDocTest.java#recover-on-start-custom
|
||||||
|
|
||||||
Upper sequence number bounds can be used to recover a processor to past state instead of current state. Automated
|
Upper sequence number bounds can be used to recover a persistent actor to past state instead of current state. Automated
|
||||||
recovery on restart can be disabled by overriding ``preRestart`` with an empty implementation.
|
recovery on restart can be disabled by overriding ``preRestart`` with an empty implementation.
|
||||||
|
|
||||||
.. includecode:: ../../../akka-samples/akka-sample-persistence-java-lambda/src/main/java/doc/LambdaPersistenceDocTest.java#recover-on-restart-disabled
|
.. includecode:: ../../../akka-samples/akka-sample-persistence-java-lambda/src/main/java/doc/LambdaPersistenceDocTest.java#recover-on-restart-disabled
|
||||||
|
|
@ -123,56 +175,90 @@ recovery on restart can be disabled by overriding ``preRestart`` with an empty i
|
||||||
Recovery status
|
Recovery status
|
||||||
^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
A processor can query its own recovery status via the methods
|
A persistent actor can query its own recovery status via the methods
|
||||||
|
|
||||||
.. includecode:: ../../../akka-samples/akka-sample-persistence-java-lambda/src/main/java/doc/LambdaPersistenceDocTest.java#recovery-status
|
.. includecode:: ../../../akka-samples/akka-sample-persistence-java-lambda/src/main/java/doc/LambdaPersistenceDocTest.java#recovery-status
|
||||||
|
|
||||||
Sometimes there is a need for performing additional initialization when the
|
Sometimes there is a need for performing additional initialization when the
|
||||||
recovery has completed, before processing any other message sent to the processor.
|
recovery has completed, before processing any other message sent to the persistent actor.
|
||||||
The processor will receive a special :class:`RecoveryCompleted` message right after recovery
|
The persistent actor will receive a special :class:`RecoveryCompleted` message right after recovery
|
||||||
and before any other received messages. If there is a problem with recovering the state of
|
and before any other received messages.
|
||||||
the actor from the journal, the actor will be sent a :class:`RecoveryFailure` message that
|
|
||||||
it can choose to handle. If the actor doesn't handle the :class:`RecoveryFailure` message it
|
If there is a problem with recovering the state of the actor from the journal, the actor will be
|
||||||
will be stopped.
|
sent a :class:`RecoveryFailure` message that it can choose to handle in ``receiveRecover``. If the
|
||||||
|
actor doesn't handle the :class:`RecoveryFailure` message it will be stopped.
|
||||||
|
|
||||||
.. includecode:: ../../../akka-samples/akka-sample-persistence-java-lambda/src/main/java/doc/LambdaPersistenceDocTest.java#recovery-completed
|
.. includecode:: ../../../akka-samples/akka-sample-persistence-java-lambda/src/main/java/doc/LambdaPersistenceDocTest.java#recovery-completed
|
||||||
|
|
||||||
.. _failure-handling-java-lambda:
|
Relaxed local consistency requirements and high throughput use-cases
|
||||||
|
--------------------------------------------------------------------
|
||||||
|
|
||||||
Failure handling
|
If faced with relaxed local consistency requirements and high throughput demands sometimes ``PersistentActor`` and it's
|
||||||
^^^^^^^^^^^^^^^^
|
``persist`` may not be enough in terms of consuming incoming Commands at a high rate, because it has to wait until all
|
||||||
|
Events related to a given Command are processed in order to start processing the next Command. While this abstraction is
|
||||||
|
very useful for most cases, sometimes you may be faced with relaxed requirements about consistency – for example you may
|
||||||
|
want to process commands as fast as you can, assuming that Event will eventually be persisted and handled properly in
|
||||||
|
the background and retroactively reacting to persistence failures if needed.
|
||||||
|
|
||||||
A persistent message that caused an exception will be received again by a processor after restart. To prevent
|
The ``persistAsync`` method provides a tool for implementing high-throughput persistent actors. It will *not*
|
||||||
a replay of that message during recovery it can be deleted.
|
stash incoming Commands while the Journal is still working on persisting and/or user code is executing event callbacks.
|
||||||
|
|
||||||
.. includecode:: ../../../akka-samples/akka-sample-persistence-java-lambda/src/main/java/doc/LambdaPersistenceDocTest.java#deletion
|
In the below example, the event callbacks may be called "at any time", even after the next Command has been processed.
|
||||||
|
The ordering between events is still guaranteed ("evt-b-1" will be sent after "evt-a-2", which will be sent after "evt-a-1" etc.).
|
||||||
|
|
||||||
|
.. includecode:: ../../../akka-samples/akka-sample-persistence-java-lambda/src/main/java/doc/LambdaPersistenceDocTest.java#persist-async
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
In order to implement the pattern known as "*command sourcing*" simply ``persistAsync`` all incoming events right away,
|
||||||
|
and handle them in the callback.
|
||||||
|
|
||||||
|
.. _defer-java-lambda:
|
||||||
|
|
||||||
|
Deferring actions until preceeding persist handlers have executed
|
||||||
|
-----------------------------------------------------------------
|
||||||
|
|
||||||
|
Sometimes when working with ``persistAsync`` you may find that it would be nice to define some actions in terms of
|
||||||
|
''happens-after the previous ``persistAsync`` handlers have been invoked''. ``PersistentActor`` provides an utility method
|
||||||
|
called ``defer``, which works similarily to ``persistAsync`` yet does not persist the passed in event. It is recommended to
|
||||||
|
use it for *read* operations, and actions which do not have corresponding events in your domain model.
|
||||||
|
|
||||||
|
Using this method is very similar to the persist family of methods, yet it does **not** persist the passed in event.
|
||||||
|
It will be kept in memory and used when invoking the handler.
|
||||||
|
|
||||||
|
.. includecode:: ../../../akka-samples/akka-sample-persistence-java-lambda/src/main/java/doc/LambdaPersistenceDocTest.java#defer
|
||||||
|
|
||||||
|
Notice that the ``sender()`` is **safe** to access in the handler callback, and will be pointing to the original sender
|
||||||
|
of the command for which this ``defer`` handler was called.
|
||||||
|
|
||||||
|
.. includecode:: ../../../akka-samples/akka-sample-persistence-java-lambda/src/main/java/doc/LambdaPersistenceDocTest.java#defer-caller
|
||||||
|
|
||||||
|
Batch writes
|
||||||
|
------------
|
||||||
|
|
||||||
|
To optimize throughput, a persistent actor internally batches events to be stored under high load before
|
||||||
|
writing them to the journal (as a single batch). The batch size dynamically grows from 1 under low and moderate loads
|
||||||
|
to a configurable maximum size (default is ``200``) under high load. When using ``persistAsync`` this increases
|
||||||
|
the maximum throughput dramatically.
|
||||||
|
|
||||||
|
.. includecode:: ../scala/code/docs/persistence/PersistencePluginDocSpec.scala#max-message-batch-size
|
||||||
|
|
||||||
|
A new batch write is triggered by a persistent actor as soon as a batch reaches the maximum size or if the journal completed
|
||||||
|
writing the previous batch. Batch writes are never timer-based which keeps latencies at a minimum.
|
||||||
|
|
||||||
|
The batches are also used internally to ensure atomic writes of events. All events that are persisted in context
|
||||||
|
of a single command are written as a single batch to the journal (even if ``persist`` is called multiple times per command).
|
||||||
|
The recovery of an ``AbstractPersistentActor`` will therefore never be done partially (with only a subset of events persisted by a
|
||||||
|
single command).
|
||||||
|
|
||||||
Message deletion
|
Message deletion
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
A processor can delete a single message by calling the ``deleteMessage`` method with the sequence number of
|
A persistent actor can delete a single message by calling the ``deleteMessage`` method with the sequence number of
|
||||||
that message as argument. An optional ``permanent`` parameter specifies whether the message shall be permanently
|
that message as argument. An optional ``permanent`` parameter specifies whether the message shall be permanently
|
||||||
deleted from the journal or only marked as deleted. In both cases, the message won't be replayed. Later extensions
|
deleted from the journal or only marked as deleted. In both cases, the message won't be replayed. Later extensions
|
||||||
to Akka persistence will allow to replay messages that have been marked as deleted which can be useful for debugging
|
to Akka persistence will allow to replay messages that have been marked as deleted which can be useful for debugging
|
||||||
purposes, for example. To delete all messages (journaled by a single processor) up to a specified sequence number,
|
purposes, for example. To delete all messages (journaled by a single persistent actor) up to a specified sequence number,
|
||||||
processors should call the ``deleteMessages`` method.
|
persistent actors should call the ``deleteMessages`` method.
|
||||||
|
|
||||||
Identifiers
|
|
||||||
-----------
|
|
||||||
|
|
||||||
A processor must have an identifier that doesn't change across different actor incarnations. It defaults to the
|
|
||||||
``String`` representation of processor's path without the address part and can be obtained via the ``persistenceId``
|
|
||||||
method.
|
|
||||||
|
|
||||||
.. includecode:: ../../../akka-samples/akka-sample-persistence-java-lambda/src/main/java/doc/LambdaPersistenceDocTest.java#persistence-id
|
|
||||||
|
|
||||||
Applications can customize a processor's id by specifying an actor name during processor creation as shown in
|
|
||||||
section :ref:`processors-java`. This changes that processor's name in its actor hierarchy and hence influences only
|
|
||||||
part of the processor id. To fully customize a processor's id, the ``persistenceId`` method must be overridden.
|
|
||||||
|
|
||||||
.. includecode:: ../../../akka-samples/akka-sample-persistence-java-lambda/src/main/java/doc/LambdaPersistenceDocTest.java#persistence-id-override
|
|
||||||
|
|
||||||
Overriding ``persistenceId`` is the recommended way to generate stable identifiers.
|
|
||||||
|
|
||||||
.. _views-java-lambda:
|
.. _views-java-lambda:
|
||||||
|
|
||||||
|
|
@ -184,9 +270,9 @@ and setting the “initial behavior” in the constructor by calling the :meth:`
|
||||||
|
|
||||||
.. includecode:: ../../../akka-samples/akka-sample-persistence-java-lambda/src/main/java/doc/LambdaPersistenceDocTest.java#view
|
.. includecode:: ../../../akka-samples/akka-sample-persistence-java-lambda/src/main/java/doc/LambdaPersistenceDocTest.java#view
|
||||||
|
|
||||||
The ``persistenceId`` identifies the processor from which the view receives journaled messages. It is not necessary
|
The ``persistenceId`` identifies the persistent actor from which the view receives journaled messages. It is not necessary
|
||||||
the referenced processor is actually running. Views read messages from a processor's journal directly. When a
|
the referenced persistent actor is actually running. Views read messages from a persistent actor's journal directly. When a
|
||||||
processor is started later and begins to write new messages, the corresponding view is updated automatically, by
|
persistent actor is started later and begins to write new messages, the corresponding view is updated automatically, by
|
||||||
default.
|
default.
|
||||||
|
|
||||||
Updates
|
Updates
|
||||||
|
|
@ -219,9 +305,11 @@ of replayed messages for manual updates can be limited with the ``replayMax`` pa
|
||||||
Recovery
|
Recovery
|
||||||
--------
|
--------
|
||||||
|
|
||||||
Initial recovery of views works in the very same way as for :ref:`processors` (i.e. by sending a ``Recover`` message
|
Initial recovery of views works in the very same way as for a persistent actor (i.e. by sending a ``Recover`` message
|
||||||
to self). The maximum number of replayed messages during initial recovery is determined by ``autoUpdateReplayMax``.
|
to self). The maximum number of replayed messages during initial recovery is determined by ``autoUpdateReplayMax``.
|
||||||
Further possibilities to customize initial recovery are explained in section :ref:`processors-java`.
|
Further possibilities to customize initial recovery are explained in section :ref:`recovery-java-lambda`.
|
||||||
|
|
||||||
|
.. _persistence-identifiers-java-lambda:
|
||||||
|
|
||||||
Identifiers
|
Identifiers
|
||||||
-----------
|
-----------
|
||||||
|
|
@ -234,184 +322,38 @@ Applications can customize a view's id by specifying an actor name during view c
|
||||||
name in its actor hierarchy and hence influences only part of the view id. To fully customize a view's id, the
|
name in its actor hierarchy and hence influences only part of the view id. To fully customize a view's id, the
|
||||||
``viewId`` method must be overridden. Overriding ``viewId`` is the recommended way to generate stable identifiers.
|
``viewId`` method must be overridden. Overriding ``viewId`` is the recommended way to generate stable identifiers.
|
||||||
|
|
||||||
The ``viewId`` must differ from the referenced ``persistenceId``, unless :ref:`snapshots-java` of a view and its
|
The ``viewId`` must differ from the referenced ``persistenceId``, unless :ref:`snapshots-java-lambda` of a view and its
|
||||||
processor shall be shared (which is what applications usually do not want).
|
persistent actor shall be shared (which is what applications usually do not want).
|
||||||
|
|
||||||
.. _channels-java-lambda:
|
.. _streams-java-lambda:
|
||||||
|
|
||||||
Channels
|
Streams
|
||||||
========
|
=======
|
||||||
|
|
||||||
Channels are special actors that are used by processors or views to communicate with other actors (channel
|
Java API coming soon. See also Scala :ref:`streams` documentation.
|
||||||
destinations). The following discusses channels in context of processors but this is also applicable to views.
|
|
||||||
|
|
||||||
Channels prevent redundant delivery of replayed messages to destinations during processor recovery. A replayed
|
|
||||||
message is retained by a channel if its delivery has been confirmed by a destination.
|
|
||||||
|
|
||||||
.. includecode:: ../../../akka-samples/akka-sample-persistence-java-lambda/src/main/java/doc/LambdaPersistenceDocTest.java#channel-example
|
|
||||||
|
|
||||||
A channel is ready to use once it has been created, no recovery or further activation is needed. A ``Deliver``
|
|
||||||
request instructs a channel to send a ``Persistent`` message to a destination. A destination is provided as
|
|
||||||
``ActorPath`` and messages are sent by the channel via that path's ``ActorSelection``. Sender references are
|
|
||||||
preserved by a channel, therefore, a destination can reply to the sender of a ``Deliver`` request.
|
|
||||||
|
|
||||||
If a processor wants to reply to a ``Persistent`` message sender it should use the ``sender()`` path as
|
|
||||||
channel destination.
|
|
||||||
|
|
||||||
.. includecode:: ../../../akka-samples/akka-sample-persistence-java-lambda/src/main/java/doc/LambdaPersistenceDocTest.java#channel-example-reply
|
|
||||||
|
|
||||||
Persistent messages delivered by a channel are of type ``ConfirmablePersistent``. ``ConfirmablePersistent`` extends
|
|
||||||
``Persistent`` by adding the methods ``confirm`` and ``redeliveries`` (see also :ref:`redelivery-java-lambda`). A
|
|
||||||
channel destination confirms the delivery of a ``ConfirmablePersistent`` message by calling ``confirm()`` on that
|
|
||||||
message. This asynchronously writes a confirmation entry to the journal. Replayed messages internally contain
|
|
||||||
confirmation entries which allows a channel to decide if it should retain these messages or not.
|
|
||||||
|
|
||||||
A ``Processor`` can also be used as channel destination i.e. it can persist ``ConfirmablePersistent`` messages too.
|
|
||||||
|
|
||||||
.. _redelivery-java-lambda:
|
|
||||||
|
|
||||||
Message re-delivery
|
|
||||||
-------------------
|
|
||||||
|
|
||||||
Channels re-deliver messages to destinations if they do not confirm delivery within a configurable timeout.
|
|
||||||
This timeout can be specified as ``redeliverInterval`` when creating a channel, optionally together with the
|
|
||||||
maximum number of re-deliveries a channel should attempt for each unconfirmed message. The number of re-delivery
|
|
||||||
attempts can be obtained via the ``redeliveries`` method on ``ConfirmablePersistent``.
|
|
||||||
|
|
||||||
.. includecode:: ../../../akka-samples/akka-sample-persistence-java-lambda/src/main/java/doc/LambdaPersistenceDocTest.java#channel-custom-settings
|
|
||||||
|
|
||||||
A channel keeps messages in memory until their successful delivery has been confirmed or the maximum number of
|
|
||||||
re-deliveries is reached. To be notified about messages that have reached the maximum number of re-deliveries,
|
|
||||||
applications can register a listener at channel creation.
|
|
||||||
|
|
||||||
.. includecode:: ../../../akka-samples/akka-sample-persistence-java-lambda/src/main/java/doc/LambdaPersistenceDocTest.java#channel-custom-listener
|
|
||||||
|
|
||||||
A listener receives ``RedeliverFailure`` notifications containing all messages that could not be delivered. On
|
|
||||||
receiving a ``RedeliverFailure`` message, an application may decide to restart the sending processor to enforce
|
|
||||||
a re-send of these messages to the channel or confirm these messages to prevent further re-sends. The sending
|
|
||||||
processor can also be restarted any time later to re-send unconfirmed messages.
|
|
||||||
|
|
||||||
This combination of
|
|
||||||
|
|
||||||
* message persistence by sending processors
|
|
||||||
* message replays by sending processors
|
|
||||||
* message re-deliveries by channels and
|
|
||||||
* application-level confirmations (acknowledgements) by destinations
|
|
||||||
|
|
||||||
enables channels to provide at-least-once message delivery semantics. Possible duplicates can be detected by
|
|
||||||
destinations by tracking message sequence numbers. Message sequence numbers are generated per sending processor.
|
|
||||||
Depending on how a processor routes outbound messages to destinations, they may either see a contiguous message
|
|
||||||
sequence or a sequence with gaps.
|
|
||||||
|
|
||||||
.. warning::
|
|
||||||
|
|
||||||
If a processor emits more than one outbound message per inbound ``Persistent`` message it **must** use a
|
|
||||||
separate channel for each outbound message to ensure that confirmations are uniquely identifiable, otherwise,
|
|
||||||
at-least-once message delivery semantics do not apply. This rule has been introduced to avoid writing additional
|
|
||||||
outbound message identifiers to the journal which would decrease the overall throughput. It is furthermore
|
|
||||||
recommended to collapse multiple outbound messages to the same destination into a single outbound message,
|
|
||||||
otherwise, if sent via multiple channels, their ordering is not defined.
|
|
||||||
|
|
||||||
If an application wants to have more control how sequence numbers are assigned to messages it should use an
|
|
||||||
application-specific sequence number generator and include the generated sequence numbers into the ``payload``
|
|
||||||
of ``Persistent`` messages.
|
|
||||||
|
|
||||||
Persistent channels
|
|
||||||
-------------------
|
|
||||||
|
|
||||||
Channels created with ``Channel.props`` do not persist messages. These channels are usually used in combination
|
|
||||||
with a sending processor that takes care of persistence, hence, channel-specific persistence is not necessary in
|
|
||||||
this case. They are referred to as transient channels in the following.
|
|
||||||
|
|
||||||
Persistent channels are like transient channels but additionally persist messages before delivering them. Messages
|
|
||||||
that have been persisted by a persistent channel are deleted when destinations confirm their delivery. A persistent
|
|
||||||
channel can be created with ``PersistentChannel.props`` and configured with a ``PersistentChannelSettings`` object.
|
|
||||||
|
|
||||||
.. includecode:: ../../../akka-samples/akka-sample-persistence-java-lambda/src/main/java/doc/LambdaPersistenceDocTest.java#persistent-channel-example
|
|
||||||
|
|
||||||
A persistent channel is useful for delivery of messages to slow destinations or destinations that are unavailable
|
|
||||||
for a long time. It can constrain the number of pending confirmations based on the ``pendingConfirmationsMax``
|
|
||||||
and ``pendingConfirmationsMin`` parameters of ``PersistentChannelSettings``.
|
|
||||||
|
|
||||||
.. includecode:: ../../../akka-samples/akka-sample-persistence-java-lambda/src/main/java/doc/LambdaPersistenceDocTest.java#persistent-channel-watermarks
|
|
||||||
|
|
||||||
It suspends delivery when the number of pending confirmations reaches ``pendingConfirmationsMax`` and resumes
|
|
||||||
delivery again when this number falls below ``pendingConfirmationsMin``. This prevents both, flooding destinations
|
|
||||||
with more messages than they can process and unlimited memory consumption by the channel. A persistent channel
|
|
||||||
continues to persist new messages even when message delivery is temporarily suspended.
|
|
||||||
|
|
||||||
Standalone usage
|
|
||||||
----------------
|
|
||||||
|
|
||||||
Applications may also use channels standalone. Transient channels can be used standalone if re-delivery attempts
|
|
||||||
to destinations are required but message loss in case of a sender JVM crash is not an issue. If message loss in
|
|
||||||
case of a sender JVM crash is an issue, persistent channels should be used. In this case, applications may want to
|
|
||||||
receive replies from the channel whether messages have been successfully persisted or not. This can be enabled by
|
|
||||||
creating the channel with the ``replyPersistent`` configuration parameter set to ``true``:
|
|
||||||
|
|
||||||
.. includecode:: ../../../akka-samples/akka-sample-persistence-java-lambda/src/main/java/doc/LambdaPersistenceDocTest.java#persistent-channel-reply
|
|
||||||
|
|
||||||
With this setting, either the successfully persisted message is replied to the sender or a ``PersistenceFailure``
|
|
||||||
message. In case the latter case, the sender should re-send the message.
|
|
||||||
|
|
||||||
Identifiers
|
|
||||||
-----------
|
|
||||||
|
|
||||||
In the same way as :ref:`processors-java` and :ref:`views-java`, channels also have an identifier that defaults to a channel's
|
|
||||||
path. A channel identifier can therefore be customized by using a custom actor name at channel creation. This changes
|
|
||||||
that channel's name in its actor hierarchy and hence influences only part of the channel identifier. To fully customize
|
|
||||||
a channel identifier, it should be provided as argument ``Channel.props(String)`` or ``PersistentChannel.props(String)``
|
|
||||||
(recommended to generate stable identifiers).
|
|
||||||
|
|
||||||
.. includecode:: ../../../akka-samples/akka-sample-persistence-java-lambda/src/main/java/doc/LambdaPersistenceDocTest.java#channel-id-override
|
|
||||||
|
|
||||||
Persistent messages
|
|
||||||
===================
|
|
||||||
|
|
||||||
Payload
|
|
||||||
-------
|
|
||||||
|
|
||||||
The payload of a ``Persistent`` message can be obtained via its ``payload`` method. Inside processors, new messages
|
|
||||||
must be derived from the current persistent message before sending them via a channel, either by calling ``p.withPayload(...)``
|
|
||||||
or ``Persistent.create(..., getCurrentPersistentMessage())`` where ``getCurrentPersistentMessage()`` is defined on
|
|
||||||
``AbstractProcessor``.
|
|
||||||
|
|
||||||
.. includecode:: ../../../akka-samples/akka-sample-persistence-java-lambda/src/main/java/doc/LambdaPersistenceDocTest.java#current-message
|
|
||||||
|
|
||||||
This is necessary for delivery confirmations to work properly. Both
|
|
||||||
ways are equivalent but we recommend using ``p.withPayload(...)`` for clarity. It is not allowed to send a message
|
|
||||||
via a channel that has been created with ``Persistent.create(...)``. This would redeliver the message on every replay
|
|
||||||
even though its delivery was confirmed by a destination.
|
|
||||||
|
|
||||||
Sequence number
|
|
||||||
---------------
|
|
||||||
|
|
||||||
The sequence number of a ``Persistent`` message can be obtained via its ``sequenceNr`` method. Persistent
|
|
||||||
messages are assigned sequence numbers on a per-processor basis (or per channel basis if used
|
|
||||||
standalone). A sequence starts at ``1L`` and doesn't contain gaps unless a processor deletes messages.
|
|
||||||
|
|
||||||
.. _snapshots-java-lambda:
|
.. _snapshots-java-lambda:
|
||||||
|
|
||||||
Snapshots
|
Snapshots
|
||||||
=========
|
=========
|
||||||
|
|
||||||
Snapshots can dramatically reduce recovery times of processors and views. The following discusses snapshots
|
Snapshots can dramatically reduce recovery times of persistent actors and views. The following discusses snapshots
|
||||||
in context of processors but this is also applicable to views.
|
in context of persistent actors but this is also applicable to views.
|
||||||
|
|
||||||
Processors can save snapshots of internal state by calling the ``saveSnapshot`` method. If saving of a snapshot
|
Persistent actor can save snapshots of internal state by calling the ``saveSnapshot`` method. If saving of a snapshot
|
||||||
succeeds, the processor receives a ``SaveSnapshotSuccess`` message, otherwise a ``SaveSnapshotFailure`` message
|
succeeds, the persistent actor receives a ``SaveSnapshotSuccess`` message, otherwise a ``SaveSnapshotFailure`` message
|
||||||
|
|
||||||
.. includecode:: ../../../akka-samples/akka-sample-persistence-java-lambda/src/main/java/doc/LambdaPersistenceDocTest.java#save-snapshot
|
.. includecode:: ../../../akka-samples/akka-sample-persistence-java-lambda/src/main/java/doc/LambdaPersistenceDocTest.java#save-snapshot
|
||||||
|
|
||||||
During recovery, the processor is offered a previously saved snapshot via a ``SnapshotOffer`` message from
|
During recovery, the persistent actor is offered a previously saved snapshot via a ``SnapshotOffer`` message from
|
||||||
which it can initialize internal state.
|
which it can initialize internal state.
|
||||||
|
|
||||||
.. includecode:: ../../../akka-samples/akka-sample-persistence-java-lambda/src/main/java/doc/LambdaPersistenceDocTest.java#snapshot-offer
|
.. includecode:: ../../../akka-samples/akka-sample-persistence-java-lambda/src/main/java/doc/LambdaPersistenceDocTest.java#snapshot-offer
|
||||||
|
|
||||||
The replayed messages that follow the ``SnapshotOffer`` message, if any, are younger than the offered snapshot.
|
The replayed messages that follow the ``SnapshotOffer`` message, if any, are younger than the offered snapshot.
|
||||||
They finally recover the processor to its current (i.e. latest) state.
|
They finally recover the persistent actor to its current (i.e. latest) state.
|
||||||
|
|
||||||
In general, a processor is only offered a snapshot if that processor has previously saved one or more snapshots
|
In general, a persistent actor is only offered a snapshot if that persistent actor has previously saved one or more snapshots
|
||||||
and at least one of these snapshots matches the ``SnapshotSelectionCriteria`` that can be specified for recovery.
|
and at least one of these snapshots matches the ``SnapshotSelectionCriteria`` that can be specified for recovery.
|
||||||
|
|
||||||
.. includecode:: ../../../akka-samples/akka-sample-persistence-java-lambda/src/main/java/doc/LambdaPersistenceDocTest.java#snapshot-criteria
|
.. includecode:: ../../../akka-samples/akka-sample-persistence-java-lambda/src/main/java/doc/LambdaPersistenceDocTest.java#snapshot-criteria
|
||||||
|
|
@ -423,165 +365,10 @@ saved snapshot matches the specified ``SnapshotSelectionCriteria`` will replay a
|
||||||
Snapshot deletion
|
Snapshot deletion
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
A processor can delete individual snapshots by calling the ``deleteSnapshot`` method with the sequence number and the
|
A persistent actor can delete individual snapshots by calling the ``deleteSnapshot`` method with the sequence number and the
|
||||||
timestamp of a snapshot as argument. To bulk-delete snapshots matching ``SnapshotSelectionCriteria``, processors should
|
timestamp of a snapshot as argument. To bulk-delete snapshots matching ``SnapshotSelectionCriteria``, persistent actors should
|
||||||
use the ``deleteSnapshots`` method.
|
use the ``deleteSnapshots`` method.
|
||||||
|
|
||||||
.. _event-sourcing-java-lambda:
|
|
||||||
|
|
||||||
Event sourcing
|
|
||||||
==============
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
The ``PersistentActor`` introduced in this section was previously known as ``EventsourcedProcessor``,
|
|
||||||
which was a subset of the ``PersistentActor``. Migrating your code to use persistent actors instead is
|
|
||||||
very simple and is explained in the :ref:`migration-guide-persistence-experimental-2.3.x-2.4.x`.
|
|
||||||
|
|
||||||
In all the examples so far, messages that change a processor's state have been sent as ``Persistent`` messages
|
|
||||||
by an application, so that they can be replayed during recovery. From this point of view, the journal acts as
|
|
||||||
a write-ahead-log for whatever ``Persistent`` messages a processor receives. This is also known as *command
|
|
||||||
sourcing*. Commands, however, may fail and some applications cannot tolerate command failures during recovery.
|
|
||||||
|
|
||||||
For these applications `Event Sourcing`_ is a better choice. Applied to Akka persistence, the basic idea behind
|
|
||||||
event sourcing is quite simple. A processor receives a (non-persistent) command which is first validated if it
|
|
||||||
can be applied to the current state. Here, validation can mean anything, from simple inspection of a command
|
|
||||||
message's fields up to a conversation with several external services, for example. If validation succeeds, events
|
|
||||||
are generated from the command, representing the effect of the command. These events are then persisted and, after
|
|
||||||
successful persistence, used to change a processor's state. When the processor needs to be recovered, only the
|
|
||||||
persisted events are replayed of which we know that they can be successfully applied. In other words, events
|
|
||||||
cannot fail when being replayed to a processor, in contrast to commands. Eventsourced processors may of course
|
|
||||||
also process commands that do not change application state, such as query commands, for example.
|
|
||||||
|
|
||||||
.. _Event Sourcing: http://martinfowler.com/eaaDev/EventSourcing.html
|
|
||||||
|
|
||||||
Akka persistence supports event sourcing with the ``AbstractPersistentActor`` abstract class (which implements
|
|
||||||
event sourcing as a pattern on top of command sourcing). A processor that extends this abstract class does not handle
|
|
||||||
``Persistent`` messages directly but uses the ``persist`` method to persist and handle events. The behavior of an
|
|
||||||
``AbstractEventsPersistentActordefined by implementing ``receiveRecover`` and ``receiveCommand``. This is
|
|
||||||
demonstrated in the following example.
|
|
||||||
|
|
||||||
.. includecode:: ../../../akka-samples/akka-sample-persistence-java-lambda/src/main/java/sample/persistence/PersistentActorExample.java#persistent-actor-example
|
|
||||||
|
|
||||||
The example defines two data types, ``Cmd`` and ``Evt`` to represent commands and events, respectively. The
|
|
||||||
``state`` of the ``ExampleProcessor`` is a list of persisted event data contained in ``ExampleState``.
|
|
||||||
|
|
||||||
The processor's ``receiveRecover`` method defines how ``state`` is updated during recovery by handling ``Evt``
|
|
||||||
and ``SnapshotOffer`` messages. The processor's ``receiveCommand`` method is a command handler. In this example,
|
|
||||||
a command is handled by generating two events which are then persisted and handled. Events are persisted by calling
|
|
||||||
``persist`` with an event (or a sequence of events) as first argument and an event handler as second argument.
|
|
||||||
|
|
||||||
The ``persist`` method persists events asynchronously and the event handler is executed for successfully persisted
|
|
||||||
events. Successfully persisted events are internally sent back to the processor as individual messages that trigger
|
|
||||||
event handler executions. An event handler may close over processor state and mutate it. The sender of a persisted
|
|
||||||
event is the sender of the corresponding command. This allows event handlers to reply to the sender of a command
|
|
||||||
(not shown).
|
|
||||||
|
|
||||||
The main responsibility of an event handler is changing processor state using event data and notifying others
|
|
||||||
about successful state changes by publishing events.
|
|
||||||
|
|
||||||
When persisting events with ``persist`` it is guaranteed that the processor will not receive further commands between
|
|
||||||
the ``persist`` call and the execution(s) of the associated event handler. This also holds for multiple ``persist``
|
|
||||||
calls in context of a single command.
|
|
||||||
|
|
||||||
The easiest way to run this example yourself is to download `Typesafe Activator <http://www.typesafe.com/platform/getstarted>`_
|
|
||||||
and open the tutorial named `Akka Persistence Samples in Java with Lambdas <http://www.typesafe.com/activator/template/akka-sample-persistence-java-lambda>`_.
|
|
||||||
It contains instructions on how to run the ``PersistentActorExample``.
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
It's also possible to switch between different command handlers during normal processing and recovery
|
|
||||||
with ``context().become()`` and ``context().unbecome()``. To get the actor into the same state after
|
|
||||||
recovery you need to take special care to perform the same state transitions with ``become`` and
|
|
||||||
``unbecome`` in the ``receiveRecover`` method as you would have done in the command handler.
|
|
||||||
|
|
||||||
Relaxed local consistency requirements and high throughput use-cases
|
|
||||||
--------------------------------------------------------------------
|
|
||||||
|
|
||||||
If faced with Relaxed local consistency requirements and high throughput demands sometimes ``PersistentActor`` and it's
|
|
||||||
``persist`` may not be enough in terms of consuming incoming Commands at a high rate, because it has to wait until all
|
|
||||||
Events related to a given Command are processed in order to start processing the next Command. While this abstraction is
|
|
||||||
very useful for most cases, sometimes you may be faced with relaxed requirements about consistency – for example you may
|
|
||||||
want to process commands as fast as you can, assuming that Event will eventually be persisted and handled properly in
|
|
||||||
the background and retroactively reacting to persistence failures if needed.
|
|
||||||
|
|
||||||
The ``persistAsync`` method provides a tool for implementing high-throughput processors. It will *not*
|
|
||||||
stash incoming Commands while the Journal is still working on persisting and/or user code is executing event callbacks.
|
|
||||||
|
|
||||||
In the below example, the event callbacks may be called "at any time", even after the next Command has been processed.
|
|
||||||
The ordering between events is still guaranteed ("evt-b-1" will be sent after "evt-a-2", which will be sent after "evt-a-1" etc.).
|
|
||||||
|
|
||||||
.. includecode:: ../../../akka-samples/akka-sample-persistence-java-lambda/src/main/java/doc/LambdaPersistenceDocTest.java#persist-async
|
|
||||||
|
|
||||||
Notice that the client does not have to wrap any messages in the `Persistent` class in order to obtain "command sourcing like"
|
|
||||||
semantics. It's up to the processor to decide about persisting (or not) of messages, unlike ``Processor`` where the sender had to be aware of this decision.
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
In order to implement the pattern known as "*command sourcing*" simply ``persistAsync`` all incoming events right away,
|
|
||||||
and handle them in the callback.
|
|
||||||
|
|
||||||
.. _defer-java-lambda:
|
|
||||||
|
|
||||||
Deferring actions until preceeding persist handlers have executed
|
|
||||||
-----------------------------------------------------------------
|
|
||||||
|
|
||||||
Sometimes when working with ``persistAsync`` you may find that it would be nice to define some actions in terms of
|
|
||||||
''happens-after the previous ``persistAsync`` handlers have been invoked''. ``PersistentActor`` provides an utility method
|
|
||||||
called ``defer``, which works similarily to ``persistAsync`` yet does not persist the passed in event. It is recommended to
|
|
||||||
use it for *read* operations, and actions which do not have corresponding events in your domain model.
|
|
||||||
|
|
||||||
Using this method is very similar to the persist family of methods, yet it does **not** persist the passed in event.
|
|
||||||
It will be kept in memory and used when invoking the handler.
|
|
||||||
|
|
||||||
.. includecode:: ../../../akka-samples/akka-sample-persistence-java-lambda/src/main/java/doc/LambdaPersistenceDocTest.java#defer
|
|
||||||
|
|
||||||
Notice that the ``sender()`` is **safe** to access in the handler callback, and will be pointing to the original sender
|
|
||||||
of the command for which this ``defer`` handler was called.
|
|
||||||
|
|
||||||
.. includecode:: ../../../akka-samples/akka-sample-persistence-java-lambda/src/main/java/doc/LambdaPersistenceDocTest.java#defer-caller
|
|
||||||
|
|
||||||
Reliable event delivery
|
|
||||||
-----------------------
|
|
||||||
|
|
||||||
Sending events from an event handler to another actor has at-most-once delivery semantics. For at-least-once delivery,
|
|
||||||
:ref:`channels-java-lambda` must be used. In this case, also replayed events (received by ``receiveRecover``) must be
|
|
||||||
sent to a channel, as shown in the following example:
|
|
||||||
|
|
||||||
.. includecode:: ../../../akka-samples/akka-sample-persistence-java-lambda/src/main/java/doc/LambdaPersistenceDocTest.java#reliable-event-delivery
|
|
||||||
|
|
||||||
In larger integration scenarios, channel destinations may be actors that submit received events to an external
|
|
||||||
message broker, for example. After having successfully submitted an event, they should call ``confirm()`` on the
|
|
||||||
received ``ConfirmablePersistent`` message.
|
|
||||||
|
|
||||||
Batch writes
|
|
||||||
============
|
|
||||||
|
|
||||||
To optimize throughput, an ``AbstractProcessor`` internally batches received ``Persistent`` messages under high load
|
|
||||||
before
|
|
||||||
writing them to the journal (as a single batch). The batch size dynamically grows from 1 under low and moderate loads
|
|
||||||
to a configurable maximum size (default is ``200``) under high load.
|
|
||||||
|
|
||||||
.. includecode:: ../scala/code/docs/persistence/PersistencePluginDocSpec.scala#max-message-batch-size
|
|
||||||
|
|
||||||
A new batch write is triggered by a processor as soon as a batch reaches the maximum size or if the journal completed
|
|
||||||
writing the previous batch. Batch writes are never timer-based which keeps latencies at a minimum.
|
|
||||||
|
|
||||||
Applications that want to have more explicit control over batch writes and batch sizes can send processors
|
|
||||||
``PersistentBatch`` messages.
|
|
||||||
|
|
||||||
.. includecode:: ../../../akka-samples/akka-sample-persistence-java-lambda/src/main/java/doc/LambdaPersistenceDocTest.java#batch-write
|
|
||||||
|
|
||||||
``Persistent`` messages contained in a ``PersistentBatch`` are always written atomically, even if the batch
|
|
||||||
size is greater than ``max-message-batch-size``. Also, a ``PersistentBatch`` is written isolated from other batches.
|
|
||||||
``Persistent`` messages contained in a ``PersistentBatch`` are received individually by a processor.
|
|
||||||
|
|
||||||
``PersistentBatch`` messages, for example, are used internally by an ``AbstractEventsourcedPersistentActor atomic
|
|
||||||
writes of events. All events that are persisted in context of a single command are written as a single batch to the
|
|
||||||
journal (even if ``persist`` is called multiple times per command). The recovery of an ``AbstractPersistentActor``
|
|
||||||
will therefore never be done partially (with only a subset of events persisted by a single command).
|
|
||||||
|
|
||||||
Confirmation and deletion operations performed by :ref:`channels-java-lambda` are also batched. The maximum
|
|
||||||
confirmation and deletion batch sizes are configurable with ``akka.persistence.journal.max-confirmation-batch-size``
|
|
||||||
and ``akka.persistence.journal.max-deletion-batch-size``, respectively.
|
|
||||||
|
|
||||||
Storage plugins
|
Storage plugins
|
||||||
===============
|
===============
|
||||||
|
|
@ -656,7 +443,7 @@ Shared LevelDB journal
|
||||||
----------------------
|
----------------------
|
||||||
|
|
||||||
A LevelDB instance can also be shared by multiple actor systems (on the same or on different nodes). This, for
|
A LevelDB instance can also be shared by multiple actor systems (on the same or on different nodes). This, for
|
||||||
example, allows processors to failover to a backup node and continue using the shared journal instance from the
|
example, allows persistent actors to failover to a backup node and continue using the shared journal instance from the
|
||||||
backup node.
|
backup node.
|
||||||
|
|
||||||
.. warning::
|
.. warning::
|
||||||
|
|
@ -683,7 +470,7 @@ done by calling the ``SharedLeveldbJournal.setStore`` method with the actor refe
|
||||||
|
|
||||||
.. includecode:: ../../../akka-samples/akka-sample-persistence-java-lambda/src/main/java/doc/LambdaPersistencePluginDocTest.java#shared-store-usage
|
.. includecode:: ../../../akka-samples/akka-sample-persistence-java-lambda/src/main/java/doc/LambdaPersistencePluginDocTest.java#shared-store-usage
|
||||||
|
|
||||||
Internal journal commands (sent by processors) are buffered until injection completes. Injection is idempotent
|
Internal journal commands (sent by persistent actors) are buffered until injection completes. Injection is idempotent
|
||||||
i.e. only the first injection is used.
|
i.e. only the first injection is used.
|
||||||
|
|
||||||
.. _local-snapshot-store-java-lambda:
|
.. _local-snapshot-store-java-lambda:
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,6 @@ Persistence
|
||||||
###########
|
###########
|
||||||
|
|
||||||
|
|
||||||
Java 8 lambda expressions are also supported now. (See section :ref:`persistence-lambda-java`)
|
|
||||||
|
|
||||||
|
|
||||||
Akka persistence enables stateful actors to persist their internal state so that it can be recovered when an actor
|
Akka persistence enables stateful actors to persist their internal state so that it can be recovered when an actor
|
||||||
is started, restarted after a JVM crash or by a supervisor, or migrated in a cluster. The key concept behind Akka
|
is started, restarted after a JVM crash or by a supervisor, or migrated in a cluster. The key concept behind Akka
|
||||||
persistence is that only changes to an actor's internal state are persisted but never its current state directly
|
persistence is that only changes to an actor's internal state are persisted but never its current state directly
|
||||||
|
|
@ -15,11 +12,11 @@ persistence is that only changes to an actor's internal state are persisted but
|
||||||
allows for very high transaction rates and efficient replication. Stateful actors are recovered by replaying stored
|
allows for very high transaction rates and efficient replication. Stateful actors are recovered by replaying stored
|
||||||
changes to these actors from which they can rebuild internal state. This can be either the full history of changes
|
changes to these actors from which they can rebuild internal state. This can be either the full history of changes
|
||||||
or starting from a snapshot which can dramatically reduce recovery times. Akka persistence also provides point-to-point
|
or starting from a snapshot which can dramatically reduce recovery times. Akka persistence also provides point-to-point
|
||||||
communication channels with at-least-once message delivery semantics.
|
communication with at-least-once message delivery semantics.
|
||||||
|
|
||||||
.. Lambda warning::
|
.. note::
|
||||||
|
|
||||||
Java 8 lambda expressions are also supported now. (See section :ref:`persistence-lambda-java`)
|
Java 8 lambda expressions are also supported. (See section :ref:`persistence-lambda-java`)
|
||||||
|
|
||||||
.. warning::
|
.. warning::
|
||||||
|
|
||||||
|
|
@ -48,75 +45,113 @@ Akka persistence is a separate jar file. Make sure that you have the following d
|
||||||
Architecture
|
Architecture
|
||||||
============
|
============
|
||||||
|
|
||||||
* *Processor* (deprecated, use *PersistentActor* instead): A processor is a persistent, stateful actor. Messages sent
|
* *UntypedPersistentActor*: Is a persistent, stateful actor. It is able to persist events to a journal and can react to
|
||||||
to a processor are written to a journal before its ``onReceive`` method is called. When a processor is started or
|
|
||||||
restarted, journaled messages are replayed to that processor, so that it can recover internal state from these messages.
|
|
||||||
|
|
||||||
* *PersistentActor*: Is a persistent, stateful actor. It is able to persist events to a journal and can react to
|
|
||||||
them in a thread-safe manner. It can be used to implement both *command* as well as *event sourced* actors.
|
them in a thread-safe manner. It can be used to implement both *command* as well as *event sourced* actors.
|
||||||
When a persistent actor is started or restarted, journaled messages are replayed to that actor, so that it can
|
When a persistent actor is started or restarted, journaled messages are replayed to that actor, so that it can
|
||||||
recover internal state from these messages.
|
recover internal state from these messages.
|
||||||
|
|
||||||
* *View*: A view is a persistent, stateful actor that receives journaled messages that have been written by another
|
* *View*: A view is a persistent, stateful actor that receives journaled messages that have been written by another
|
||||||
processor. A view itself does not journal new messages, instead, it updates internal state only from a processor's
|
persistent actor. A view itself does not journal new messages, instead, it updates internal state only from a persistent actor's
|
||||||
replicated message stream.
|
replicated message stream.
|
||||||
|
|
||||||
* *Streams*: Messages written by a processor can be published in compliance with the `Reactive Streams`_ specification.
|
* *Streams*: Messages written by a persistent actor can be published in compliance with the `Reactive Streams`_ specification.
|
||||||
Only those messages that are explicitly requested from downstream processors are actually pulled from a processor's
|
Only those messages that are explicitly requested from downstream persistent actors are actually pulled from a persistent actor's
|
||||||
journal.
|
journal.
|
||||||
|
|
||||||
* *Channel*: Channels are used by processors and views to communicate with other actors. They prevent that replayed
|
* *UntypedPersistentActorAtLeastOnceDelivery*: To send messages with at-least-once delivery semantics to destinations, also in
|
||||||
messages are redundantly delivered to these actors and provide at-least-once message delivery semantics, also in
|
|
||||||
case of sender and receiver JVM crashes.
|
case of sender and receiver JVM crashes.
|
||||||
|
|
||||||
* *Journal*: A journal stores the sequence of messages sent to a processor. An application can control which messages
|
* *Journal*: A journal stores the sequence of messages sent to a persistent actor. An application can control which messages
|
||||||
are journaled and which are received by the processor without being journaled. The storage backend of a journal is
|
are journaled and which are received by the persistent actor without being journaled. The storage backend of a journal is
|
||||||
pluggable. The default journal storage plugin writes to the local filesystem, replicated journals are available as
|
pluggable. The default journal storage plugin writes to the local filesystem, replicated journals are available as
|
||||||
`Community plugins`_.
|
`Community plugins`_.
|
||||||
|
|
||||||
* *Snapshot store*: A snapshot store persists snapshots of a processor's or a view's internal state. Snapshots are
|
* *Snapshot store*: A snapshot store persists snapshots of a persistent actor's or a view's internal state. Snapshots are
|
||||||
used for optimizing recovery times. The storage backend of a snapshot store is pluggable. The default snapshot
|
used for optimizing recovery times. The storage backend of a snapshot store is pluggable. The default snapshot
|
||||||
storage plugin writes to the local filesystem.
|
storage plugin writes to the local filesystem.
|
||||||
|
|
||||||
* *Event sourcing*. Based on the building blocks described above, Akka persistence provides abstractions for the
|
|
||||||
development of event sourced applications (see section :ref:`event-sourcing-java`)
|
|
||||||
|
|
||||||
.. _Community plugins: http://akka.io/community/
|
.. _Community plugins: http://akka.io/community/
|
||||||
.. _Reactive Streams: http://www.reactive-streams.org/
|
.. _Reactive Streams: http://www.reactive-streams.org/
|
||||||
|
|
||||||
.. _processors-java:
|
.. _event-sourcing-java:
|
||||||
|
|
||||||
Processors
|
Event sourcing
|
||||||
==========
|
==============
|
||||||
|
|
||||||
.. warning::
|
The basic idea behind `Event Sourcing`_ is quite simple. A persistent actor receives a (non-persistent) command
|
||||||
``Processor`` is deprecated. Instead the current ``PersistentActor`` will be extended to provide equivalent
|
which is first validated if it can be applied to the current state. Here, validation can mean anything, from simple
|
||||||
functionality if required (by introducing the ``persistAsync`` method).
|
inspection of a command message's fields up to a conversation with several external services, for example.
|
||||||
For details see `Relaxed local consistency requirements and high throughput use-cases`_ as well as the discussion
|
If validation succeeds, events are generated from the command, representing the effect of the command. These events
|
||||||
and pull requests related to this `issue on Github <https://github.com/akka/akka/issues/15230>`_.
|
are then persisted and, after successful persistence, used to change the actor's state. When the persistent actor
|
||||||
|
needs to be recovered, only the persisted events are replayed of which we know that they can be successfully applied.
|
||||||
|
In other words, events cannot fail when being replayed to a persistent actor, in contrast to commands. Event sourced
|
||||||
|
actors may of course also process commands that do not change application state, such as query commands, for example.
|
||||||
|
|
||||||
A processor can be implemented by extending the abstract ``UntypedProcessor`` class and implementing the
|
.. _Event Sourcing: http://martinfowler.com/eaaDev/EventSourcing.html
|
||||||
``onReceive`` method.
|
|
||||||
|
|
||||||
.. includecode:: code/docs/persistence/PersistenceDocTest.java#definition
|
Akka persistence supports event sourcing with the ``UntypedPersistentActor`` abstract class. An actor that extends this
|
||||||
|
class uses the ``persist`` method to persist and handle events. The behavior of an ``UntypedPersistentActor``
|
||||||
|
is defined by implementing ``receiveRecover`` and ``receiveCommand``. This is demonstrated in the following example.
|
||||||
|
|
||||||
Processors only write messages of type ``Persistent`` to the journal, others are received without being persisted.
|
.. includecode:: ../../../akka-samples/akka-sample-persistence-java/src/main/java/sample/persistence/PersistentActorExample.java#persistent-actor-example
|
||||||
When a processor's ``onReceive`` method is called with a ``Persistent`` message it can safely assume that this message
|
|
||||||
has been successfully written to the journal. If a journal fails to write a ``Persistent`` message then the processor
|
|
||||||
is stopped, by default. If a processor should continue running on persistence failures it must handle
|
|
||||||
``PersistenceFailure`` messages. In this case, a processor may want to inform the sender about the failure,
|
|
||||||
so that the sender can re-send the message, if needed.
|
|
||||||
|
|
||||||
An ``UntypedProcessor`` itself is an ``Actor`` and can therefore be instantiated with ``actorOf``.
|
The example defines two data types, ``Cmd`` and ``Evt`` to represent commands and events, respectively. The
|
||||||
|
``state`` of the ``ExampleProcessor`` is a list of persisted event data contained in ``ExampleState``.
|
||||||
|
|
||||||
.. includecode:: code/docs/persistence/PersistenceDocTest.java#usage
|
The persistent actor's ``onReceiveRecover`` method defines how ``state`` is updated during recovery by handling ``Evt``
|
||||||
|
and ``SnapshotOffer`` messages. The persistent actor's ``onReceiveCommand`` method is a command handler. In this example,
|
||||||
|
a command is handled by generating two events which are then persisted and handled. Events are persisted by calling
|
||||||
|
``persist`` with an event (or a sequence of events) as first argument and an event handler as second argument.
|
||||||
|
|
||||||
|
The ``persist`` method persists events asynchronously and the event handler is executed for successfully persisted
|
||||||
|
events. Successfully persisted events are internally sent back to the persistent actor as individual messages that trigger
|
||||||
|
event handler executions. An event handler may close over persistent actor state and mutate it. The sender of a persisted
|
||||||
|
event is the sender of the corresponding command. This allows event handlers to reply to the sender of a command
|
||||||
|
(not shown).
|
||||||
|
|
||||||
|
The main responsibility of an event handler is changing persistent actor state using event data and notifying others
|
||||||
|
about successful state changes by publishing events.
|
||||||
|
|
||||||
|
When persisting events with ``persist`` it is guaranteed that the persistent actor will not receive further commands between
|
||||||
|
the ``persist`` call and the execution(s) of the associated event handler. This also holds for multiple ``persist``
|
||||||
|
calls in context of a single command.
|
||||||
|
|
||||||
|
The easiest way to run this example yourself is to download `Typesafe Activator <http://www.typesafe.com/platform/getstarted>`_
|
||||||
|
and open the tutorial named `Akka Persistence Samples with Java <http://www.typesafe.com/activator/template/akka-sample-persistence-java>`_.
|
||||||
|
It contains instructions on how to run the ``PersistentActorExample``.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
It's also possible to switch between different command handlers during normal processing and recovery
|
||||||
|
with ``getContext().become()`` and ``getContext().unbecome()``. To get the actor into the same state after
|
||||||
|
recovery you need to take special care to perform the same state transitions with ``become`` and
|
||||||
|
``unbecome`` in the ``receiveRecover`` method as you would have done in the command handler.
|
||||||
|
|
||||||
|
Identifiers
|
||||||
|
-----------
|
||||||
|
|
||||||
|
A persistent actor must have an identifier that doesn't change across different actor incarnations. It defaults to the
|
||||||
|
``String`` representation of persistent actor's path without the address part and can be obtained via the ``persistenceId``
|
||||||
|
method.
|
||||||
|
|
||||||
|
.. includecode:: code/docs/persistence/PersistenceDocTest.java#persistence-id
|
||||||
|
|
||||||
|
Applications can customize a persistent actor's id by specifying an actor name during persistent actor creation as shown in
|
||||||
|
section :ref:`event-sourcing-java`. This changes that processor's name in its actor hierarchy and hence influences only
|
||||||
|
part of the processor id. To fully customize a processor's id, the ``persistenceId`` method must be overridden.
|
||||||
|
|
||||||
|
.. includecode:: code/docs/persistence/PersistenceDocTest.java#persistence-id-override
|
||||||
|
|
||||||
|
Overriding ``persistenceId`` is the recommended way to generate stable identifiers.
|
||||||
|
|
||||||
|
.. _recovery-java:
|
||||||
|
|
||||||
Recovery
|
Recovery
|
||||||
--------
|
--------
|
||||||
|
|
||||||
By default, a processor is automatically recovered on start and on restart by replaying journaled messages.
|
By default, a persistent actor is automatically recovered on start and on restart by replaying journaled messages.
|
||||||
New messages sent to a processor during recovery do not interfere with replayed messages. New messages will
|
New messages sent to a persistent actor during recovery do not interfere with replayed messages. New messages will
|
||||||
only be received by a processor after recovery completes.
|
only be received by a persistent actor after recovery completes.
|
||||||
|
|
||||||
Recovery customization
|
Recovery customization
|
||||||
^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
@ -125,7 +160,7 @@ Automated recovery on start can be disabled by overriding ``preStart`` with an e
|
||||||
|
|
||||||
.. includecode:: code/docs/persistence/PersistenceDocTest.java#recover-on-start-disabled
|
.. includecode:: code/docs/persistence/PersistenceDocTest.java#recover-on-start-disabled
|
||||||
|
|
||||||
In this case, a processor must be recovered explicitly by sending it a ``Recover`` message.
|
In this case, a persistent actor must be recovered explicitly by sending it a ``Recover`` message.
|
||||||
|
|
||||||
.. includecode:: code/docs/persistence/PersistenceDocTest.java#recover-explicit
|
.. includecode:: code/docs/persistence/PersistenceDocTest.java#recover-explicit
|
||||||
|
|
||||||
|
|
@ -134,7 +169,7 @@ If not overridden, ``preStart`` sends a ``Recover`` message to ``getSelf()``. Ap
|
||||||
|
|
||||||
.. includecode:: code/docs/persistence/PersistenceDocTest.java#recover-on-start-custom
|
.. includecode:: code/docs/persistence/PersistenceDocTest.java#recover-on-start-custom
|
||||||
|
|
||||||
Upper sequence number bounds can be used to recover a processor to past state instead of current state. Automated
|
Upper sequence number bounds can be used to recover a persistent actor to past state instead of current state. Automated
|
||||||
recovery on restart can be disabled by overriding ``preRestart`` with an empty implementation.
|
recovery on restart can be disabled by overriding ``preRestart`` with an empty implementation.
|
||||||
|
|
||||||
.. includecode:: code/docs/persistence/PersistenceDocTest.java#recover-on-restart-disabled
|
.. includecode:: code/docs/persistence/PersistenceDocTest.java#recover-on-restart-disabled
|
||||||
|
|
@ -142,56 +177,93 @@ recovery on restart can be disabled by overriding ``preRestart`` with an empty i
|
||||||
Recovery status
|
Recovery status
|
||||||
^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
A processor can query its own recovery status via the methods
|
A persistent actor can query its own recovery status via the methods
|
||||||
|
|
||||||
.. includecode:: code/docs/persistence/PersistenceDocTest.java#recovery-status
|
.. includecode:: code/docs/persistence/PersistenceDocTest.java#recovery-status
|
||||||
|
|
||||||
Sometimes there is a need for performing additional initialization when the
|
Sometimes there is a need for performing additional initialization when the
|
||||||
recovery has completed, before processing any other message sent to the processor.
|
recovery has completed, before processing any other message sent to the persistent actor.
|
||||||
The processor will receive a special :class:`RecoveryCompleted` message right after recovery
|
The persistent actor will receive a special :class:`RecoveryCompleted` message right after recovery
|
||||||
and before any other received messages. If there is a problem with recovering the state of
|
and before any other received messages.
|
||||||
the actor from the journal, the actor will be sent a :class:`RecoveryFailure` message that
|
|
||||||
it can choose to handle. If the actor doesn't handle the :class:`RecoveryFailure` message it
|
If there is a problem with recovering the state of the actor from the journal, the actor will be
|
||||||
will be stopped.
|
sent a :class:`RecoveryFailure` message that it can choose to handle in ``receiveRecover``. If the
|
||||||
|
actor doesn't handle the :class:`RecoveryFailure` message it will be stopped.
|
||||||
|
|
||||||
.. includecode:: code/docs/persistence/PersistenceDocTest.java#recovery-completed
|
.. includecode:: code/docs/persistence/PersistenceDocTest.java#recovery-completed
|
||||||
|
|
||||||
.. _failure-handling-java:
|
.. _persist-async-java:
|
||||||
|
|
||||||
Failure handling
|
Relaxed local consistency requirements and high throughput use-cases
|
||||||
^^^^^^^^^^^^^^^^
|
--------------------------------------------------------------------
|
||||||
|
|
||||||
A persistent message that caused an exception will be received again by a processor after restart. To prevent
|
If faced with relaxed local consistency requirements and high throughput demands sometimes ``PersistentActor`` and it's
|
||||||
a replay of that message during recovery it can be deleted.
|
``persist`` may not be enough in terms of consuming incoming Commands at a high rate, because it has to wait until all
|
||||||
|
Events related to a given Command are processed in order to start processing the next Command. While this abstraction is
|
||||||
|
very useful for most cases, sometimes you may be faced with relaxed requirements about consistency – for example you may
|
||||||
|
want to process commands as fast as you can, assuming that Event will eventually be persisted and handled properly in
|
||||||
|
the background and retroactively reacting to persistence failures if needed.
|
||||||
|
|
||||||
|
The ``persistAsync`` method provides a tool for implementing high-throughput persistent actors. It will *not*
|
||||||
|
stash incoming Commands while the Journal is still working on persisting and/or user code is executing event callbacks.
|
||||||
|
|
||||||
|
In the below example, the event callbacks may be called "at any time", even after the next Command has been processed.
|
||||||
|
The ordering between events is still guaranteed ("evt-b-1" will be sent after "evt-a-2", which will be sent after "evt-a-1" etc.).
|
||||||
|
|
||||||
|
.. includecode:: code/docs/persistence/PersistenceDocTest.java#persist-async
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
In order to implement the pattern known as "*command sourcing*" simply ``persistAsync`` all incoming events right away,
|
||||||
|
and handle them in the callback.
|
||||||
|
|
||||||
|
.. _defer-java:
|
||||||
|
|
||||||
|
Deferring actions until preceeding persist handlers have executed
|
||||||
|
-----------------------------------------------------------------
|
||||||
|
|
||||||
|
Sometimes when working with ``persistAsync`` you may find that it would be nice to define some actions in terms of
|
||||||
|
''happens-after the previous ``persistAsync`` handlers have been invoked''. ``PersistentActor`` provides an utility method
|
||||||
|
called ``defer``, which works similarily to ``persistAsync`` yet does not persist the passed in event. It is recommended to
|
||||||
|
use it for *read* operations, and actions which do not have corresponding events in your domain model.
|
||||||
|
|
||||||
|
Using this method is very similar to the persist family of methods, yet it does **not** persist the passed in event.
|
||||||
|
It will be kept in memory and used when invoking the handler.
|
||||||
|
|
||||||
|
.. includecode:: code/docs/persistence/PersistenceDocTest.java#defer
|
||||||
|
|
||||||
|
Notice that the ``sender()`` is **safe** to access in the handler callback, and will be pointing to the original sender
|
||||||
|
of the command for which this ``defer`` handler was called.
|
||||||
|
|
||||||
|
.. includecode:: code/docs/persistence/PersistenceDocTest.java#defer-caller
|
||||||
|
|
||||||
|
Batch writes
|
||||||
|
------------
|
||||||
|
|
||||||
|
To optimize throughput, a persistent actor internally batches events to be stored under high load before
|
||||||
|
writing them to the journal (as a single batch). The batch size dynamically grows from 1 under low and moderate loads
|
||||||
|
to a configurable maximum size (default is ``200``) under high load. When using ``persistAsync`` this increases
|
||||||
|
the maximum throughput dramatically.
|
||||||
|
|
||||||
|
.. includecode:: ../scala/code/docs/persistence/PersistencePluginDocSpec.scala#max-message-batch-size
|
||||||
|
|
||||||
|
A new batch write is triggered by a persistent actor as soon as a batch reaches the maximum size or if the journal completed
|
||||||
|
writing the previous batch. Batch writes are never timer-based which keeps latencies at a minimum.
|
||||||
|
|
||||||
|
The batches are also used internally to ensure atomic writes of events. All events that are persisted in context
|
||||||
|
of a single command are written as a single batch to the journal (even if ``persist`` is called multiple times per command).
|
||||||
|
The recovery of an ``UntypedPersistentActor`` will therefore never be done partially (with only a subset of events persisted by a
|
||||||
|
single command).
|
||||||
|
|
||||||
.. includecode:: code/docs/persistence/PersistenceDocTest.java#deletion
|
|
||||||
|
|
||||||
Message deletion
|
Message deletion
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
A processor can delete a single message by calling the ``deleteMessage`` method with the sequence number of
|
A persistent actor can delete a single message by calling the ``deleteMessage`` method with the sequence number of
|
||||||
that message as argument. An optional ``permanent`` parameter specifies whether the message shall be permanently
|
that message as argument. An optional ``permanent`` parameter specifies whether the message shall be permanently
|
||||||
deleted from the journal or only marked as deleted. In both cases, the message won't be replayed. Later extensions
|
deleted from the journal or only marked as deleted. In both cases, the message won't be replayed. Later extensions
|
||||||
to Akka persistence will allow to replay messages that have been marked as deleted which can be useful for debugging
|
to Akka persistence will allow to replay messages that have been marked as deleted which can be useful for debugging
|
||||||
purposes, for example. To delete all messages (journaled by a single processor) up to a specified sequence number,
|
purposes, for example. To delete all messages (journaled by a single persistent actor) up to a specified sequence number,
|
||||||
processors should call the ``deleteMessages`` method.
|
persistent actors should call the ``deleteMessages`` method.
|
||||||
|
|
||||||
Identifiers
|
|
||||||
-----------
|
|
||||||
|
|
||||||
A processor must have an identifier that doesn't change across different actor incarnations. It defaults to the
|
|
||||||
``String`` representation of processor's path without the address part and can be obtained via the ``persistenceId``
|
|
||||||
method.
|
|
||||||
|
|
||||||
.. includecode:: code/docs/persistence/PersistenceDocTest.java#persistence-id
|
|
||||||
|
|
||||||
Applications can customize a processor's id by specifying an actor name during processor creation as shown in
|
|
||||||
section :ref:`processors-java`. This changes that processor's name in its actor hierarchy and hence influences only
|
|
||||||
part of the processor id. To fully customize a processor's id, the ``persistenceId`` method must be overridden.
|
|
||||||
|
|
||||||
.. includecode:: code/docs/persistence/PersistenceDocTest.java#persistence-id-override
|
|
||||||
|
|
||||||
Overriding ``persistenceId`` is the recommended way to generate stable identifiers.
|
|
||||||
|
|
||||||
.. _views-java:
|
.. _views-java:
|
||||||
|
|
||||||
|
|
@ -203,9 +275,9 @@ methods.
|
||||||
|
|
||||||
.. includecode:: code/docs/persistence/PersistenceDocTest.java#view
|
.. includecode:: code/docs/persistence/PersistenceDocTest.java#view
|
||||||
|
|
||||||
The ``persistenceId`` identifies the processor from which the view receives journaled messages. It is not necessary
|
The ``persistenceId`` identifies the persistent actor from which the view receives journaled messages. It is not necessary
|
||||||
the referenced processor is actually running. Views read messages from a processor's journal directly. When a
|
the referenced persistent actor is actually running. Views read messages from a persistent actor's journal directly. When a
|
||||||
processor is started later and begins to write new messages, the corresponding view is updated automatically, by
|
persistent actor is started later and begins to write new messages, the corresponding view is updated automatically, by
|
||||||
default.
|
default.
|
||||||
|
|
||||||
Updates
|
Updates
|
||||||
|
|
@ -238,9 +310,11 @@ of replayed messages for manual updates can be limited with the ``replayMax`` pa
|
||||||
Recovery
|
Recovery
|
||||||
--------
|
--------
|
||||||
|
|
||||||
Initial recovery of views works in the very same way as for :ref:`processors` (i.e. by sending a ``Recover`` message
|
Initial recovery of views works in the very same way as for a persistent actor (i.e. by sending a ``Recover`` message
|
||||||
to self). The maximum number of replayed messages during initial recovery is determined by ``autoUpdateReplayMax``.
|
to self). The maximum number of replayed messages during initial recovery is determined by ``autoUpdateReplayMax``.
|
||||||
Further possibilities to customize initial recovery are explained in section :ref:`processors-java`.
|
Further possibilities to customize initial recovery are explained in section :ref:`recovery-java`.
|
||||||
|
|
||||||
|
.. _persistence-identifiers-java:
|
||||||
|
|
||||||
Identifiers
|
Identifiers
|
||||||
-----------
|
-----------
|
||||||
|
|
@ -254,7 +328,7 @@ name in its actor hierarchy and hence influences only part of the view id. To fu
|
||||||
``viewId`` method must be overridden. Overriding ``viewId`` is the recommended way to generate stable identifiers.
|
``viewId`` method must be overridden. Overriding ``viewId`` is the recommended way to generate stable identifiers.
|
||||||
|
|
||||||
The ``viewId`` must differ from the referenced ``persistenceId``, unless :ref:`snapshots-java` of a view and its
|
The ``viewId`` must differ from the referenced ``persistenceId``, unless :ref:`snapshots-java` of a view and its
|
||||||
processor shall be shared (which is what applications usually do not want).
|
persistent actor shall be shared (which is what applications usually do not want).
|
||||||
|
|
||||||
.. _streams-java:
|
.. _streams-java:
|
||||||
|
|
||||||
|
|
@ -263,199 +337,28 @@ Streams
|
||||||
|
|
||||||
Java API coming soon. See also Scala :ref:`streams` documentation.
|
Java API coming soon. See also Scala :ref:`streams` documentation.
|
||||||
|
|
||||||
.. _channels-java:
|
|
||||||
|
|
||||||
Channels
|
|
||||||
========
|
|
||||||
|
|
||||||
Channels are special actors that are used by processors or views to communicate with other actors (channel
|
|
||||||
destinations). The following discusses channels in context of processors but this is also applicable to views.
|
|
||||||
|
|
||||||
Channels prevent redundant delivery of replayed messages to destinations during processor recovery. A replayed
|
|
||||||
message is retained by a channel if its delivery has been confirmed by a destination.
|
|
||||||
|
|
||||||
.. includecode:: code/docs/persistence/PersistenceDocTest.java#channel-example
|
|
||||||
|
|
||||||
A channel is ready to use once it has been created, no recovery or further activation is needed. A ``Deliver``
|
|
||||||
request instructs a channel to send a ``Persistent`` message to a destination. A destination is provided as
|
|
||||||
``ActorPath`` and messages are sent by the channel via that path's ``ActorSelection``. Sender references are
|
|
||||||
preserved by a channel, therefore, a destination can reply to the sender of a ``Deliver`` request.
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
Sending via a channel has at-least-once delivery semantics—by virtue of either
|
|
||||||
the sending actor or the channel being persistent—which means that the
|
|
||||||
semantics do not match those of a normal :class:`ActorRef` send operation:
|
|
||||||
|
|
||||||
* it is not at-most-once delivery
|
|
||||||
|
|
||||||
* message order for the same sender–receiver pair is not retained due to
|
|
||||||
possible resends
|
|
||||||
|
|
||||||
* after a crash and restart of the destination messages are still
|
|
||||||
delivered—to the new actor incarnation
|
|
||||||
|
|
||||||
These semantics match precisely what an :class:`ActorPath` represents (see
|
|
||||||
:ref:`actor-lifecycle-java`), therefore you need to supply a path and not a
|
|
||||||
reference when constructing :class:`Deliver` messages.
|
|
||||||
|
|
||||||
If a processor wants to reply to a ``Persistent`` message sender it should use the ``getSender()`` path as
|
|
||||||
channel destination.
|
|
||||||
|
|
||||||
.. includecode:: code/docs/persistence/PersistenceDocTest.java#channel-example-reply
|
|
||||||
|
|
||||||
Persistent messages delivered by a channel are of type ``ConfirmablePersistent``. ``ConfirmablePersistent`` extends
|
|
||||||
``Persistent`` by adding the methods ``confirm`` and ``redeliveries`` (see also :ref:`redelivery-java`). A channel
|
|
||||||
destination confirms the delivery of a ``ConfirmablePersistent`` message by calling ``confirm()`` on that message.
|
|
||||||
This asynchronously writes a confirmation entry to the journal. Replayed messages internally contain confirmation
|
|
||||||
entries which allows a channel to decide if it should retain these messages or not.
|
|
||||||
|
|
||||||
A ``Processor`` can also be used as channel destination i.e. it can persist ``ConfirmablePersistent`` messages too.
|
|
||||||
|
|
||||||
.. _redelivery-java:
|
|
||||||
|
|
||||||
Message re-delivery
|
|
||||||
-------------------
|
|
||||||
|
|
||||||
Channels re-deliver messages to destinations if they do not confirm delivery within a configurable timeout.
|
|
||||||
This timeout can be specified as ``redeliverInterval`` when creating a channel, optionally together with the
|
|
||||||
maximum number of re-deliveries a channel should attempt for each unconfirmed message. The number of re-delivery
|
|
||||||
attempts can be obtained via the ``redeliveries`` method on ``ConfirmablePersistent``.
|
|
||||||
|
|
||||||
.. includecode:: code/docs/persistence/PersistenceDocTest.java#channel-custom-settings
|
|
||||||
|
|
||||||
A channel keeps messages in memory until their successful delivery has been confirmed or the maximum number of
|
|
||||||
re-deliveries is reached. To be notified about messages that have reached the maximum number of re-deliveries,
|
|
||||||
applications can register a listener at channel creation.
|
|
||||||
|
|
||||||
.. includecode:: code/docs/persistence/PersistenceDocTest.java#channel-custom-listener
|
|
||||||
|
|
||||||
A listener receives ``RedeliverFailure`` notifications containing all messages that could not be delivered. On
|
|
||||||
receiving a ``RedeliverFailure`` message, an application may decide to restart the sending processor to enforce
|
|
||||||
a re-send of these messages to the channel or confirm these messages to prevent further re-sends. The sending
|
|
||||||
processor can also be restarted any time later to re-send unconfirmed messages.
|
|
||||||
|
|
||||||
This combination of
|
|
||||||
|
|
||||||
* message persistence by sending processors
|
|
||||||
* message replays by sending processors
|
|
||||||
* message re-deliveries by channels and
|
|
||||||
* application-level confirmations (acknowledgements) by destinations
|
|
||||||
|
|
||||||
enables channels to provide at-least-once message delivery semantics. Possible duplicates can be detected by
|
|
||||||
destinations by tracking message sequence numbers. Message sequence numbers are generated per sending processor.
|
|
||||||
Depending on how a processor routes outbound messages to destinations, they may either see a contiguous message
|
|
||||||
sequence or a sequence with gaps.
|
|
||||||
|
|
||||||
.. warning::
|
|
||||||
|
|
||||||
If a processor emits more than one outbound message per inbound ``Persistent`` message it **must** use a
|
|
||||||
separate channel for each outbound message to ensure that confirmations are uniquely identifiable, otherwise,
|
|
||||||
at-least-once message delivery semantics do not apply. This rule has been introduced to avoid writing additional
|
|
||||||
outbound message identifiers to the journal which would decrease the overall throughput. It is furthermore
|
|
||||||
recommended to collapse multiple outbound messages to the same destination into a single outbound message,
|
|
||||||
otherwise, if sent via multiple channels, their ordering is not defined.
|
|
||||||
|
|
||||||
If an application wants to have more control how sequence numbers are assigned to messages it should use an
|
|
||||||
application-specific sequence number generator and include the generated sequence numbers into the ``payload``
|
|
||||||
of ``Persistent`` messages.
|
|
||||||
|
|
||||||
Persistent channels
|
|
||||||
-------------------
|
|
||||||
|
|
||||||
Channels created with ``Channel.props`` do not persist messages. These channels are usually used in combination
|
|
||||||
with a sending processor that takes care of persistence, hence, channel-specific persistence is not necessary in
|
|
||||||
this case. They are referred to as transient channels in the following.
|
|
||||||
|
|
||||||
Persistent channels are like transient channels but additionally persist messages before delivering them. Messages
|
|
||||||
that have been persisted by a persistent channel are deleted when destinations confirm their delivery. A persistent
|
|
||||||
channel can be created with ``PersistentChannel.props`` and configured with a ``PersistentChannelSettings`` object.
|
|
||||||
|
|
||||||
.. includecode:: code/docs/persistence/PersistenceDocTest.java#persistent-channel-example
|
|
||||||
|
|
||||||
A persistent channel is useful for delivery of messages to slow destinations or destinations that are unavailable
|
|
||||||
for a long time. It can constrain the number of pending confirmations based on the ``pendingConfirmationsMax``
|
|
||||||
and ``pendingConfirmationsMin`` parameters of ``PersistentChannelSettings``.
|
|
||||||
|
|
||||||
.. includecode:: code/docs/persistence/PersistenceDocTest.java#persistent-channel-watermarks
|
|
||||||
|
|
||||||
It suspends delivery when the number of pending confirmations reaches ``pendingConfirmationsMax`` and resumes
|
|
||||||
delivery again when this number falls below ``pendingConfirmationsMin``. This prevents both, flooding destinations
|
|
||||||
with more messages than they can process and unlimited memory consumption by the channel. A persistent channel
|
|
||||||
continues to persist new messages even when message delivery is temporarily suspended.
|
|
||||||
|
|
||||||
Standalone usage
|
|
||||||
----------------
|
|
||||||
|
|
||||||
Applications may also use channels standalone. Transient channels can be used standalone if re-delivery attempts
|
|
||||||
to destinations are required but message loss in case of a sender JVM crash is not an issue. If message loss in
|
|
||||||
case of a sender JVM crash is an issue, persistent channels should be used. In this case, applications may want to
|
|
||||||
receive replies from the channel whether messages have been successfully persisted or not. This can be enabled by
|
|
||||||
creating the channel with the ``replyPersistent`` configuration parameter set to ``true``:
|
|
||||||
|
|
||||||
.. includecode:: code/docs/persistence/PersistenceDocTest.java#persistent-channel-reply
|
|
||||||
|
|
||||||
With this setting, either the successfully persisted message is replied to the sender or a ``PersistenceFailure``
|
|
||||||
message. In case the latter case, the sender should re-send the message.
|
|
||||||
|
|
||||||
Identifiers
|
|
||||||
-----------
|
|
||||||
|
|
||||||
In the same way as :ref:`processors-java` and :ref:`views-java`, channels also have an identifier that defaults to a channel's
|
|
||||||
path. A channel identifier can therefore be customized by using a custom actor name at channel creation. This changes
|
|
||||||
that channel's name in its actor hierarchy and hence influences only part of the channel identifier. To fully customize
|
|
||||||
a channel identifier, it should be provided as argument ``Channel.props(String)`` or ``PersistentChannel.props(String)``
|
|
||||||
(recommended to generate stable identifiers).
|
|
||||||
|
|
||||||
.. includecode:: code/docs/persistence/PersistenceDocTest.java#channel-id-override
|
|
||||||
|
|
||||||
Persistent messages
|
|
||||||
===================
|
|
||||||
|
|
||||||
Payload
|
|
||||||
-------
|
|
||||||
|
|
||||||
The payload of a ``Persistent`` message can be obtained via its ``payload`` method. Inside processors, new messages
|
|
||||||
must be derived from the current persistent message before sending them via a channel, either by calling ``p.withPayload(...)``
|
|
||||||
or ``Persistent.create(..., getCurrentPersistentMessage())`` where ``getCurrentPersistentMessage()`` is defined on
|
|
||||||
``UntypedProcessor``.
|
|
||||||
|
|
||||||
.. includecode:: code/docs/persistence/PersistenceDocTest.java#current-message
|
|
||||||
|
|
||||||
This is necessary for delivery confirmations to work properly. Both
|
|
||||||
ways are equivalent but we recommend using ``p.withPayload(...)`` for clarity. It is not allowed to send a message
|
|
||||||
via a channel that has been created with ``Persistent.create(...)``. This would redeliver the message on every replay
|
|
||||||
even though its delivery was confirmed by a destination.
|
|
||||||
|
|
||||||
Sequence number
|
|
||||||
---------------
|
|
||||||
|
|
||||||
The sequence number of a ``Persistent`` message can be obtained via its ``sequenceNr`` method. Persistent
|
|
||||||
messages are assigned sequence numbers on a per-processor basis (or per channel basis if used
|
|
||||||
standalone). A sequence starts at ``1L`` and doesn't contain gaps unless a processor deletes messages.
|
|
||||||
|
|
||||||
.. _snapshots-java:
|
.. _snapshots-java:
|
||||||
|
|
||||||
Snapshots
|
Snapshots
|
||||||
=========
|
=========
|
||||||
|
|
||||||
Snapshots can dramatically reduce recovery times of processors and views. The following discusses snapshots
|
Snapshots can dramatically reduce recovery times of persistent actor and views. The following discusses snapshots
|
||||||
in context of processors but this is also applicable to views.
|
in context of persistent actor but this is also applicable to views.
|
||||||
|
|
||||||
Processors can save snapshots of internal state by calling the ``saveSnapshot`` method. If saving of a snapshot
|
Persistent actor can save snapshots of internal state by calling the ``saveSnapshot`` method. If saving of a snapshot
|
||||||
succeeds, the processor receives a ``SaveSnapshotSuccess`` message, otherwise a ``SaveSnapshotFailure`` message
|
succeeds, the persistent actor receives a ``SaveSnapshotSuccess`` message, otherwise a ``SaveSnapshotFailure`` message
|
||||||
|
|
||||||
.. includecode:: code/docs/persistence/PersistenceDocTest.java#save-snapshot
|
.. includecode:: code/docs/persistence/PersistenceDocTest.java#save-snapshot
|
||||||
|
|
||||||
During recovery, the processor is offered a previously saved snapshot via a ``SnapshotOffer`` message from
|
During recovery, the persistent actor is offered a previously saved snapshot via a ``SnapshotOffer`` message from
|
||||||
which it can initialize internal state.
|
which it can initialize internal state.
|
||||||
|
|
||||||
.. includecode:: code/docs/persistence/PersistenceDocTest.java#snapshot-offer
|
.. includecode:: code/docs/persistence/PersistenceDocTest.java#snapshot-offer
|
||||||
|
|
||||||
The replayed messages that follow the ``SnapshotOffer`` message, if any, are younger than the offered snapshot.
|
The replayed messages that follow the ``SnapshotOffer`` message, if any, are younger than the offered snapshot.
|
||||||
They finally recover the processor to its current (i.e. latest) state.
|
They finally recover the persistent actor to its current (i.e. latest) state.
|
||||||
|
|
||||||
In general, a processor is only offered a snapshot if that processor has previously saved one or more snapshots
|
In general, a persistent actor is only offered a snapshot if that persistent actor has previously saved one or more snapshots
|
||||||
and at least one of these snapshots matches the ``SnapshotSelectionCriteria`` that can be specified for recovery.
|
and at least one of these snapshots matches the ``SnapshotSelectionCriteria`` that can be specified for recovery.
|
||||||
|
|
||||||
.. includecode:: code/docs/persistence/PersistenceDocTest.java#snapshot-criteria
|
.. includecode:: code/docs/persistence/PersistenceDocTest.java#snapshot-criteria
|
||||||
|
|
@ -467,168 +370,10 @@ saved snapshot matches the specified ``SnapshotSelectionCriteria`` will replay a
|
||||||
Snapshot deletion
|
Snapshot deletion
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
A processor can delete individual snapshots by calling the ``deleteSnapshot`` method with the sequence number and the
|
A persistent actor can delete individual snapshots by calling the ``deleteSnapshot`` method with the sequence number and the
|
||||||
timestamp of a snapshot as argument. To bulk-delete snapshots matching ``SnapshotSelectionCriteria``, processors should
|
timestamp of a snapshot as argument. To bulk-delete snapshots matching ``SnapshotSelectionCriteria``, persistent actors should
|
||||||
use the ``deleteSnapshots`` method.
|
use the ``deleteSnapshots`` method.
|
||||||
|
|
||||||
.. _event-sourcing-java:
|
|
||||||
|
|
||||||
Event sourcing
|
|
||||||
==============
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
The ``PersistentActor`` introduced in this section was previously known as ``EventsourcedProcessor``,
|
|
||||||
which was a subset of the ``PersistentActor``. Migrating your code to use persistent actors instead is
|
|
||||||
very simple and is explained in the :ref:`migration-guide-persistence-experimental-2.3.x-2.4.x`.
|
|
||||||
|
|
||||||
In all the examples so far, messages that change a processor's state have been sent as ``Persistent`` messages
|
|
||||||
by an application, so that they can be replayed during recovery. From this point of view, the journal acts as
|
|
||||||
a write-ahead-log for whatever ``Persistent`` messages a processor receives. This is also known as *command
|
|
||||||
sourcing*. Commands, however, may fail and some applications cannot tolerate command failures during recovery.
|
|
||||||
|
|
||||||
For these applications `Event Sourcing`_ is a better choice. Applied to Akka persistence, the basic idea behind
|
|
||||||
event sourcing is quite simple. A processor receives a (non-persistent) command which is first validated if it
|
|
||||||
can be applied to the current state. Here, validation can mean anything, from simple inspection of a command
|
|
||||||
message's fields up to a conversation with several external services, for example. If validation succeeds, events
|
|
||||||
are generated from the command, representing the effect of the command. These events are then persisted and, after
|
|
||||||
successful persistence, used to change a processor's state. When the processor needs to be recovered, only the
|
|
||||||
persisted events are replayed of which we know that they can be successfully applied. In other words, events
|
|
||||||
cannot fail when being replayed to a processor, in contrast to commands. Eventsourced processors may of course
|
|
||||||
also process commands that do not change application state, such as query commands, for example.
|
|
||||||
|
|
||||||
.. _Event Sourcing: http://martinfowler.com/eaaDev/EventSourcing.html
|
|
||||||
|
|
||||||
Akka persistence supports event sourcing with the abstract ``UntypedPersistentActor`` class (which implements
|
|
||||||
event sourcing as a pattern on top of command sourcing). A processor that extends this abstract class does not handle
|
|
||||||
``Persistent`` messages directly but uses the ``persist`` method to persist and handle events. The behavior of an
|
|
||||||
``UntypedPersistentActor`` is defined by implementing ``onReceiveRecover`` and ``onReceiveCommand``. This is
|
|
||||||
demonstrated in the following example.
|
|
||||||
|
|
||||||
.. includecode:: ../../../akka-samples/akka-sample-persistence-java/src/main/java/sample/persistence/PersistentActorExample.java#persistent-actor-example
|
|
||||||
|
|
||||||
The example defines two data types, ``Cmd`` and ``Evt`` to represent commands and events, respectively. The
|
|
||||||
``state`` of the ``ExampleProcessor`` is a list of persisted event data contained in ``ExampleState``.
|
|
||||||
|
|
||||||
The processor's ``onReceiveRecover`` method defines how ``state`` is updated during recovery by handling ``Evt``
|
|
||||||
and ``SnapshotOffer`` messages. The processor's ``onReceiveCommand`` method is a command handler. In this example,
|
|
||||||
a command is handled by generating two events which are then persisted and handled. Events are persisted by calling
|
|
||||||
``persist`` with an event (or a sequence of events) as first argument and an event handler as second argument.
|
|
||||||
|
|
||||||
The ``persist`` method persists events asynchronously and the event handler is executed for successfully persisted
|
|
||||||
events. Successfully persisted events are internally sent back to the processor as individual messages that trigger
|
|
||||||
event handler executions. An event handler may close over processor state and mutate it. The sender of a persisted
|
|
||||||
event is the sender of the corresponding command. This allows event handlers to reply to the sender of a command
|
|
||||||
(not shown).
|
|
||||||
|
|
||||||
The main responsibility of an event handler is changing processor state using event data and notifying others
|
|
||||||
about successful state changes by publishing events.
|
|
||||||
|
|
||||||
When persisting events with ``persist`` it is guaranteed that the processor will not receive further commands between
|
|
||||||
the ``persist`` call and the execution(s) of the associated event handler. This also holds for multiple ``persist``
|
|
||||||
calls in context of a single command.
|
|
||||||
|
|
||||||
The easiest way to run this example yourself is to download `Typesafe Activator <http://www.typesafe.com/platform/getstarted>`_
|
|
||||||
and open the tutorial named `Akka Persistence Samples with Java <http://www.typesafe.com/activator/template/akka-sample-persistence-java>`_.
|
|
||||||
It contains instructions on how to run the ``PersistentActorExample``.
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
It's also possible to switch between different command handlers during normal processing and recovery
|
|
||||||
with ``getContext().become()`` and ``getContext().unbecome()``. To get the actor into the same state after
|
|
||||||
recovery you need to take special care to perform the same state transitions with ``become`` and
|
|
||||||
``unbecome`` in the ``receiveRecover`` method as you would have done in the command handler.
|
|
||||||
|
|
||||||
.. _persist-async-java:
|
|
||||||
|
|
||||||
Relaxed local consistency requirements and high throughput use-cases
|
|
||||||
--------------------------------------------------------------------
|
|
||||||
|
|
||||||
If faced with Relaxed local consistency requirements and high throughput demands sometimes ``PersistentActor`` and it's
|
|
||||||
``persist`` may not be enough in terms of consuming incoming Commands at a high rate, because it has to wait until all
|
|
||||||
Events related to a given Command are processed in order to start processing the next Command. While this abstraction is
|
|
||||||
very useful for most cases, sometimes you may be faced with relaxed requirements about consistency – for example you may
|
|
||||||
want to process commands as fast as you can, assuming that Event will eventually be persisted and handled properly in
|
|
||||||
the background and retroactively reacting to persistence failures if needed.
|
|
||||||
|
|
||||||
The ``persistAsync`` method provides a tool for implementing high-throughput processors. It will *not*
|
|
||||||
stash incoming Commands while the Journal is still working on persisting and/or user code is executing event callbacks.
|
|
||||||
|
|
||||||
In the below example, the event callbacks may be called "at any time", even after the next Command has been processed.
|
|
||||||
The ordering between events is still guaranteed ("evt-b-1" will be sent after "evt-a-2", which will be sent after "evt-a-1" etc.).
|
|
||||||
|
|
||||||
.. includecode:: code/docs/persistence/PersistenceDocTest.java#persist-async
|
|
||||||
|
|
||||||
Notice that the client does not have to wrap any messages in the `Persistent` class in order to obtain "command sourcing like"
|
|
||||||
semantics. It's up to the processor to decide about persisting (or not) of messages, unlike ``Processor`` where the sender had to be aware of this decision.
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
In order to implement the pattern known as "*command sourcing*" simply ``persistAsync`` all incoming events right away,
|
|
||||||
and handle them in the callback.
|
|
||||||
|
|
||||||
.. _defer-java:
|
|
||||||
|
|
||||||
Deferring actions until preceeding persist handlers have executed
|
|
||||||
-----------------------------------------------------------------
|
|
||||||
|
|
||||||
Sometimes when working with ``persistAsync`` you may find that it would be nice to define some actions in terms of
|
|
||||||
''happens-after the previous ``persistAsync`` handlers have been invoked''. ``PersistentActor`` provides an utility method
|
|
||||||
called ``defer``, which works similarily to ``persistAsync`` yet does not persist the passed in event. It is recommended to
|
|
||||||
use it for *read* operations, and actions which do not have corresponding events in your domain model.
|
|
||||||
|
|
||||||
Using this method is very similar to the persist family of methods, yet it does **not** persist the passed in event.
|
|
||||||
It will be kept in memory and used when invoking the handler.
|
|
||||||
|
|
||||||
.. includecode:: code/docs/persistence/PersistenceDocTest.java#defer
|
|
||||||
|
|
||||||
Notice that the ``sender()`` is **safe** to access in the handler callback, and will be pointing to the original sender
|
|
||||||
of the command for which this ``defer`` handler was called.
|
|
||||||
|
|
||||||
.. includecode:: code/docs/persistence/PersistenceDocTest.java#defer-caller
|
|
||||||
|
|
||||||
|
|
||||||
Reliable event delivery
|
|
||||||
-----------------------
|
|
||||||
|
|
||||||
Sending events from an event handler to another actor has at-most-once delivery semantics. For at-least-once delivery,
|
|
||||||
:ref:`channels-java` must be used. In this case, also replayed events (received by ``receiveRecover``) must be sent to a
|
|
||||||
channel, as shown in the following example:
|
|
||||||
|
|
||||||
.. includecode:: code/docs/persistence/PersistenceDocTest.java#reliable-event-delivery
|
|
||||||
|
|
||||||
In larger integration scenarios, channel destinations may be actors that submit received events to an external
|
|
||||||
message broker, for example. After having successfully submitted an event, they should call ``confirm()`` on the
|
|
||||||
received ``ConfirmablePersistent`` message.
|
|
||||||
|
|
||||||
Batch writes
|
|
||||||
============
|
|
||||||
|
|
||||||
To optimize throughput, an ``UntypedProcessor`` internally batches received ``Persistent`` messages under high load before
|
|
||||||
writing them to the journal (as a single batch). The batch size dynamically grows from 1 under low and moderate loads
|
|
||||||
to a configurable maximum size (default is ``200``) under high load.
|
|
||||||
|
|
||||||
.. includecode:: ../scala/code/docs/persistence/PersistencePluginDocSpec.scala#max-message-batch-size
|
|
||||||
|
|
||||||
A new batch write is triggered by a processor as soon as a batch reaches the maximum size or if the journal completed
|
|
||||||
writing the previous batch. Batch writes are never timer-based which keeps latencies at a minimum.
|
|
||||||
|
|
||||||
Applications that want to have more explicit control over batch writes and batch sizes can send processors
|
|
||||||
``PersistentBatch`` messages.
|
|
||||||
|
|
||||||
.. includecode:: code/docs/persistence/PersistenceDocTest.java#batch-write
|
|
||||||
|
|
||||||
``Persistent`` messages contained in a ``PersistentBatch`` are always written atomically, even if the batch
|
|
||||||
size is greater than ``max-message-batch-size``. Also, a ``PersistentBatch`` is written isolated from other batches.
|
|
||||||
``Persistent`` messages contained in a ``PersistentBatch`` are received individually by a processor.
|
|
||||||
|
|
||||||
``PersistentBatch`` messages, for example, are used internally by an ``UntypedPersistentActor`` to ensure atomic
|
|
||||||
writes of events. All events that are persisted in context of a single command are written as a single batch to the
|
|
||||||
journal (even if ``persist`` is called multiple times per command). The recovery of an ``UntypedPersistentActor``
|
|
||||||
will therefore never be done partially (with only a subset of events persisted by a single command).
|
|
||||||
|
|
||||||
Confirmation and deletion operations performed by :ref:`channels-java` are also batched. The maximum confirmation
|
|
||||||
and deletion batch sizes are configurable with ``akka.persistence.journal.max-confirmation-batch-size`` and
|
|
||||||
``akka.persistence.journal.max-deletion-batch-size``, respectively.
|
|
||||||
|
|
||||||
Storage plugins
|
Storage plugins
|
||||||
===============
|
===============
|
||||||
|
|
||||||
|
|
@ -702,7 +447,7 @@ Shared LevelDB journal
|
||||||
----------------------
|
----------------------
|
||||||
|
|
||||||
A LevelDB instance can also be shared by multiple actor systems (on the same or on different nodes). This, for
|
A LevelDB instance can also be shared by multiple actor systems (on the same or on different nodes). This, for
|
||||||
example, allows processors to failover to a backup node and continue using the shared journal instance from the
|
example, allows persistent actors to failover to a backup node and continue using the shared journal instance from the
|
||||||
backup node.
|
backup node.
|
||||||
|
|
||||||
.. warning::
|
.. warning::
|
||||||
|
|
@ -729,7 +474,7 @@ done by calling the ``SharedLeveldbJournal.setStore`` method with the actor refe
|
||||||
|
|
||||||
.. includecode:: code/docs/persistence/PersistencePluginDocTest.java#shared-store-usage
|
.. includecode:: code/docs/persistence/PersistencePluginDocTest.java#shared-store-usage
|
||||||
|
|
||||||
Internal journal commands (sent by processors) are buffered until injection completes. Injection is idempotent
|
Internal journal commands (sent by persistent actors) are buffered until injection completes. Injection is idempotent
|
||||||
i.e. only the first injection is used.
|
i.e. only the first injection is used.
|
||||||
|
|
||||||
.. _local-snapshot-store-java:
|
.. _local-snapshot-store-java:
|
||||||
|
|
|
||||||
|
|
@ -36,14 +36,14 @@ Eventsourced and Akka Persistence are both :ref:`extending-akka-scala`.
|
||||||
**Akka Persistence:** ``Persistence`` extension
|
**Akka Persistence:** ``Persistence`` extension
|
||||||
|
|
||||||
- Must **not** be explicitly created by an application. A ``Persistence`` extension is implicitly created upon first
|
- Must **not** be explicitly created by an application. A ``Persistence`` extension is implicitly created upon first
|
||||||
processor or channel creation. Journal actors are automatically created from a journal plugin configuration (see
|
`PersistentActor`` creation. Journal actors are automatically created from a journal plugin configuration (see
|
||||||
:ref:`journal-plugin-api`).
|
:ref:`journal-plugin-api`).
|
||||||
- :ref:`processors` and :ref:`channels` can be created like any other actor with ``actorOf`` without using the
|
- ``PersistentActor`` can be created like any other actor with ``actorOf`` without using the
|
||||||
``Persistence`` extension.
|
``Persistence`` extension.
|
||||||
- Is **not** a central registry of processors and channels.
|
- Is **not** a central registry of persistent actors.
|
||||||
|
|
||||||
Processors
|
Processors / PersistentActor
|
||||||
==========
|
============================
|
||||||
|
|
||||||
**Eventsourced:** ``Eventsourced``
|
**Eventsourced:** ``Eventsourced``
|
||||||
|
|
||||||
|
|
@ -70,30 +70,22 @@ Processors
|
||||||
- Does not support batch-writes of messages to the journal.
|
- Does not support batch-writes of messages to the journal.
|
||||||
- Does not support stashing of messages.
|
- Does not support stashing of messages.
|
||||||
|
|
||||||
**Akka Persistence:** ``Processor``
|
**Akka Persistence:** ``PersistentActor``
|
||||||
|
|
||||||
- Trait that adds journaling (write-ahead-logging) to actors (see :ref:`processors`) and used by applications for
|
- Trait that adds journaling to actors (see :ref:`event-sourcing`) and used by applications for
|
||||||
*command sourcing*. Corresponds to ``Eventsourced`` processors in Eventsourced but is not a stackable trait.
|
*event sourcing* or *command sourcing*. Corresponds to ``Eventsourced`` processors in Eventsourced but is not a stackable trait.
|
||||||
- Automatically recovers on start and re-start, by default. :ref:`recovery` can be customized or turned off by
|
- Automatically recovers on start and re-start, by default. :ref:`recovery` can be customized or turned off by
|
||||||
overriding actor life cycle hooks ``preStart`` and ``preRestart``. ``Processor`` takes care that new messages
|
overriding actor life cycle hooks ``preStart`` and ``preRestart``. ``Processor`` takes care that new messages
|
||||||
never interfere with replayed messages. New messages are internally buffered until recovery completes.
|
never interfere with replayed messages. New messages are internally buffered until recovery completes.
|
||||||
- No special-purpose behavior change methods. Default behavior change methods ``context.become`` and
|
- No special-purpose behavior change methods. Default behavior change methods ``context.become`` and
|
||||||
``context.unbecome`` can be used and are journaling-preserving.
|
``context.unbecome`` can be used and are journaling-preserving.
|
||||||
- Writes messages of type ``Persistent`` to the journal (see :ref:`persistent-messages`). Corresponds to ``Message``
|
- Sender references are written to the journal. Sender references of type ``PromiseActorRef`` are
|
||||||
in Eventsourced. Sender references are written to the journal. A reply to senders must therefore be done via a
|
|
||||||
channel in order to avoid redundant replies during replay. Sender references of type ``PromiseActorRef`` are
|
|
||||||
not journaled, they are ``system.deadLetters`` on replay.
|
not journaled, they are ``system.deadLetters`` on replay.
|
||||||
- Supports :ref:`snapshots`.
|
- Supports :ref:`snapshots`.
|
||||||
- :ref:`persistence-identifiers` are of type ``String``, have a default value and can be overridden by applications.
|
- :ref:`persistence-identifiers` are of type ``String``, have a default value and can be overridden by applications.
|
||||||
- Supports :ref:`batch-writes`.
|
- Supports :ref:`batch-writes`.
|
||||||
- Supports stashing of messages.
|
- Supports stashing of messages.
|
||||||
|
|
||||||
**Akka Persistence:** ``EventsourcedProcessor``
|
|
||||||
|
|
||||||
- Extension trait and pattern on top of ``Processor`` to support :ref:`event-sourcing`. Has no direct counterpart in
|
|
||||||
Eventsourced. Can be considered as a replacement of two processors in Eventsourced where one processor processes
|
|
||||||
commands and the other processes events that have been emitted by the command processor.
|
|
||||||
|
|
||||||
Channels
|
Channels
|
||||||
========
|
========
|
||||||
|
|
||||||
|
|
@ -112,25 +104,6 @@ Channels
|
||||||
- Does not redeliver messages on missing or negative delivery confirmation.
|
- Does not redeliver messages on missing or negative delivery confirmation.
|
||||||
- Cannot be used standalone.
|
- Cannot be used standalone.
|
||||||
|
|
||||||
**Akka Persistence:** ``Channel``
|
|
||||||
|
|
||||||
- Prevents redundant delivery of messages to a destination (see :ref:`channels`) i.e. serves the same primary purpose
|
|
||||||
as in Eventsourced.
|
|
||||||
- Is not associated with a single destination. A destination can be specified with each ``Deliver`` request and is
|
|
||||||
referred to by an actor path. A destination path is resolved to the current destination incarnation during delivery
|
|
||||||
(via ``actorSelection``).
|
|
||||||
- Must not be explicitly activated. Also, a network of processors and channels automatically recover consistently,
|
|
||||||
even if they are distributed. This enhancement, together with improved processor recovery, makes recovery of complex
|
|
||||||
Akka Persistence applications trivial. No special recovery procedures must be run by applications.
|
|
||||||
- Redelivers messages on missing delivery confirmation (see :ref:`redelivery`). In contrast to Eventsourced, Akka
|
|
||||||
Persistence doesn't distinguish between missing and negative confirmations. It only has a notion of missing
|
|
||||||
confirmations using timeouts (which are closely related to negative confirmations as both trigger message
|
|
||||||
redelivery).
|
|
||||||
- Can be used standalone.
|
|
||||||
|
|
||||||
Persistent channels
|
|
||||||
===================
|
|
||||||
|
|
||||||
**Eventsourced:** ``ReliableChannel``
|
**Eventsourced:** ``ReliableChannel``
|
||||||
|
|
||||||
- Provides ``DefaultChannel`` functionality plus persistence and recovery from sender JVM crashes (see `ReliableChannel
|
- Provides ``DefaultChannel`` functionality plus persistence and recovery from sender JVM crashes (see `ReliableChannel
|
||||||
|
|
@ -142,16 +115,17 @@ Persistent channels
|
||||||
- Cannot reply on persistence.
|
- Cannot reply on persistence.
|
||||||
- Can be used standalone.
|
- Can be used standalone.
|
||||||
|
|
||||||
**Akka Persistence:** ``PersistentChannel``
|
**Akka Persistence:** ``AtLeastOnceDelivery``
|
||||||
|
|
||||||
- Provides ``Channel`` functionality plus persistence and recovery from sender JVM crashes (see
|
- ``AtLeastOnceDelivery`` trait is mixed in to a ``PersistentActor``
|
||||||
:ref:`persistent-channels`). Same message redelivery features as ``Channel``.
|
- Does not prevent redundant delivery of messages to a destination
|
||||||
- Redelivers unconfirmed messages concurrently to newly delivered messages. Flow control is done by channel using
|
- Is not associated with a single destination. A destination can be specified with each ``deliver`` request and is
|
||||||
a configurable minimum and maximum number of pending confirmations.
|
referred to by an actor path. A destination path is resolved to the current destination incarnation during delivery
|
||||||
- Optionally notifies applications about messages for which the maximum number of delivery attempts has been reached
|
(via ``actorSelection``).
|
||||||
(also offered by ``Channel``).
|
- Redelivers messages on missing delivery confirmation. In contrast to Eventsourced, Akka
|
||||||
- Can reply on persistence (= accept acknowledgement).
|
Persistence doesn't distinguish between missing and negative confirmations. It only has a notion of missing
|
||||||
- Can be used standalone.
|
confirmations using timeouts (which are closely related to negative confirmations as both trigger message
|
||||||
|
redelivery).
|
||||||
|
|
||||||
Views
|
Views
|
||||||
=====
|
=====
|
||||||
|
|
@ -162,10 +136,9 @@ Views
|
||||||
|
|
||||||
**Akka Persistence:** ``View``
|
**Akka Persistence:** ``View``
|
||||||
|
|
||||||
- Receives the message stream written by a ``Processor`` or ``EventsourcedProcessor`` by reading it directly from the
|
- Receives the message stream written by a ``PersistentActor`` by reading it directly from the
|
||||||
journal (see :ref:`views`). Alternative to using channels. Useful in situations where actors shall receive a
|
journal (see :ref:`views`). Alternative to using channels. Useful in situations where actors shall receive a
|
||||||
persistent message stream in correct order without duplicates.
|
persistent message stream in correct order without duplicates.
|
||||||
- Can be used in combination with :ref:`channels` for sending messages.
|
|
||||||
- Supports :ref:`snapshots`.
|
- Supports :ref:`snapshots`.
|
||||||
|
|
||||||
Serializers
|
Serializers
|
||||||
|
|
@ -194,7 +167,7 @@ Sequence numbers
|
||||||
|
|
||||||
**Akka Persistence:**
|
**Akka Persistence:**
|
||||||
|
|
||||||
- Generated on a per-processor basis.
|
- Generated on a per persistent actor basis.
|
||||||
|
|
||||||
Storage plugins
|
Storage plugins
|
||||||
===============
|
===============
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,7 @@ trait PersistenceDocSpec {
|
||||||
}
|
}
|
||||||
|
|
||||||
new AnyRef {
|
new AnyRef {
|
||||||
trait MyProcessor1 extends Processor {
|
trait MyProcessor1 extends PersistentActor {
|
||||||
//#recover-on-start-disabled
|
//#recover-on-start-disabled
|
||||||
override def preStart() = ()
|
override def preStart() = ()
|
||||||
//#recover-on-start-disabled
|
//#recover-on-start-disabled
|
||||||
|
|
@ -67,7 +67,7 @@ trait PersistenceDocSpec {
|
||||||
//#recover-on-restart-disabled
|
//#recover-on-restart-disabled
|
||||||
}
|
}
|
||||||
|
|
||||||
trait MyProcessor2 extends Processor {
|
trait MyProcessor2 extends PersistentActor {
|
||||||
//#recover-on-start-custom
|
//#recover-on-start-custom
|
||||||
override def preStart() {
|
override def preStart() {
|
||||||
self ! Recover(toSequenceNr = 457L)
|
self ! Recover(toSequenceNr = 457L)
|
||||||
|
|
@ -75,7 +75,7 @@ trait PersistenceDocSpec {
|
||||||
//#recover-on-start-custom
|
//#recover-on-start-custom
|
||||||
}
|
}
|
||||||
|
|
||||||
trait MyProcessor3 extends Processor {
|
trait MyProcessor3 extends PersistentActor {
|
||||||
//#deletion
|
//#deletion
|
||||||
override def preRestart(reason: Throwable, message: Option[Any]) {
|
override def preRestart(reason: Throwable, message: Option[Any]) {
|
||||||
message match {
|
message match {
|
||||||
|
|
@ -87,14 +87,16 @@ trait PersistenceDocSpec {
|
||||||
//#deletion
|
//#deletion
|
||||||
}
|
}
|
||||||
|
|
||||||
class MyProcessor4 extends Processor {
|
class MyProcessor4 extends PersistentActor {
|
||||||
//#recovery-completed
|
//#recovery-completed
|
||||||
def receive = initializing
|
|
||||||
|
|
||||||
def initializing: Receive = {
|
def receiveRecover: Receive = {
|
||||||
case RecoveryCompleted =>
|
case evt => //...
|
||||||
recoveryCompleted()
|
}
|
||||||
context.become(active)
|
|
||||||
|
def receiveCommand: Receive = {
|
||||||
|
case RecoveryCompleted => recoveryCompleted()
|
||||||
|
case msg => //...
|
||||||
}
|
}
|
||||||
|
|
||||||
def recoveryCompleted(): Unit = {
|
def recoveryCompleted(): Unit = {
|
||||||
|
|
@ -102,9 +104,6 @@ trait PersistenceDocSpec {
|
||||||
// ...
|
// ...
|
||||||
}
|
}
|
||||||
|
|
||||||
def active: Receive = {
|
|
||||||
case Persistent(msg, _) => //...
|
|
||||||
}
|
|
||||||
//#recovery-completed
|
//#recovery-completed
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ persistence is that only changes to an actor's internal state are persisted but
|
||||||
allows for very high transaction rates and efficient replication. Stateful actors are recovered by replaying stored
|
allows for very high transaction rates and efficient replication. Stateful actors are recovered by replaying stored
|
||||||
changes to these actors from which they can rebuild internal state. This can be either the full history of changes
|
changes to these actors from which they can rebuild internal state. This can be either the full history of changes
|
||||||
or starting from a snapshot which can dramatically reduce recovery times. Akka persistence also provides point-to-point
|
or starting from a snapshot which can dramatically reduce recovery times. Akka persistence also provides point-to-point
|
||||||
communication channels with at-least-once message delivery semantics.
|
communication with at-least-once message delivery semantics.
|
||||||
|
|
||||||
.. warning::
|
.. warning::
|
||||||
|
|
||||||
|
|
@ -36,76 +36,114 @@ Akka persistence is a separate jar file. Make sure that you have the following d
|
||||||
Architecture
|
Architecture
|
||||||
============
|
============
|
||||||
|
|
||||||
* *Processor* (deprecated, use *PersistentActor* instead): A processor is a persistent, stateful actor. Messages sent
|
|
||||||
to a processor are written to a journal before its ``onReceive`` method is called. When a processor is started or
|
|
||||||
restarted, journaled messages are replayed to that processor, so that it can recover internal state from these messages.
|
|
||||||
|
|
||||||
* *PersistentActor*: Is a persistent, stateful actor. It is able to persist events to a journal and can react to
|
* *PersistentActor*: Is a persistent, stateful actor. It is able to persist events to a journal and can react to
|
||||||
them in a thread-safe manner. It can be used to implement both *command* as well as *event sourced* actors.
|
them in a thread-safe manner. It can be used to implement both *command* as well as *event sourced* actors.
|
||||||
When a persistent actor is started or restarted, journaled messages are replayed to that actor, so that it can
|
When a persistent actor is started or restarted, journaled messages are replayed to that actor, so that it can
|
||||||
recover internal state from these messages.
|
recover internal state from these messages.
|
||||||
|
|
||||||
* *View*: A view is a persistent, stateful actor that receives journaled messages that have been written by another
|
* *View*: A view is a persistent, stateful actor that receives journaled messages that have been written by another
|
||||||
processor. A view itself does not journal new messages, instead, it updates internal state only from a processor's
|
persistent actor. A view itself does not journal new messages, instead, it updates internal state only from a persistent actor's
|
||||||
replicated message stream.
|
replicated message stream.
|
||||||
|
|
||||||
* *Streams*: Messages written by a processor can be published in compliance with the `Reactive Streams`_ specification.
|
* *Streams*: Messages written by a persistent actor can be published in compliance with the `Reactive Streams`_ specification.
|
||||||
Only those messages that are explicitly requested from downstream processors are actually pulled from a processor's
|
Only those messages that are explicitly requested from downstream persistent actors are actually pulled from a persistent actor's
|
||||||
journal.
|
journal.
|
||||||
|
|
||||||
* *Channel*: Channels are used by processors and views to communicate with other actors. They prevent that replayed
|
* *AtLeastOnceDelivery*: To send messages with at-least-once delivery semantics to destinations, also in
|
||||||
messages are redundantly delivered to these actors and provide at-least-once message delivery semantics, also in
|
|
||||||
case of sender and receiver JVM crashes.
|
case of sender and receiver JVM crashes.
|
||||||
|
|
||||||
* *Journal*: A journal stores the sequence of messages sent to a processor. An application can control which messages
|
* *Journal*: A journal stores the sequence of messages sent to a persistent actor. An application can control which messages
|
||||||
are journaled and which are received by the processor without being journaled. The storage backend of a journal is
|
are journaled and which are received by the persistent actor without being journaled. The storage backend of a journal is
|
||||||
pluggable. The default journal storage plugin writes to the local filesystem, replicated journals are available as
|
pluggable. The default journal storage plugin writes to the local filesystem, replicated journals are available as
|
||||||
`Community plugins`_.
|
`Community plugins`_.
|
||||||
|
|
||||||
* *Snapshot store*: A snapshot store persists snapshots of a processor's or a view's internal state. Snapshots are
|
* *Snapshot store*: A snapshot store persists snapshots of a persistent actor's or a view's internal state. Snapshots are
|
||||||
used for optimizing recovery times. The storage backend of a snapshot store is pluggable. The default snapshot
|
used for optimizing recovery times. The storage backend of a snapshot store is pluggable. The default snapshot
|
||||||
storage plugin writes to the local filesystem.
|
storage plugin writes to the local filesystem.
|
||||||
|
|
||||||
* *Event sourcing*. Based on the building blocks described above, Akka persistence provides abstractions for the
|
|
||||||
development of event sourced applications (see section :ref:`event-sourcing`)
|
|
||||||
|
|
||||||
.. _Community plugins: http://akka.io/community/
|
.. _Community plugins: http://akka.io/community/
|
||||||
.. _Reactive Streams: http://www.reactive-streams.org/
|
.. _Reactive Streams: http://www.reactive-streams.org/
|
||||||
|
|
||||||
.. _processors:
|
|
||||||
|
|
||||||
Processors
|
.. _event-sourcing:
|
||||||
==========
|
|
||||||
|
|
||||||
.. warning::
|
Event sourcing
|
||||||
``Processor`` is deprecated. Instead the current ``PersistentActor`` will be extended to provide equivalent
|
==============
|
||||||
functionality if required (by introducing the ``persistAsync`` method).
|
|
||||||
For details see `Relaxed local consistency requirements and high throughput use-cases`_ as well as the discussion
|
|
||||||
and pull requests related to this `issue on Github <https://github.com/akka/akka/issues/15230>`_.
|
|
||||||
|
|
||||||
A processor can be implemented by extending the ``Processor`` trait and implementing the ``receive`` method.
|
The basic idea behind `Event Sourcing`_ is quite simple. A persistent actor receives a (non-persistent) command
|
||||||
|
which is first validated if it can be applied to the current state. Here, validation can mean anything, from simple
|
||||||
|
inspection of a command message's fields up to a conversation with several external services, for example.
|
||||||
|
If validation succeeds, events are generated from the command, representing the effect of the command. These events
|
||||||
|
are then persisted and, after successful persistence, used to change the actor's state. When the persistent actor
|
||||||
|
needs to be recovered, only the persisted events are replayed of which we know that they can be successfully applied.
|
||||||
|
In other words, events cannot fail when being replayed to a persistent actor, in contrast to commands. Event sourced
|
||||||
|
actors may of course also process commands that do not change application state, such as query commands, for example.
|
||||||
|
|
||||||
.. includecode:: code/docs/persistence/PersistenceDocSpec.scala#definition
|
.. _Event Sourcing: http://martinfowler.com/eaaDev/EventSourcing.html
|
||||||
|
|
||||||
Processors only write messages of type ``Persistent`` to the journal, others are received without being persisted.
|
Akka persistence supports event sourcing with the ``PersistentActor`` trait. An actor that extends this trait uses the
|
||||||
When a processor's ``receive`` method is called with a ``Persistent`` message it can safely assume that this message
|
``persist`` method to persist and handle events. The behavior of a ``PersistentActor``
|
||||||
has been successfully written to the journal. If a journal fails to write a ``Persistent`` message then the processor
|
is defined by implementing ``receiveRecover`` and ``receiveCommand``. This is demonstrated in the following example.
|
||||||
is stopped, by default. If a processor should continue running on persistence failures it must handle
|
|
||||||
``PersistenceFailure`` messages. In this case, a processor may want to inform the sender about the failure,
|
|
||||||
so that the sender can re-send the message, if needed.
|
|
||||||
|
|
||||||
A ``Processor`` itself is an ``Actor`` and can therefore be instantiated with ``actorOf``.
|
.. includecode:: ../../../akka-samples/akka-sample-persistence-scala/src/main/scala/sample/persistence/PersistentActorExample.scala#persistent-actor-example
|
||||||
|
|
||||||
.. includecode:: code/docs/persistence/PersistenceDocSpec.scala#usage
|
The example defines two data types, ``Cmd`` and ``Evt`` to represent commands and events, respectively. The
|
||||||
|
``state`` of the ``ExampleProcessor`` is a list of persisted event data contained in ``ExampleState``.
|
||||||
|
|
||||||
|
The persistent actor's ``receiveRecover`` method defines how ``state`` is updated during recovery by handling ``Evt``
|
||||||
|
and ``SnapshotOffer`` messages. The persistent actor's ``receiveCommand`` method is a command handler. In this example,
|
||||||
|
a command is handled by generating two events which are then persisted and handled. Events are persisted by calling
|
||||||
|
``persist`` with an event (or a sequence of events) as first argument and an event handler as second argument.
|
||||||
|
|
||||||
|
The ``persist`` method persists events asynchronously and the event handler is executed for successfully persisted
|
||||||
|
events. Successfully persisted events are internally sent back to the persistent actor as individual messages that trigger
|
||||||
|
event handler executions. An event handler may close over persistent actor state and mutate it. The sender of a persisted
|
||||||
|
event is the sender of the corresponding command. This allows event handlers to reply to the sender of a command
|
||||||
|
(not shown).
|
||||||
|
|
||||||
|
The main responsibility of an event handler is changing persistent actor state using event data and notifying others
|
||||||
|
about successful state changes by publishing events.
|
||||||
|
|
||||||
|
When persisting events with ``persist`` it is guaranteed that the persistent actor will not receive further commands between
|
||||||
|
the ``persist`` call and the execution(s) of the associated event handler. This also holds for multiple ``persist``
|
||||||
|
calls in context of a single command.
|
||||||
|
|
||||||
|
The easiest way to run this example yourself is to download `Typesafe Activator <http://www.typesafe.com/platform/getstarted>`_
|
||||||
|
and open the tutorial named `Akka Persistence Samples with Scala <http://www.typesafe.com/activator/template/akka-sample-persistence-scala>`_.
|
||||||
|
It contains instructions on how to run the ``PersistentActorExample``.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
It's also possible to switch between different command handlers during normal processing and recovery
|
||||||
|
with ``context.become()`` and ``context.unbecome()``. To get the actor into the same state after
|
||||||
|
recovery you need to take special care to perform the same state transitions with ``become`` and
|
||||||
|
``unbecome`` in the ``receiveRecover`` method as you would have done in the command handler.
|
||||||
|
|
||||||
|
Identifiers
|
||||||
|
-----------
|
||||||
|
|
||||||
|
A persistent actor must have an identifier that doesn't change across different actor incarnations. It defaults to the
|
||||||
|
``String`` representation of persistent actor's path without the address part and can be obtained via the ``persistenceId``
|
||||||
|
method.
|
||||||
|
|
||||||
|
.. includecode:: code/docs/persistence/PersistenceDocSpec.scala#persistence-id
|
||||||
|
|
||||||
|
Applications can customize a persistent actor's id by specifying an actor name during persistent actor creation as shown in
|
||||||
|
section :ref:`event-sourcing`. This changes that persistent actor's name in its actor hierarchy and hence influences only
|
||||||
|
part of the persistent actor id. To fully customize a persistent actor's id, the ``persistenceId`` method must be overridden.
|
||||||
|
|
||||||
|
.. includecode:: code/docs/persistence/PersistenceDocSpec.scala#persistence-id-override
|
||||||
|
|
||||||
|
Overriding ``persistenceId`` is the recommended way to generate stable identifiers.
|
||||||
|
|
||||||
.. _recovery:
|
.. _recovery:
|
||||||
|
|
||||||
Recovery
|
Recovery
|
||||||
--------
|
--------
|
||||||
|
|
||||||
By default, a processor is automatically recovered on start and on restart by replaying journaled messages.
|
By default, a persistent actor is automatically recovered on start and on restart by replaying journaled messages.
|
||||||
New messages sent to a processor during recovery do not interfere with replayed messages. New messages will
|
New messages sent to a persistent actor during recovery do not interfere with replayed messages. New messages will
|
||||||
only be received by a processor after recovery completes.
|
only be received by a persistent actor after recovery completes.
|
||||||
|
|
||||||
Recovery customization
|
Recovery customization
|
||||||
^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
@ -114,7 +152,7 @@ Automated recovery on start can be disabled by overriding ``preStart`` with an e
|
||||||
|
|
||||||
.. includecode:: code/docs/persistence/PersistenceDocSpec.scala#recover-on-start-disabled
|
.. includecode:: code/docs/persistence/PersistenceDocSpec.scala#recover-on-start-disabled
|
||||||
|
|
||||||
In this case, a processor must be recovered explicitly by sending it a ``Recover()`` message.
|
In this case, a persistent actor must be recovered explicitly by sending it a ``Recover()`` message.
|
||||||
|
|
||||||
.. includecode:: code/docs/persistence/PersistenceDocSpec.scala#recover-explicit
|
.. includecode:: code/docs/persistence/PersistenceDocSpec.scala#recover-explicit
|
||||||
|
|
||||||
|
|
@ -123,7 +161,7 @@ If not overridden, ``preStart`` sends a ``Recover()`` message to ``self``. Appli
|
||||||
|
|
||||||
.. includecode:: code/docs/persistence/PersistenceDocSpec.scala#recover-on-start-custom
|
.. includecode:: code/docs/persistence/PersistenceDocSpec.scala#recover-on-start-custom
|
||||||
|
|
||||||
Upper sequence number bounds can be used to recover a processor to past state instead of current state. Automated
|
Upper sequence number bounds can be used to recover a persistent actor to past state instead of current state. Automated
|
||||||
recovery on restart can be disabled by overriding ``preRestart`` with an empty implementation.
|
recovery on restart can be disabled by overriding ``preRestart`` with an empty implementation.
|
||||||
|
|
||||||
.. includecode:: code/docs/persistence/PersistenceDocSpec.scala#recover-on-restart-disabled
|
.. includecode:: code/docs/persistence/PersistenceDocSpec.scala#recover-on-restart-disabled
|
||||||
|
|
@ -131,57 +169,99 @@ recovery on restart can be disabled by overriding ``preRestart`` with an empty i
|
||||||
Recovery status
|
Recovery status
|
||||||
^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
A processor can query its own recovery status via the methods
|
A persistent actor can query its own recovery status via the methods
|
||||||
|
|
||||||
.. includecode:: code/docs/persistence/PersistenceDocSpec.scala#recovery-status
|
.. includecode:: code/docs/persistence/PersistenceDocSpec.scala#recovery-status
|
||||||
|
|
||||||
Sometimes there is a need for performing additional initialization when the
|
Sometimes there is a need for performing additional initialization when the
|
||||||
recovery has completed, before processing any other message sent to the processor.
|
recovery has completed, before processing any other message sent to the persistent actor.
|
||||||
The processor will receive a special :class:`RecoveryCompleted` message right after recovery
|
The persistent actor will receive a special :class:`RecoveryCompleted` message right after recovery
|
||||||
and before any other received messages. If there is a problem with recovering the state of
|
and before any other received messages.
|
||||||
the actor from the journal, the actor will be sent a :class:`RecoveryFailure` message that
|
|
||||||
it can choose to handle. If the actor doesn't handle the :class:`RecoveryFailure` message it
|
If there is a problem with recovering the state of the actor from the journal, the actor will be
|
||||||
will be stopped.
|
sent a :class:`RecoveryFailure` message that it can choose to handle in ``receiveRecover``. If the
|
||||||
|
actor doesn't handle the :class:`RecoveryFailure` message it will be stopped.
|
||||||
|
|
||||||
.. includecode:: code/docs/persistence/PersistenceDocSpec.scala#recovery-completed
|
.. includecode:: code/docs/persistence/PersistenceDocSpec.scala#recovery-completed
|
||||||
|
|
||||||
|
.. _persist-async-scala:
|
||||||
|
|
||||||
.. _failure-handling:
|
Relaxed local consistency requirements and high throughput use-cases
|
||||||
|
--------------------------------------------------------------------
|
||||||
|
|
||||||
Failure handling
|
If faced with relaxed local consistency requirements and high throughput demands sometimes ``PersistentActor`` and it's
|
||||||
^^^^^^^^^^^^^^^^
|
``persist`` may not be enough in terms of consuming incoming Commands at a high rate, because it has to wait until all
|
||||||
|
Events related to a given Command are processed in order to start processing the next Command. While this abstraction is
|
||||||
|
very useful for most cases, sometimes you may be faced with relaxed requirements about consistency – for example you may
|
||||||
|
want to process commands as fast as you can, assuming that Event will eventually be persisted and handled properly in
|
||||||
|
the background and retroactively reacting to persistence failures if needed.
|
||||||
|
|
||||||
A persistent message that caused an exception will be received again by a processor after restart. To prevent
|
The ``persistAsync`` method provides a tool for implementing high-throughput persistent actors. It will *not*
|
||||||
a replay of that message during recovery it can be deleted.
|
stash incoming Commands while the Journal is still working on persisting and/or user code is executing event callbacks.
|
||||||
|
|
||||||
.. includecode:: code/docs/persistence/PersistenceDocSpec.scala#deletion
|
In the below example, the event callbacks may be called "at any time", even after the next Command has been processed.
|
||||||
|
The ordering between events is still guaranteed ("evt-b-1" will be sent after "evt-a-2", which will be sent after "evt-a-1" etc.).
|
||||||
|
|
||||||
|
.. includecode:: code/docs/persistence/PersistenceDocSpec.scala#persist-async
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
In order to implement the pattern known as "*command sourcing*" simply call ``persistAsync(cmd)(...)`` right away on all incomming
|
||||||
|
messages right away, and handle them in the callback.
|
||||||
|
|
||||||
|
.. _defer-scala:
|
||||||
|
|
||||||
|
Deferring actions until preceeding persist handlers have executed
|
||||||
|
-----------------------------------------------------------------
|
||||||
|
|
||||||
|
Sometimes when working with ``persistAsync`` you may find that it would be nice to define some actions in terms of
|
||||||
|
''happens-after the previous ``persistAsync`` handlers have been invoked''. ``PersistentActor`` provides an utility method
|
||||||
|
called ``defer``, which works similarily to ``persistAsync`` yet does not persist the passed in event. It is recommended to
|
||||||
|
use it for *read* operations, and actions which do not have corresponding events in your domain model.
|
||||||
|
|
||||||
|
Using this method is very similar to the persist family of methods, yet it does **not** persist the passed in event.
|
||||||
|
It will be kept in memory and used when invoking the handler.
|
||||||
|
|
||||||
|
.. includecode:: code/docs/persistence/PersistenceDocSpec.scala#defer
|
||||||
|
|
||||||
|
Notice that the ``sender()`` is **safe** to access in the handler callback, and will be pointing to the original sender
|
||||||
|
of the command for which this ``defer`` handler was called.
|
||||||
|
|
||||||
|
The calling side will get the responses in this (guaranteed) order:
|
||||||
|
|
||||||
|
.. includecode:: code/docs/persistence/PersistenceDocSpec.scala#defer-caller
|
||||||
|
|
||||||
|
|
||||||
|
.. _batch-writes:
|
||||||
|
|
||||||
|
Batch writes
|
||||||
|
------------
|
||||||
|
|
||||||
|
To optimize throughput, a persistent actor internally batches events to be stored under high load before
|
||||||
|
writing them to the journal (as a single batch). The batch size dynamically grows from 1 under low and moderate loads
|
||||||
|
to a configurable maximum size (default is ``200``) under high load. When using ``persistAsync`` this increases
|
||||||
|
the maximum throughput dramatically.
|
||||||
|
|
||||||
|
.. includecode:: code/docs/persistence/PersistencePluginDocSpec.scala#max-message-batch-size
|
||||||
|
|
||||||
|
A new batch write is triggered by a persistent actor as soon as a batch reaches the maximum size or if the journal completed
|
||||||
|
writing the previous batch. Batch writes are never timer-based which keeps latencies at a minimum.
|
||||||
|
|
||||||
|
The batches are also used internally to ensure atomic writes of events. All events that are persisted in context
|
||||||
|
of a single command are written as a single batch to the journal (even if ``persist`` is called multiple times per command).
|
||||||
|
The recovery of a ``PersistentActor`` will therefore never be done partially (with only a subset of events persisted by a
|
||||||
|
single command).
|
||||||
|
|
||||||
Message deletion
|
Message deletion
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
A processor can delete a single message by calling the ``deleteMessage`` method with the sequence number of
|
A persistent actor can delete a single message by calling the ``deleteMessage`` method with the sequence number of
|
||||||
that message as argument. An optional ``permanent`` parameter specifies whether the message shall be permanently
|
that message as argument. An optional ``permanent`` parameter specifies whether the message shall be permanently
|
||||||
deleted from the journal or only marked as deleted. In both cases, the message won't be replayed. Later extensions
|
deleted from the journal or only marked as deleted. In both cases, the message won't be replayed. Later extensions
|
||||||
to Akka persistence will allow to replay messages that have been marked as deleted which can be useful for debugging
|
to Akka persistence will allow to replay messages that have been marked as deleted which can be useful for debugging
|
||||||
purposes, for example. To delete all messages (journaled by a single processor) up to a specified sequence number,
|
purposes, for example. To delete all messages (journaled by a single persistent actor) up to a specified sequence number,
|
||||||
processors should call the ``deleteMessages`` method.
|
persistent actors should call the ``deleteMessages`` method.
|
||||||
|
|
||||||
Identifiers
|
|
||||||
-----------
|
|
||||||
|
|
||||||
A processor must have an identifier that doesn't change across different actor incarnations. It defaults to the
|
|
||||||
``String`` representation of processor's path without the address part and can be obtained via the ``persistenceId``
|
|
||||||
method.
|
|
||||||
|
|
||||||
.. includecode:: code/docs/persistence/PersistenceDocSpec.scala#persistence-id
|
|
||||||
|
|
||||||
Applications can customize a processor's id by specifying an actor name during processor creation as shown in
|
|
||||||
section :ref:`processors`. This changes that processor's name in its actor hierarchy and hence influences only
|
|
||||||
part of the processor id. To fully customize a processor's id, the ``persistenceId`` method must be overridden.
|
|
||||||
|
|
||||||
.. includecode:: code/docs/persistence/PersistenceDocSpec.scala#persistence-id-override
|
|
||||||
|
|
||||||
Overriding ``persistenceId`` is the recommended way to generate stable identifiers.
|
|
||||||
|
|
||||||
.. _views:
|
.. _views:
|
||||||
|
|
||||||
|
|
@ -193,9 +273,9 @@ methods.
|
||||||
|
|
||||||
.. includecode:: code/docs/persistence/PersistenceDocSpec.scala#view
|
.. includecode:: code/docs/persistence/PersistenceDocSpec.scala#view
|
||||||
|
|
||||||
The ``persistenceId`` identifies the processor from which the view receives journaled messages. It is not necessary
|
The ``persistenceId`` identifies the persistent actor from which the view receives journaled messages. It is not necessary
|
||||||
the referenced processor is actually running. Views read messages from a processor's journal directly. When a
|
the referenced persistent actor is actually running. Views read messages from a persistent actor's journal directly. When a
|
||||||
processor is started later and begins to write new messages, the corresponding view is updated automatically, by
|
persistent actor is started later and begins to write new messages, the corresponding view is updated automatically, by
|
||||||
default.
|
default.
|
||||||
|
|
||||||
Updates
|
Updates
|
||||||
|
|
@ -228,9 +308,11 @@ of replayed messages for manual updates can be limited with the ``replayMax`` pa
|
||||||
Recovery
|
Recovery
|
||||||
--------
|
--------
|
||||||
|
|
||||||
Initial recovery of views works in the very same way as for :ref:`processors` (i.e. by sending a ``Recover`` message
|
Initial recovery of views works in the very same way as for a persistent actor (i.e. by sending a ``Recover`` message
|
||||||
to self). The maximum number of replayed messages during initial recovery is determined by ``autoUpdateReplayMax``.
|
to self). The maximum number of replayed messages during initial recovery is determined by ``autoUpdateReplayMax``.
|
||||||
Further possibilities to customize initial recovery are explained in section :ref:`processors`.
|
Further possibilities to customize initial recovery are explained in section :ref:`recovery`.
|
||||||
|
|
||||||
|
.. _persistence-identifiers:
|
||||||
|
|
||||||
Identifiers
|
Identifiers
|
||||||
-----------
|
-----------
|
||||||
|
|
@ -244,7 +326,7 @@ name in its actor hierarchy and hence influences only part of the view id. To fu
|
||||||
``viewId`` method must be overridden. Overriding ``viewId`` is the recommended way to generate stable identifiers.
|
``viewId`` method must be overridden. Overriding ``viewId`` is the recommended way to generate stable identifiers.
|
||||||
|
|
||||||
The ``viewId`` must differ from the referenced ``persistenceId``, unless :ref:`snapshots` of a view and its
|
The ``viewId`` must differ from the referenced ``persistenceId``, unless :ref:`snapshots` of a view and its
|
||||||
processor shall be shared (which is what applications usually do not want).
|
persistent actor shall be shared (which is what applications usually do not want).
|
||||||
|
|
||||||
.. _streams:
|
.. _streams:
|
||||||
|
|
||||||
|
|
@ -253,7 +335,7 @@ Streams
|
||||||
|
|
||||||
**TODO: rename *producer* to *publisher*.**
|
**TODO: rename *producer* to *publisher*.**
|
||||||
|
|
||||||
A `Reactive Streams`_ ``Producer`` can be created from a processor's message stream via the ``PersistentFlow``
|
A `Reactive Streams`_ ``Producer`` can be created from a persistent actor's message stream via the ``PersistentFlow``
|
||||||
extension of the Akka Streams Scala DSL:
|
extension of the Akka Streams Scala DSL:
|
||||||
|
|
||||||
.. includecode:: code/docs/persistence/PersistenceDocSpec.scala#producer-creation
|
.. includecode:: code/docs/persistence/PersistenceDocSpec.scala#producer-creation
|
||||||
|
|
@ -262,8 +344,8 @@ The created ``flow`` object is of type ``Flow[Persistent]`` and can be composed
|
||||||
combinators (= methods defined on ``Flow``). Calling the ``toProducer`` method on ``flow`` creates a producer
|
combinators (= methods defined on ``Flow``). Calling the ``toProducer`` method on ``flow`` creates a producer
|
||||||
of type ``Producer[Persistent]``.
|
of type ``Producer[Persistent]``.
|
||||||
|
|
||||||
A persistent message producer only reads from a processor's journal when explicitly requested by downstream
|
A persistent message producer only reads from a persistent actor's journal when explicitly requested by downstream
|
||||||
consumers. In order to avoid frequent, fine grained read access to a processor's journal, the producer tries
|
consumers. In order to avoid frequent, fine grained read access to a persistent actor's journal, the producer tries
|
||||||
to buffer persistent messages in memory from which it serves downstream requests. The maximum buffer size per
|
to buffer persistent messages in memory from which it serves downstream requests. The maximum buffer size per
|
||||||
producer is configurable with a ``PersistentPublisherSettings`` configuration object.
|
producer is configurable with a ``PersistentPublisherSettings`` configuration object.
|
||||||
|
|
||||||
|
|
@ -284,205 +366,16 @@ Streams Scala DSL and its ``PersistentFlow`` extension.
|
||||||
|
|
||||||
.. includecode:: code/docs/persistence/PersistenceDocSpec.scala#producer-examples
|
.. includecode:: code/docs/persistence/PersistenceDocSpec.scala#producer-examples
|
||||||
|
|
||||||
.. _channels:
|
|
||||||
|
|
||||||
Channels
|
|
||||||
========
|
|
||||||
|
|
||||||
Channels are special actors that are used by processors or views to communicate with other actors (channel
|
|
||||||
destinations). The following discusses channels in context of processors but this is also applicable to views.
|
|
||||||
|
|
||||||
Channels prevent redundant delivery of replayed messages to destinations during processor recovery. A replayed
|
|
||||||
message is retained by a channel if its delivery has been confirmed by a destination.
|
|
||||||
|
|
||||||
.. includecode:: code/docs/persistence/PersistenceDocSpec.scala#channel-example
|
|
||||||
|
|
||||||
A channel is ready to use once it has been created, no recovery or further activation is needed. A ``Deliver``
|
|
||||||
request instructs a channel to send a ``Persistent`` message to a destination. A destination is provided as
|
|
||||||
``ActorPath`` and messages are sent by the channel via that path's ``ActorSelection``. Sender references are
|
|
||||||
preserved by a channel, therefore, a destination can reply to the sender of a ``Deliver`` request.
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
Sending via a channel has at-least-once delivery semantics—by virtue of either
|
|
||||||
the sending actor or the channel being persistent—which means that the
|
|
||||||
semantics do not match those of a normal :class:`ActorRef` send operation:
|
|
||||||
|
|
||||||
* it is not at-most-once delivery
|
|
||||||
|
|
||||||
* message order for the same sender–receiver pair is not retained due to
|
|
||||||
possible resends
|
|
||||||
|
|
||||||
* after a crash and restart of the destination messages are still
|
|
||||||
delivered—to the new actor incarnation
|
|
||||||
|
|
||||||
These semantics match precisely what an :class:`ActorPath` represents (see
|
|
||||||
:ref:`actor-lifecycle-scala`), therefore you need to supply a path and not a
|
|
||||||
reference when constructing :class:`Deliver` messages.
|
|
||||||
|
|
||||||
If a processor wants to reply to a ``Persistent`` message sender it should use the ``sender`` path as channel
|
|
||||||
destination.
|
|
||||||
|
|
||||||
.. includecode:: code/docs/persistence/PersistenceDocSpec.scala#channel-example-reply
|
|
||||||
|
|
||||||
Persistent messages delivered by a channel are of type ``ConfirmablePersistent``. ``ConfirmablePersistent`` extends
|
|
||||||
``Persistent`` by adding the methods ``confirm`` and ``redeliveries`` (see also :ref:`redelivery`). A channel
|
|
||||||
destination confirms the delivery of a ``ConfirmablePersistent`` message by calling ``confirm()`` on that message.
|
|
||||||
This asynchronously writes a confirmation entry to the journal. Replayed messages internally contain confirmation
|
|
||||||
entries which allows a channel to decide if it should retain these messages or not.
|
|
||||||
|
|
||||||
A ``Processor`` can also be used as channel destination i.e. it can persist ``ConfirmablePersistent`` messages too.
|
|
||||||
|
|
||||||
.. _redelivery:
|
|
||||||
|
|
||||||
Message re-delivery
|
|
||||||
-------------------
|
|
||||||
|
|
||||||
Channels re-deliver messages to destinations if they do not confirm delivery within a configurable timeout.
|
|
||||||
This timeout can be specified as ``redeliverInterval`` when creating a channel, optionally together with the
|
|
||||||
maximum number of re-deliveries a channel should attempt for each unconfirmed message. The number of re-delivery
|
|
||||||
attempts can be obtained via the ``redeliveries`` method on ``ConfirmablePersistent`` or by pattern matching.
|
|
||||||
|
|
||||||
.. includecode:: code/docs/persistence/PersistenceDocSpec.scala#channel-custom-settings
|
|
||||||
|
|
||||||
A channel keeps messages in memory until their successful delivery has been confirmed or the maximum number of
|
|
||||||
re-deliveries is reached. To be notified about messages that have reached the maximum number of re-deliveries,
|
|
||||||
applications can register a listener at channel creation.
|
|
||||||
|
|
||||||
.. includecode:: code/docs/persistence/PersistenceDocSpec.scala#channel-custom-listener
|
|
||||||
|
|
||||||
A listener receives ``RedeliverFailure`` notifications containing all messages that could not be delivered. On
|
|
||||||
receiving a ``RedeliverFailure`` message, an application may decide to restart the sending processor to enforce
|
|
||||||
a re-send of these messages to the channel or confirm these messages to prevent further re-sends. The sending
|
|
||||||
processor can also be restarted any time later to re-send unconfirmed messages.
|
|
||||||
|
|
||||||
This combination of
|
|
||||||
|
|
||||||
* message persistence by sending processors
|
|
||||||
* message replays by sending processors
|
|
||||||
* message re-deliveries by channels and
|
|
||||||
* application-level confirmations (acknowledgements) by destinations
|
|
||||||
|
|
||||||
enables channels to provide at-least-once message delivery semantics. Possible duplicates can be detected by
|
|
||||||
destinations by tracking message sequence numbers. Message sequence numbers are generated per sending processor.
|
|
||||||
Depending on how a processor routes outbound messages to destinations, they may either see a contiguous message
|
|
||||||
sequence or a sequence with gaps.
|
|
||||||
|
|
||||||
.. warning::
|
|
||||||
|
|
||||||
If a processor emits more than one outbound message per inbound ``Persistent`` message it **must** use a
|
|
||||||
separate channel for each outbound message to ensure that confirmations are uniquely identifiable, otherwise,
|
|
||||||
at-least-once message delivery semantics do not apply. This rule has been introduced to avoid writing additional
|
|
||||||
outbound message identifiers to the journal which would decrease the overall throughput. It is furthermore
|
|
||||||
recommended to collapse multiple outbound messages to the same destination into a single outbound message,
|
|
||||||
otherwise, if sent via multiple channels, their ordering is not defined.
|
|
||||||
|
|
||||||
If an application wants to have more control how sequence numbers are assigned to messages it should use an
|
|
||||||
application-specific sequence number generator and include the generated sequence numbers into the ``payload``
|
|
||||||
of ``Persistent`` messages.
|
|
||||||
|
|
||||||
.. _persistent-channels:
|
|
||||||
|
|
||||||
Persistent channels
|
|
||||||
-------------------
|
|
||||||
|
|
||||||
Channels created with ``Channel.props`` do not persist messages. These channels are usually used in combination
|
|
||||||
with a sending processor that takes care of persistence, hence, channel-specific persistence is not necessary in
|
|
||||||
this case. They are referred to as transient channels in the following.
|
|
||||||
|
|
||||||
Persistent channels are like transient channels but additionally persist messages before delivering them. Messages
|
|
||||||
that have been persisted by a persistent channel are deleted when destinations confirm their delivery. A persistent
|
|
||||||
channel can be created with ``PersistentChannel.props`` and configured with a ``PersistentChannelSettings`` object.
|
|
||||||
|
|
||||||
.. includecode:: code/docs/persistence/PersistenceDocSpec.scala#persistent-channel-example
|
|
||||||
|
|
||||||
A persistent channel is useful for delivery of messages to slow destinations or destinations that are unavailable
|
|
||||||
for a long time. It can constrain the number of pending confirmations based on the ``pendingConfirmationsMax``
|
|
||||||
and ``pendingConfirmationsMin`` parameters of ``PersistentChannelSettings``.
|
|
||||||
|
|
||||||
.. includecode:: code/docs/persistence/PersistenceDocSpec.scala#persistent-channel-watermarks
|
|
||||||
|
|
||||||
It suspends delivery when the number of pending confirmations reaches ``pendingConfirmationsMax`` and resumes
|
|
||||||
delivery again when this number falls below ``pendingConfirmationsMin``. This prevents both, flooding destinations
|
|
||||||
with more messages than they can process and unlimited memory consumption by the channel. A persistent channel
|
|
||||||
continues to persist new messages even when message delivery is temporarily suspended.
|
|
||||||
|
|
||||||
Standalone usage
|
|
||||||
----------------
|
|
||||||
|
|
||||||
Applications may also use channels standalone. Transient channels can be used standalone if re-delivery attempts
|
|
||||||
to destinations are required but message loss in case of a sender JVM crash is not an issue. If message loss in
|
|
||||||
case of a sender JVM crash is an issue, persistent channels should be used. In this case, applications may want to
|
|
||||||
receive replies from the channel whether messages have been successfully persisted or not. This can be enabled by
|
|
||||||
creating the channel with the ``replyPersistent`` configuration parameter set to ``true``:
|
|
||||||
|
|
||||||
.. includecode:: code/docs/persistence/PersistenceDocSpec.scala#persistent-channel-reply
|
|
||||||
|
|
||||||
With this setting, either the successfully persisted message is replied to the sender or a ``PersistenceFailure``
|
|
||||||
message. In case the latter case, the sender should re-send the message.
|
|
||||||
|
|
||||||
.. _persistence-identifiers:
|
|
||||||
|
|
||||||
Identifiers
|
|
||||||
-----------
|
|
||||||
|
|
||||||
In the same way as :ref:`processors` and :ref:`views`, channels also have an identifier that defaults to a channel's
|
|
||||||
path. A channel identifier can therefore be customized by using a custom actor name at channel creation. This changes
|
|
||||||
that channel's name in its actor hierarchy and hence influences only part of the channel identifier. To fully customize
|
|
||||||
a channel identifier, it should be provided as argument ``Channel.props(String)`` or ``PersistentChannel.props(String)``
|
|
||||||
(recommended to generate stable identifiers).
|
|
||||||
|
|
||||||
.. includecode:: code/docs/persistence/PersistenceDocSpec.scala#channel-id-override
|
|
||||||
|
|
||||||
.. _persistent-messages:
|
|
||||||
|
|
||||||
Persistent messages
|
|
||||||
===================
|
|
||||||
|
|
||||||
Payload
|
|
||||||
-------
|
|
||||||
|
|
||||||
The payload of a ``Persistent`` message can be obtained via its
|
|
||||||
|
|
||||||
.. includecode:: ../../../akka-persistence/src/main/scala/akka/persistence/Persistent.scala#payload
|
|
||||||
|
|
||||||
method or by pattern matching
|
|
||||||
|
|
||||||
.. includecode:: code/docs/persistence/PersistenceDocSpec.scala#payload-pattern-matching
|
|
||||||
|
|
||||||
Inside processors, new persistent messages are derived from the current persistent message before sending them via a
|
|
||||||
channel, either by calling ``p.withPayload(...)`` or ``Persistent(...)`` where the latter uses the
|
|
||||||
implicit ``currentPersistentMessage`` made available by ``Processor``.
|
|
||||||
|
|
||||||
.. includecode:: code/docs/persistence/PersistenceDocSpec.scala#current-message
|
|
||||||
|
|
||||||
This is necessary for delivery confirmations to work properly. Both ways are equivalent but we recommend
|
|
||||||
using ``p.withPayload(...)`` for clarity.
|
|
||||||
|
|
||||||
Sequence number
|
|
||||||
---------------
|
|
||||||
|
|
||||||
The sequence number of a ``Persistent`` message can be obtained via its
|
|
||||||
|
|
||||||
.. includecode:: ../../../akka-persistence/src/main/scala/akka/persistence/Persistent.scala#sequence-nr
|
|
||||||
|
|
||||||
method or by pattern matching
|
|
||||||
|
|
||||||
.. includecode:: code/docs/persistence/PersistenceDocSpec.scala#sequence-nr-pattern-matching
|
|
||||||
|
|
||||||
Persistent messages are assigned sequence numbers on a per-processor basis (or per channel basis if used
|
|
||||||
standalone). A sequence starts at ``1L`` and doesn't contain gaps unless a processor deletes messages.
|
|
||||||
|
|
||||||
.. _snapshots:
|
.. _snapshots:
|
||||||
|
|
||||||
Snapshots
|
Snapshots
|
||||||
=========
|
=========
|
||||||
|
|
||||||
Snapshots can dramatically reduce recovery times of processors and views. The following discusses snapshots
|
Snapshots can dramatically reduce recovery times of persistent actors and views. The following discusses snapshots
|
||||||
in context of processors but this is also applicable to views.
|
in context of persistent actors but this is also applicable to views.
|
||||||
|
|
||||||
Processors can save snapshots of internal state by calling the ``saveSnapshot`` method. If saving of a snapshot
|
Persistent actors can save snapshots of internal state by calling the ``saveSnapshot`` method. If saving of a snapshot
|
||||||
succeeds, the processor receives a ``SaveSnapshotSuccess`` message, otherwise a ``SaveSnapshotFailure`` message
|
succeeds, the persistent actor receives a ``SaveSnapshotSuccess`` message, otherwise a ``SaveSnapshotFailure`` message
|
||||||
|
|
||||||
.. includecode:: code/docs/persistence/PersistenceDocSpec.scala#save-snapshot
|
.. includecode:: code/docs/persistence/PersistenceDocSpec.scala#save-snapshot
|
||||||
|
|
||||||
|
|
@ -490,15 +383,15 @@ where ``metadata`` is of type ``SnapshotMetadata``:
|
||||||
|
|
||||||
.. includecode:: ../../../akka-persistence/src/main/scala/akka/persistence/Snapshot.scala#snapshot-metadata
|
.. includecode:: ../../../akka-persistence/src/main/scala/akka/persistence/Snapshot.scala#snapshot-metadata
|
||||||
|
|
||||||
During recovery, the processor is offered a previously saved snapshot via a ``SnapshotOffer`` message from
|
During recovery, the persistent actor is offered a previously saved snapshot via a ``SnapshotOffer`` message from
|
||||||
which it can initialize internal state.
|
which it can initialize internal state.
|
||||||
|
|
||||||
.. includecode:: code/docs/persistence/PersistenceDocSpec.scala#snapshot-offer
|
.. includecode:: code/docs/persistence/PersistenceDocSpec.scala#snapshot-offer
|
||||||
|
|
||||||
The replayed messages that follow the ``SnapshotOffer`` message, if any, are younger than the offered snapshot.
|
The replayed messages that follow the ``SnapshotOffer`` message, if any, are younger than the offered snapshot.
|
||||||
They finally recover the processor to its current (i.e. latest) state.
|
They finally recover the persistent actor to its current (i.e. latest) state.
|
||||||
|
|
||||||
In general, a processor is only offered a snapshot if that processor has previously saved one or more snapshots
|
In general, a persistent actor is only offered a snapshot if that persistent actor has previously saved one or more snapshots
|
||||||
and at least one of these snapshots matches the ``SnapshotSelectionCriteria`` that can be specified for recovery.
|
and at least one of these snapshots matches the ``SnapshotSelectionCriteria`` that can be specified for recovery.
|
||||||
|
|
||||||
.. includecode:: code/docs/persistence/PersistenceDocSpec.scala#snapshot-criteria
|
.. includecode:: code/docs/persistence/PersistenceDocSpec.scala#snapshot-criteria
|
||||||
|
|
@ -510,169 +403,10 @@ saved snapshot matches the specified ``SnapshotSelectionCriteria`` will replay a
|
||||||
Snapshot deletion
|
Snapshot deletion
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
A processor can delete individual snapshots by calling the ``deleteSnapshot`` method with the sequence number and the
|
A persistent actor can delete individual snapshots by calling the ``deleteSnapshot`` method with the sequence number and the
|
||||||
timestamp of a snapshot as argument. To bulk-delete snapshots matching ``SnapshotSelectionCriteria``, processors should
|
timestamp of a snapshot as argument. To bulk-delete snapshots matching ``SnapshotSelectionCriteria``, persistent actors should
|
||||||
use the ``deleteSnapshots`` method.
|
use the ``deleteSnapshots`` method.
|
||||||
|
|
||||||
.. _event-sourcing:
|
|
||||||
|
|
||||||
Event sourcing
|
|
||||||
==============
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
The ``PersistentActor`` introduced in this section was previously known as ``EventsourcedProcessor``
|
|
||||||
which was a subset of the ``PersistentActor``. Migrating your code to use persistent actors instead is
|
|
||||||
very simple and is explained in the :ref:`migration-guide-persistence-experimental-2.3.x-2.4.x`.
|
|
||||||
|
|
||||||
In all the examples so far, messages that change a processor's state have been sent as ``Persistent`` messages
|
|
||||||
by an application, so that they can be replayed during recovery. From this point of view, the journal acts as
|
|
||||||
a write-ahead-log for whatever ``Persistent`` messages a processor receives. This is also known as *command
|
|
||||||
sourcing*. Commands, however, may fail and some applications cannot tolerate command failures during recovery.
|
|
||||||
|
|
||||||
For these applications `Event Sourcing`_ is a better choice. Applied to Akka persistence, the basic idea behind
|
|
||||||
event sourcing is quite simple. A processor receives a (non-persistent) command which is first validated if it
|
|
||||||
can be applied to the current state. Here, validation can mean anything, from simple inspection of a command
|
|
||||||
message's fields up to a conversation with several external services, for example. If validation succeeds, events
|
|
||||||
are generated from the command, representing the effect of the command. These events are then persisted and, after
|
|
||||||
successful persistence, used to change a processor's state. When the processor needs to be recovered, only the
|
|
||||||
persisted events are replayed of which we know that they can be successfully applied. In other words, events
|
|
||||||
cannot fail when being replayed to a processor, in contrast to commands. Eventsourced processors may of course
|
|
||||||
also process commands that do not change application state, such as query commands, for example.
|
|
||||||
|
|
||||||
.. _Event Sourcing: http://martinfowler.com/eaaDev/EventSourcing.html
|
|
||||||
|
|
||||||
Akka persistence supports event sourcing with the ``PersistentActor`` trait (which implements event sourcing
|
|
||||||
as a pattern on top of command sourcing). A processor that extends this trait does not handle ``Persistent`` messages
|
|
||||||
directly but uses the ``persist`` method to persist and handle events. The behavior of an ``PersistentActor``
|
|
||||||
is defined by implementing ``receiveRecover`` and ``receiveCommand``. This is demonstrated in the following example.
|
|
||||||
|
|
||||||
.. includecode:: ../../../akka-samples/akka-sample-persistence-scala/src/main/scala/sample/persistence/PersistentActorExample.scala#persistent-actor-example
|
|
||||||
|
|
||||||
The example defines two data types, ``Cmd`` and ``Evt`` to represent commands and events, respectively. The
|
|
||||||
``state`` of the ``ExampleProcessor`` is a list of persisted event data contained in ``ExampleState``.
|
|
||||||
|
|
||||||
The processor's ``receiveRecover`` method defines how ``state`` is updated during recovery by handling ``Evt``
|
|
||||||
and ``SnapshotOffer`` messages. The processor's ``receiveCommand`` method is a command handler. In this example,
|
|
||||||
a command is handled by generating two events which are then persisted and handled. Events are persisted by calling
|
|
||||||
``persist`` with an event (or a sequence of events) as first argument and an event handler as second argument.
|
|
||||||
|
|
||||||
The ``persist`` method persists events asynchronously and the event handler is executed for successfully persisted
|
|
||||||
events. Successfully persisted events are internally sent back to the processor as individual messages that trigger
|
|
||||||
event handler executions. An event handler may close over processor state and mutate it. The sender of a persisted
|
|
||||||
event is the sender of the corresponding command. This allows event handlers to reply to the sender of a command
|
|
||||||
(not shown).
|
|
||||||
|
|
||||||
The main responsibility of an event handler is changing processor state using event data and notifying others
|
|
||||||
about successful state changes by publishing events.
|
|
||||||
|
|
||||||
When persisting events with ``persist`` it is guaranteed that the processor will not receive further commands between
|
|
||||||
the ``persist`` call and the execution(s) of the associated event handler. This also holds for multiple ``persist``
|
|
||||||
calls in context of a single command.
|
|
||||||
|
|
||||||
The easiest way to run this example yourself is to download `Typesafe Activator <http://www.typesafe.com/platform/getstarted>`_
|
|
||||||
and open the tutorial named `Akka Persistence Samples with Scala <http://www.typesafe.com/activator/template/akka-sample-persistence-scala>`_.
|
|
||||||
It contains instructions on how to run the ``PersistentActorExample``.
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
It's also possible to switch between different command handlers during normal processing and recovery
|
|
||||||
with ``context.become()`` and ``context.unbecome()``. To get the actor into the same state after
|
|
||||||
recovery you need to take special care to perform the same state transitions with ``become`` and
|
|
||||||
``unbecome`` in the ``receiveRecover`` method as you would have done in the command handler.
|
|
||||||
|
|
||||||
.. _persist-async-scala:
|
|
||||||
|
|
||||||
Relaxed local consistency requirements and high throughput use-cases
|
|
||||||
--------------------------------------------------------------------
|
|
||||||
|
|
||||||
If faced with Relaxed local consistency requirements and high throughput demands sometimes ``PersistentActor`` and it's
|
|
||||||
``persist`` may not be enough in terms of consuming incoming Commands at a high rate, because it has to wait until all
|
|
||||||
Events related to a given Command are processed in order to start processing the next Command. While this abstraction is
|
|
||||||
very useful for most cases, sometimes you may be faced with relaxed requirements about consistency – for example you may
|
|
||||||
want to process commands as fast as you can, assuming that Event will eventually be persisted and handled properly in
|
|
||||||
the background and retroactively reacting to persistence failures if needed.
|
|
||||||
|
|
||||||
The ``persistAsync`` method provides a tool for implementing high-throughput processors. It will *not*
|
|
||||||
stash incoming Commands while the Journal is still working on persisting and/or user code is executing event callbacks.
|
|
||||||
|
|
||||||
In the below example, the event callbacks may be called "at any time", even after the next Command has been processed.
|
|
||||||
The ordering between events is still guaranteed ("evt-b-1" will be sent after "evt-a-2", which will be sent after "evt-a-1" etc.).
|
|
||||||
|
|
||||||
.. includecode:: code/docs/persistence/PersistenceDocSpec.scala#persist-async
|
|
||||||
|
|
||||||
Notice that the client does not have to wrap any messages in the `Persistent` class in order to obtain "command sourcing like"
|
|
||||||
semantics. It's up to the processor to decide about persisting (or not) of messages, unlike ``Processor`` where the sender had to be aware of this decision.
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
In order to implement the "*command sourcing*" simply call ``persistAsync(cmd)(...)`` right away on all incomming
|
|
||||||
messages right away, and handle them in the callback.
|
|
||||||
|
|
||||||
.. _defer-scala:
|
|
||||||
|
|
||||||
Deferring actions until preceeding persist handlers have executed
|
|
||||||
-----------------------------------------------------------------
|
|
||||||
|
|
||||||
Sometimes when working with ``persistAsync`` you may find that it would be nice to define some actions in terms of
|
|
||||||
''happens-after the previous ``persistAsync`` handlers have been invoked''. ``PersistentActor`` provides an utility method
|
|
||||||
called ``defer``, which works similarily to ``persistAsync`` yet does not persist the passed in event. It is recommended to
|
|
||||||
use it for *read* operations, and actions which do not have corresponding events in your domain model.
|
|
||||||
|
|
||||||
Using this method is very similar to the persist family of methods, yet it does **not** persist the passed in event.
|
|
||||||
It will be kept in memory and used when invoking the handler.
|
|
||||||
|
|
||||||
.. includecode:: code/docs/persistence/PersistenceDocSpec.scala#defer
|
|
||||||
|
|
||||||
Notice that the ``sender()`` is **safe** to access in the handler callback, and will be pointing to the original sender
|
|
||||||
of the command for which this ``defer`` handler was called.
|
|
||||||
|
|
||||||
The calling side will get the responses in this (guaranteed) order:
|
|
||||||
|
|
||||||
.. includecode:: code/docs/persistence/PersistenceDocSpec.scala#defer-caller
|
|
||||||
|
|
||||||
Reliable event delivery
|
|
||||||
-----------------------
|
|
||||||
|
|
||||||
Sending events from an event handler to another actor has at-most-once delivery semantics. For at-least-once delivery,
|
|
||||||
:ref:`channels` must be used. In this case, also replayed events (received by ``receiveRecover``) must be sent to a
|
|
||||||
channel, as shown in the following example:
|
|
||||||
|
|
||||||
.. includecode:: code/docs/persistence/PersistenceDocSpec.scala#reliable-event-delivery
|
|
||||||
|
|
||||||
In larger integration scenarios, channel destinations may be actors that submit received events to an external
|
|
||||||
message broker, for example. After having successfully submitted an event, they should call ``confirm()`` on the
|
|
||||||
received ``ConfirmablePersistent`` message.
|
|
||||||
|
|
||||||
.. _batch-writes:
|
|
||||||
|
|
||||||
Batch writes
|
|
||||||
============
|
|
||||||
|
|
||||||
To optimize throughput, a ``Processor`` internally batches received ``Persistent`` messages under high load before
|
|
||||||
writing them to the journal (as a single batch). The batch size dynamically grows from 1 under low and moderate loads
|
|
||||||
to a configurable maximum size (default is ``200``) under high load.
|
|
||||||
|
|
||||||
.. includecode:: code/docs/persistence/PersistencePluginDocSpec.scala#max-message-batch-size
|
|
||||||
|
|
||||||
A new batch write is triggered by a processor as soon as a batch reaches the maximum size or if the journal completed
|
|
||||||
writing the previous batch. Batch writes are never timer-based which keeps latencies at a minimum.
|
|
||||||
|
|
||||||
Applications that want to have more explicit control over batch writes and batch sizes can send processors
|
|
||||||
``PersistentBatch`` messages.
|
|
||||||
|
|
||||||
.. includecode:: code/docs/persistence/PersistenceDocSpec.scala#batch-write
|
|
||||||
|
|
||||||
``Persistent`` messages contained in a ``PersistentBatch`` are always written atomically, even if the batch
|
|
||||||
size is greater than ``max-message-batch-size``. Also, a ``PersistentBatch`` is written isolated from other batches.
|
|
||||||
``Persistent`` messages contained in a ``PersistentBatch`` are received individually by a processor.
|
|
||||||
|
|
||||||
``PersistentBatch`` messages, for example, are used internally by an ``PersistentActor`` to ensure atomic
|
|
||||||
writes of events. All events that are persisted in context of a single command are written as a single batch to the
|
|
||||||
journal (even if ``persist`` is called multiple times per command). The recovery of an ``PersistentActor``
|
|
||||||
will therefore never be done partially (with only a subset of events persisted by a single command).
|
|
||||||
|
|
||||||
Confirmation and deletion operations performed by :ref:`channels` are also batched. The maximum confirmation
|
|
||||||
and deletion batch sizes are configurable with ``akka.persistence.journal.max-confirmation-batch-size`` and
|
|
||||||
``akka.persistence.journal.max-deletion-batch-size``, respectively.
|
|
||||||
|
|
||||||
.. _storage-plugins:
|
.. _storage-plugins:
|
||||||
|
|
||||||
|
|
@ -754,7 +488,7 @@ Shared LevelDB journal
|
||||||
----------------------
|
----------------------
|
||||||
|
|
||||||
A LevelDB instance can also be shared by multiple actor systems (on the same or on different nodes). This, for
|
A LevelDB instance can also be shared by multiple actor systems (on the same or on different nodes). This, for
|
||||||
example, allows processors to failover to a backup node and continue using the shared journal instance from the
|
example, allows persistent actors to failover to a backup node and continue using the shared journal instance from the
|
||||||
backup node.
|
backup node.
|
||||||
|
|
||||||
.. warning::
|
.. warning::
|
||||||
|
|
@ -781,7 +515,7 @@ done by calling the ``SharedLeveldbJournal.setStore`` method with the actor refe
|
||||||
|
|
||||||
.. includecode:: code/docs/persistence/PersistencePluginDocSpec.scala#shared-store-usage
|
.. includecode:: code/docs/persistence/PersistencePluginDocSpec.scala#shared-store-usage
|
||||||
|
|
||||||
Internal journal commands (sent by processors) are buffered until injection completes. Injection is idempotent
|
Internal journal commands (sent by persistent actors) are buffered until injection completes. Injection is idempotent
|
||||||
i.e. only the first injection is used.
|
i.e. only the first injection is used.
|
||||||
|
|
||||||
.. _local-snapshot-store:
|
.. _local-snapshot-store:
|
||||||
|
|
@ -832,7 +566,7 @@ Miscellaneous
|
||||||
State machines
|
State machines
|
||||||
--------------
|
--------------
|
||||||
|
|
||||||
State machines can be persisted by mixing in the ``FSM`` trait into processors.
|
State machines can be persisted by mixing in the ``FSM`` trait into persistent actors.
|
||||||
|
|
||||||
.. includecode:: code/docs/persistence/PersistenceDocSpec.scala#fsm-example
|
.. includecode:: code/docs/persistence/PersistenceDocSpec.scala#fsm-example
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -542,6 +542,7 @@ abstract class UntypedEventsourcedProcessor extends UntypedProcessor with Events
|
||||||
*
|
*
|
||||||
* @see [[Recover]]
|
* @see [[Recover]]
|
||||||
*/
|
*/
|
||||||
|
@throws(classOf[Exception])
|
||||||
def onReceiveRecover(msg: Any): Unit
|
def onReceiveRecover(msg: Any): Unit
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -552,6 +553,7 @@ abstract class UntypedEventsourcedProcessor extends UntypedProcessor with Events
|
||||||
* [[ResequenceableBatch]] messages. In this case an `UnsupportedOperationException` is
|
* [[ResequenceableBatch]] messages. In this case an `UnsupportedOperationException` is
|
||||||
* thrown by the processor.
|
* thrown by the processor.
|
||||||
*/
|
*/
|
||||||
|
@throws(classOf[Exception])
|
||||||
def onReceiveCommand(msg: Any): Unit
|
def onReceiveCommand(msg: Any): Unit
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -90,7 +90,7 @@ trait Processor extends Actor with Recovery {
|
||||||
case ReplayedMessage(p) ⇒ processPersistent(receive, p) // can occur after unstash from user stash
|
case ReplayedMessage(p) ⇒ processPersistent(receive, p) // can occur after unstash from user stash
|
||||||
case WriteMessageSuccess(p: PersistentRepr) ⇒ processPersistent(receive, p)
|
case WriteMessageSuccess(p: PersistentRepr) ⇒ processPersistent(receive, p)
|
||||||
case WriteMessageSuccess(r: Resequenceable) ⇒ process(receive, r)
|
case WriteMessageSuccess(r: Resequenceable) ⇒ process(receive, r)
|
||||||
case WriteMessageFailure(p, cause) ⇒
|
case WriteMessageFailure(p, cause) ⇒
|
||||||
process(receive, PersistenceFailure(p.payload, p.sequenceNr, cause))
|
process(receive, PersistenceFailure(p.payload, p.sequenceNr, cause))
|
||||||
case LoopMessageSuccess(m) ⇒ process(receive, m)
|
case LoopMessageSuccess(m) ⇒ process(receive, m)
|
||||||
case WriteMessagesSuccessful | WriteMessagesFailed(_) ⇒
|
case WriteMessagesSuccessful | WriteMessagesFailed(_) ⇒
|
||||||
|
|
@ -156,7 +156,7 @@ trait Processor extends Actor with Recovery {
|
||||||
*/
|
*/
|
||||||
private def onRecoveryCompleted(receive: Receive): Unit =
|
private def onRecoveryCompleted(receive: Receive): Unit =
|
||||||
receive.applyOrElse(RecoveryCompleted, unhandled)
|
receive.applyOrElse(RecoveryCompleted, unhandled)
|
||||||
|
|
||||||
private val _persistenceId = extension.persistenceId(self)
|
private val _persistenceId = extension.persistenceId(self)
|
||||||
|
|
||||||
private var processorBatch = Vector.empty[Resequenceable]
|
private var processorBatch = Vector.empty[Resequenceable]
|
||||||
|
|
|
||||||
|
|
@ -136,27 +136,31 @@ public class LambdaPersistenceDocTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
//#recovery-completed
|
//#recovery-completed
|
||||||
class MyProcessor5 extends AbstractProcessor {
|
class MyPersistentActor5 extends AbstractPersistentActor {
|
||||||
|
|
||||||
public MyProcessor5() {
|
@Override public PartialFunction<Object, BoxedUnit> receiveRecover() {
|
||||||
receive(ReceiveBuilder.
|
return ReceiveBuilder.
|
||||||
match(RecoveryCompleted.class, r -> {
|
match(String.class, this::handleEvent).build();
|
||||||
recoveryCompleted();
|
|
||||||
getContext().become(active);
|
|
||||||
}).
|
|
||||||
build()
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override public PartialFunction<Object, BoxedUnit> receiveCommand() {
|
||||||
|
return ReceiveBuilder.
|
||||||
|
match(RecoveryCompleted.class, r -> {
|
||||||
|
recoveryCompleted();
|
||||||
|
}).
|
||||||
|
match(String.class, s -> s.equals("cmd"),
|
||||||
|
s -> persist("evt", this::handleEvent)).build();
|
||||||
|
}
|
||||||
|
|
||||||
private void recoveryCompleted() {
|
private void recoveryCompleted() {
|
||||||
// perform init after recovery, before any other messages
|
// perform init after recovery, before any other messages
|
||||||
// ...
|
// ...
|
||||||
}
|
}
|
||||||
|
|
||||||
PartialFunction<Object, BoxedUnit> active =
|
private void handleEvent(String event) {
|
||||||
ReceiveBuilder.
|
// update state
|
||||||
match(Persistent.class, message -> {/* ... */}).
|
// ...
|
||||||
build();
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
//#recovery-completed
|
//#recovery-completed
|
||||||
|
|
|
||||||
|
|
@ -1,76 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
|
|
||||||
*/
|
|
||||||
|
|
||||||
package sample.persistence;
|
|
||||||
|
|
||||||
import akka.actor.ActorRef;
|
|
||||||
import akka.actor.ActorSystem;
|
|
||||||
import akka.actor.Props;
|
|
||||||
import akka.japi.pf.ReceiveBuilder;
|
|
||||||
import akka.persistence.*;
|
|
||||||
import scala.PartialFunction;
|
|
||||||
import scala.runtime.BoxedUnit;
|
|
||||||
|
|
||||||
public class ConversationRecoveryExample {
|
|
||||||
public static String PING = "PING";
|
|
||||||
public static String PONG = "PONG";
|
|
||||||
|
|
||||||
public static class Ping extends AbstractProcessor {
|
|
||||||
final ActorRef pongChannel = context().actorOf(Channel.props(), "pongChannel");
|
|
||||||
int counter = 0;
|
|
||||||
|
|
||||||
public Ping() {
|
|
||||||
receive(ReceiveBuilder.
|
|
||||||
match(ConfirmablePersistent.class, cp -> cp.payload().equals(PING), cp -> {
|
|
||||||
counter += 1;
|
|
||||||
System.out.println(String.format("received ping %d times", counter));
|
|
||||||
cp.confirm();
|
|
||||||
if (!recoveryRunning()) {
|
|
||||||
try {
|
|
||||||
Thread.sleep(1000);
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pongChannel.tell(Deliver.create(cp.withPayload(PONG), sender().path()), self());
|
|
||||||
}).
|
|
||||||
match(String.class,
|
|
||||||
s -> s.equals("init"),
|
|
||||||
s -> pongChannel.tell(Deliver.create(Persistent.create(PONG), sender().path()), self())).build()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Pong extends AbstractProcessor {
|
|
||||||
private final ActorRef pingChannel = context().actorOf(Channel.props(), "pingChannel");
|
|
||||||
private int counter = 0;
|
|
||||||
|
|
||||||
public Pong() {
|
|
||||||
receive(ReceiveBuilder.
|
|
||||||
match(ConfirmablePersistent.class, cp -> cp.payload().equals(PONG), cp -> {
|
|
||||||
counter += 1;
|
|
||||||
System.out.println(String.format("received pong %d times", counter));
|
|
||||||
cp.confirm();
|
|
||||||
if (!recoveryRunning()) {
|
|
||||||
try {
|
|
||||||
Thread.sleep(1000);
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pingChannel.tell(Deliver.create(cp.withPayload(PING), sender().path()), self());
|
|
||||||
}).build()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void main(String... args) throws Exception {
|
|
||||||
final ActorSystem system = ActorSystem.create("example");
|
|
||||||
|
|
||||||
final ActorRef ping = system.actorOf(Props.create(Ping.class), "ping");
|
|
||||||
final ActorRef pong = system.actorOf(Props.create(Pong.class), "pong");
|
|
||||||
|
|
||||||
ping.tell("init", pong);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -73,43 +73,46 @@ class ExampleState implements Serializable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ExampleProcessor extends AbstractPersistentActor {
|
class ExamplePersistentActor extends AbstractPersistentActor {
|
||||||
private ExampleState state = new ExampleState();
|
private ExampleState state = new ExampleState();
|
||||||
|
|
||||||
public int getNumEvents() {
|
public int getNumEvents() {
|
||||||
return state.size();
|
return state.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PartialFunction<Object, BoxedUnit> receiveRecover() {
|
public PartialFunction<Object, BoxedUnit> receiveRecover() {
|
||||||
return ReceiveBuilder.
|
return ReceiveBuilder.
|
||||||
match(Evt.class, state::update).
|
match(Evt.class, state::update).
|
||||||
match(SnapshotOffer.class, ss -> state = (ExampleState) ss.snapshot()).build();
|
match(SnapshotOffer.class, ss -> state = (ExampleState) ss.snapshot()).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PartialFunction<Object, BoxedUnit> receiveCommand() {
|
public PartialFunction<Object, BoxedUnit> receiveCommand() {
|
||||||
return ReceiveBuilder.match(Cmd.class, c -> {
|
return ReceiveBuilder.
|
||||||
final String data = c.getData();
|
match(Cmd.class, c -> {
|
||||||
final Evt evt1 = new Evt(data + "-" + getNumEvents());
|
final String data = c.getData();
|
||||||
final Evt evt2 = new Evt(data + "-" + (getNumEvents() + 1));
|
final Evt evt1 = new Evt(data + "-" + getNumEvents());
|
||||||
persist(asList(evt1, evt2), (Evt evt) -> {
|
final Evt evt2 = new Evt(data + "-" + (getNumEvents() + 1));
|
||||||
state.update(evt);
|
persist(asList(evt1, evt2), (Evt evt) -> {
|
||||||
if (evt.equals(evt2)) {
|
state.update(evt);
|
||||||
context().system().eventStream().publish(evt);
|
if (evt.equals(evt2)) {
|
||||||
}
|
context().system().eventStream().publish(evt);
|
||||||
});
|
}
|
||||||
}).
|
});
|
||||||
|
}).
|
||||||
match(String.class, s -> s.equals("snap"), s -> saveSnapshot(state.copy())).
|
match(String.class, s -> s.equals("snap"), s -> saveSnapshot(state.copy())).
|
||||||
match(String.class, s -> s.equals("print"), s -> System.out.println(state)).build();
|
match(String.class, s -> s.equals("print"), s -> System.out.println(state)).
|
||||||
|
build();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
//#persistent-actor-example
|
//#persistent-actor-example
|
||||||
|
|
||||||
public class PersistentActorExample {
|
public class PersistentActorExample {
|
||||||
public static void main(String... args) throws Exception {
|
public static void main(String... args) throws Exception {
|
||||||
final ActorSystem system = ActorSystem.create("example");
|
final ActorSystem system = ActorSystem.create("example");
|
||||||
final ActorRef processor = system.actorOf(Props.create(ExampleProcessor.class), "processor-4-java8");
|
final ActorRef processor = system.actorOf(Props.create(ExamplePersistentActor.class), "processor-4-java8");
|
||||||
processor.tell(new Cmd("foo"), null);
|
processor.tell(new Cmd("foo"), null);
|
||||||
processor.tell(new Cmd("baz"), null);
|
processor.tell(new Cmd("baz"), null);
|
||||||
processor.tell(new Cmd("bar"), null);
|
processor.tell(new Cmd("bar"), null);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,74 @@
|
||||||
|
/**
|
||||||
|
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
package sample.persistence;
|
||||||
|
|
||||||
|
import akka.actor.ActorRef;
|
||||||
|
import akka.actor.ActorSystem;
|
||||||
|
import akka.actor.Props;
|
||||||
|
import akka.japi.pf.ReceiveBuilder;
|
||||||
|
import akka.persistence.AbstractPersistentActor;
|
||||||
|
import scala.PartialFunction;
|
||||||
|
import scala.runtime.BoxedUnit;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
public class PersistentActorFailureExample {
|
||||||
|
public static class ExamplePersistentActor extends AbstractPersistentActor {
|
||||||
|
private ArrayList<Object> received = new ArrayList<Object>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PartialFunction<Object, BoxedUnit> receiveCommand() {
|
||||||
|
return ReceiveBuilder.
|
||||||
|
match(String.class, s -> s.equals("boom"), s -> {throw new RuntimeException("boom");}).
|
||||||
|
match(String.class, s -> s.equals("print"), s -> System.out.println("received " + received)).
|
||||||
|
match(String.class, s -> {
|
||||||
|
persist(s, evt -> {
|
||||||
|
received.add(evt);
|
||||||
|
});
|
||||||
|
}).
|
||||||
|
build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PartialFunction<Object, BoxedUnit> receiveRecover() {
|
||||||
|
return ReceiveBuilder.
|
||||||
|
match(String.class, s -> received.add(s)).
|
||||||
|
build();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String... args) throws Exception {
|
||||||
|
final ActorSystem system = ActorSystem.create("example");
|
||||||
|
final ActorRef persistentActor = system.actorOf(Props.create(ExamplePersistentActor.class), "persistentActor-2");
|
||||||
|
|
||||||
|
persistentActor.tell("a", null);
|
||||||
|
persistentActor.tell("print", null);
|
||||||
|
persistentActor.tell("boom", null);
|
||||||
|
persistentActor.tell("print", null);
|
||||||
|
persistentActor.tell("b", null);
|
||||||
|
persistentActor.tell("print", null);
|
||||||
|
persistentActor.tell("c", null);
|
||||||
|
persistentActor.tell("print", null);
|
||||||
|
|
||||||
|
// Will print in a first run (i.e. with empty journal):
|
||||||
|
|
||||||
|
// received [a]
|
||||||
|
// received [a, b]
|
||||||
|
// received [a, b, c]
|
||||||
|
|
||||||
|
// Will print in a second run:
|
||||||
|
|
||||||
|
// received [a, b, c, a]
|
||||||
|
// received [a, b, c, a, b]
|
||||||
|
// received [a, b, c, a, b, c]
|
||||||
|
|
||||||
|
// etc ...
|
||||||
|
|
||||||
|
Thread.sleep(1000);
|
||||||
|
system.shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -14,11 +14,11 @@ import scala.PartialFunction;
|
||||||
import scala.runtime.BoxedUnit;
|
import scala.runtime.BoxedUnit;
|
||||||
|
|
||||||
public class ProcessorChannelExample {
|
public class ProcessorChannelExample {
|
||||||
public static class ExampleProcessor extends AbstractProcessor {
|
public static class ExamplePersistentActor extends AbstractProcessor {
|
||||||
private ActorRef destination;
|
private ActorRef destination;
|
||||||
private ActorRef channel;
|
private ActorRef channel;
|
||||||
|
|
||||||
public ExampleProcessor(ActorRef destination) {
|
public ExamplePersistentActor(ActorRef destination) {
|
||||||
this.destination = destination;
|
this.destination = destination;
|
||||||
this.channel = context().actorOf(Channel.props(), "channel");
|
this.channel = context().actorOf(Channel.props(), "channel");
|
||||||
|
|
||||||
|
|
@ -47,7 +47,7 @@ public class ProcessorChannelExample {
|
||||||
public static void main(String... args) throws Exception {
|
public static void main(String... args) throws Exception {
|
||||||
final ActorSystem system = ActorSystem.create("example");
|
final ActorSystem system = ActorSystem.create("example");
|
||||||
final ActorRef destination = system.actorOf(Props.create(ExampleDestination.class));
|
final ActorRef destination = system.actorOf(Props.create(ExampleDestination.class));
|
||||||
final ActorRef processor = system.actorOf(Props.create(ExampleProcessor.class, destination), "processor-1");
|
final ActorRef processor = system.actorOf(Props.create(ExamplePersistentActor.class, destination), "processor-1");
|
||||||
|
|
||||||
processor.tell(Persistent.create("a"), null);
|
processor.tell(Persistent.create("a"), null);
|
||||||
processor.tell(Persistent.create("b"), null);
|
processor.tell(Persistent.create("b"), null);
|
||||||
|
|
|
||||||
|
|
@ -1,71 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
|
|
||||||
*/
|
|
||||||
|
|
||||||
package sample.persistence;
|
|
||||||
|
|
||||||
import akka.actor.ActorRef;
|
|
||||||
import akka.actor.ActorSystem;
|
|
||||||
import akka.actor.Props;
|
|
||||||
import akka.japi.pf.ReceiveBuilder;
|
|
||||||
import akka.persistence.AbstractProcessor;
|
|
||||||
import akka.persistence.Persistent;
|
|
||||||
import scala.Option;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
|
|
||||||
public class ProcessorFailureExample {
|
|
||||||
public static class ExampleProcessor extends AbstractProcessor {
|
|
||||||
private ArrayList<Object> received = new ArrayList<Object>();
|
|
||||||
|
|
||||||
public ExampleProcessor() {
|
|
||||||
receive(ReceiveBuilder.
|
|
||||||
match(Persistent.class, p -> p.payload().equals("boom"), p -> {throw new RuntimeException("boom");}).
|
|
||||||
match(Persistent.class, p -> !p.payload().equals("boom"), p -> received.add(p.payload())).
|
|
||||||
match(String.class, s -> s.equals("boom"), s -> {throw new RuntimeException("boom");}).
|
|
||||||
match(String.class, s -> s.equals("print"), s -> System.out.println("received " + received)).build()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void preRestart(Throwable reason, Option<Object> message) {
|
|
||||||
if (message.isDefined() && message.get() instanceof Persistent) {
|
|
||||||
deleteMessage(((Persistent) message.get()).sequenceNr(), false);
|
|
||||||
}
|
|
||||||
super.preRestart(reason, message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void main(String... args) throws Exception {
|
|
||||||
final ActorSystem system = ActorSystem.create("example");
|
|
||||||
final ActorRef processor = system.actorOf(Props.create(ExampleProcessor.class), "processor-2");
|
|
||||||
|
|
||||||
processor.tell(Persistent.create("a"), null);
|
|
||||||
processor.tell("print", null);
|
|
||||||
processor.tell("boom", null);
|
|
||||||
processor.tell("print", null);
|
|
||||||
processor.tell(Persistent.create("b"), null);
|
|
||||||
processor.tell("print", null);
|
|
||||||
processor.tell(Persistent.create("boom"), null);
|
|
||||||
processor.tell("print", null);
|
|
||||||
processor.tell(Persistent.create("c"), null);
|
|
||||||
processor.tell("print", null);
|
|
||||||
|
|
||||||
// Will print in a first run (i.e. with empty journal):
|
|
||||||
|
|
||||||
// received [a]
|
|
||||||
// received [a, b]
|
|
||||||
// received [a, b, c]
|
|
||||||
|
|
||||||
// Will print in a second run:
|
|
||||||
|
|
||||||
// received [a, b, c, a]
|
|
||||||
// received [a, b, c, a, b]
|
|
||||||
// received [a, b, c, a, b, c]
|
|
||||||
|
|
||||||
// etc ...
|
|
||||||
|
|
||||||
Thread.sleep(1000);
|
|
||||||
system.shutdown();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -8,7 +8,7 @@ import akka.actor.ActorRef;
|
||||||
import akka.actor.ActorSystem;
|
import akka.actor.ActorSystem;
|
||||||
import akka.actor.Props;
|
import akka.actor.Props;
|
||||||
import akka.japi.pf.ReceiveBuilder;
|
import akka.japi.pf.ReceiveBuilder;
|
||||||
import akka.persistence.AbstractProcessor;
|
import akka.persistence.AbstractPersistentActor;
|
||||||
import akka.persistence.Persistent;
|
import akka.persistence.Persistent;
|
||||||
import akka.persistence.SnapshotOffer;
|
import akka.persistence.SnapshotOffer;
|
||||||
import scala.PartialFunction;
|
import scala.PartialFunction;
|
||||||
|
|
@ -43,36 +43,47 @@ public class SnapshotExample {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class ExampleProcessor extends AbstractProcessor {
|
public static class ExamplePersistentActor extends AbstractPersistentActor {
|
||||||
private ExampleState state = new ExampleState();
|
private ExampleState state = new ExampleState();
|
||||||
|
|
||||||
public ExampleProcessor() {
|
@Override
|
||||||
receive(ReceiveBuilder.
|
public PartialFunction<Object, BoxedUnit> receiveCommand() {
|
||||||
match(Persistent.class, p -> state.update(String.format("%s-%d", p.payload(), p.sequenceNr()))).
|
return ReceiveBuilder.
|
||||||
match(SnapshotOffer.class, s -> {
|
|
||||||
ExampleState exState = (ExampleState) s.snapshot();
|
|
||||||
System.out.println("offered state = " + exState);
|
|
||||||
state = exState;
|
|
||||||
}).
|
|
||||||
match(String.class, s -> s.equals("print"), s -> System.out.println("current state = " + state)).
|
match(String.class, s -> s.equals("print"), s -> System.out.println("current state = " + state)).
|
||||||
match(String.class, s -> s.equals("snap"), s ->
|
match(String.class, s -> s.equals("snap"), s ->
|
||||||
// IMPORTANT: create a copy of snapshot
|
// IMPORTANT: create a copy of snapshot
|
||||||
// because ExampleState is mutable !!!
|
// because ExampleState is mutable !!!
|
||||||
saveSnapshot(state.copy())).build()
|
saveSnapshot(state.copy())).
|
||||||
);
|
match(String.class, s -> {
|
||||||
|
persist(s, evt -> {
|
||||||
|
state.update(evt);
|
||||||
|
});
|
||||||
|
}).
|
||||||
|
build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PartialFunction<Object, BoxedUnit> receiveRecover() {
|
||||||
|
return ReceiveBuilder.
|
||||||
|
match(String.class, evt -> state.update(evt)).
|
||||||
|
match(SnapshotOffer.class, ss -> {
|
||||||
|
System.out.println("offered state = " + ss);
|
||||||
|
state = (ExampleState) ss.snapshot();
|
||||||
|
}).
|
||||||
|
build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void main(String... args) throws Exception {
|
public static void main(String... args) throws Exception {
|
||||||
final ActorSystem system = ActorSystem.create("example");
|
final ActorSystem system = ActorSystem.create("example");
|
||||||
final ActorRef processor = system.actorOf(Props.create(ExampleProcessor.class), "processor-3-java");
|
final ActorRef persistentActor = system.actorOf(Props.create(ExamplePersistentActor.class), "persistentActor-3-java");
|
||||||
|
|
||||||
processor.tell(Persistent.create("a"), null);
|
persistentActor.tell("a", null);
|
||||||
processor.tell(Persistent.create("b"), null);
|
persistentActor.tell("b", null);
|
||||||
processor.tell("snap", null);
|
persistentActor.tell("snap", null);
|
||||||
processor.tell(Persistent.create("c"), null);
|
persistentActor.tell("c", null);
|
||||||
processor.tell(Persistent.create("d"), null);
|
persistentActor.tell("d", null);
|
||||||
processor.tell("print", null);
|
persistentActor.tell("print", null);
|
||||||
|
|
||||||
Thread.sleep(1000);
|
Thread.sleep(1000);
|
||||||
system.shutdown();
|
system.shutdown();
|
||||||
|
|
|
||||||
|
|
@ -17,25 +17,35 @@ import scala.runtime.BoxedUnit;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
public class ViewExample {
|
public class ViewExample {
|
||||||
public static class ExampleProcessor extends AbstractProcessor {
|
public static class ExamplePersistentActor extends AbstractPersistentActor {
|
||||||
|
private int count = 1;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String processorId() {
|
public String persistenceId() {
|
||||||
return "processor-5";
|
return "persistentActor-5";
|
||||||
}
|
}
|
||||||
|
|
||||||
public ExampleProcessor() {
|
@Override
|
||||||
receive(ReceiveBuilder.
|
public PartialFunction<Object, BoxedUnit> receiveCommand() {
|
||||||
match(Persistent.class,
|
return ReceiveBuilder.
|
||||||
p -> System.out.println(String.format("processor received %s (sequence nr = %d)",
|
match(String.class, s -> {
|
||||||
p.payload(),
|
System.out.println(String.format("persistentActor received %s (nr = %d)", s, count));
|
||||||
p.sequenceNr()))).build()
|
persist(s + count, evt -> {
|
||||||
);
|
count += 1;
|
||||||
|
});
|
||||||
|
}).
|
||||||
|
build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PartialFunction<Object, BoxedUnit> receiveRecover() {
|
||||||
|
return ReceiveBuilder.
|
||||||
|
match(String.class, s -> count += 1).
|
||||||
|
build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class ExampleView extends AbstractView {
|
public static class ExampleView extends AbstractView {
|
||||||
private final ActorRef destination = context().actorOf(Props.create(ExampleDestination.class));
|
|
||||||
private final ActorRef channel = context().actorOf(Channel.props("channel"));
|
|
||||||
|
|
||||||
private int numReplicated = 0;
|
private int numReplicated = 0;
|
||||||
|
|
||||||
|
|
@ -46,19 +56,16 @@ public class ViewExample {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String persistenceId() {
|
public String persistenceId() {
|
||||||
return "processor-5";
|
return "persistentActor-5";
|
||||||
}
|
}
|
||||||
|
|
||||||
public ExampleView() {
|
public ExampleView() {
|
||||||
receive(ReceiveBuilder.
|
receive(ReceiveBuilder.
|
||||||
match(Persistent.class, p -> {
|
match(Persistent.class, p -> {
|
||||||
numReplicated += 1;
|
numReplicated += 1;
|
||||||
System.out.println(String.format("view received %s (sequence nr = %d, num replicated = %d)",
|
System.out.println(String.format("view received %s (num replicated = %d)",
|
||||||
p.payload(),
|
p.payload(),
|
||||||
p.sequenceNr(),
|
|
||||||
numReplicated));
|
numReplicated));
|
||||||
channel.tell(Deliver.create(p.withPayload("replicated-" + p.payload()), destination.path()),
|
|
||||||
self());
|
|
||||||
}).
|
}).
|
||||||
match(SnapshotOffer.class, so -> {
|
match(SnapshotOffer.class, so -> {
|
||||||
numReplicated = (Integer) so.snapshot();
|
numReplicated = (Integer) so.snapshot();
|
||||||
|
|
@ -71,30 +78,16 @@ public class ViewExample {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class ExampleDestination extends AbstractActor {
|
|
||||||
|
|
||||||
public ExampleDestination() {
|
|
||||||
receive(ReceiveBuilder.
|
|
||||||
match(ConfirmablePersistent.class, cp -> {
|
|
||||||
System.out.println(String.format("destination received %s (sequence nr = %s)",
|
|
||||||
cp.payload(),
|
|
||||||
cp.sequenceNr()));
|
|
||||||
cp.confirm();
|
|
||||||
}).build()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void main(String... args) throws Exception {
|
public static void main(String... args) throws Exception {
|
||||||
final ActorSystem system = ActorSystem.create("example");
|
final ActorSystem system = ActorSystem.create("example");
|
||||||
final ActorRef processor = system.actorOf(Props.create(ExampleProcessor.class));
|
final ActorRef persistentActor = system.actorOf(Props.create(ExamplePersistentActor.class));
|
||||||
final ActorRef view = system.actorOf(Props.create(ExampleView.class));
|
final ActorRef view = system.actorOf(Props.create(ExampleView.class));
|
||||||
|
|
||||||
system.scheduler()
|
system.scheduler()
|
||||||
.schedule(Duration.Zero(),
|
.schedule(Duration.Zero(),
|
||||||
Duration.create(2, TimeUnit.SECONDS),
|
Duration.create(2, TimeUnit.SECONDS),
|
||||||
processor,
|
persistentActor,
|
||||||
Persistent.create("scheduled"),
|
"scheduled",
|
||||||
system.dispatcher(),
|
system.dispatcher(),
|
||||||
null);
|
null);
|
||||||
system.scheduler()
|
system.scheduler()
|
||||||
|
|
|
||||||
|
|
@ -12,12 +12,10 @@ This tutorial contains examples that illustrate a subset of
|
||||||
<a href="http://doc.akka.io/docs/akka/2.4-SNAPSHOT/java/lambda-persistence.html" target="_blank">Akka Persistence</a> features.
|
<a href="http://doc.akka.io/docs/akka/2.4-SNAPSHOT/java/lambda-persistence.html" target="_blank">Akka Persistence</a> features.
|
||||||
</p>
|
</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li>Processors and channels</li>
|
<li>persistent actor</li>
|
||||||
<li>Processsor snapshots</li>
|
<li>persistent actor snapshots</li>
|
||||||
<li>Eventsourced processors</li>
|
<li>persistent actor recovery</li>
|
||||||
<li>Processor failure handling</li>
|
<li>persistent actor views</li>
|
||||||
<li>Processor views</li>
|
|
||||||
<li>Processor conversation recovery</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
|
|
@ -27,47 +25,13 @@ Custom storage locations for the journal and snapshots can be defined in
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h2>Processors and channels</h2>
|
<h2>Persistent actor</h2>
|
||||||
<p>
|
<p>
|
||||||
<a href="#code/src/main/java/sample/persistence/ProcessorChannelExample.java" class="shortcut">ProcessorChannelExample.java</a>
|
<a href="#code/src/main/java/sample/persistence/PersistentActorExample.java" class="shortcut">PersistentActorExample.java</a>
|
||||||
defines an <code>ExampleProcessor</code> and an <code>ExampleDestination</code>. The processor sends messages to a
|
is described in detail in the <a href="http://doc.akka.io/docs/akka/2.3-SNAPSHOT/java/lambda-persistence.html#event-sourcing-java-lambda" target="_blank">Event sourcing</a>
|
||||||
destination via a channel. The destination confirms the delivery of these messages so that replayed messages aren't
|
section of the user documentation. With every application run, the <code>ExamplePersistentActor</code> is recovered from
|
||||||
redundantly delivered to the destination. Repeated runs of the application demonstrates that the processor receives
|
|
||||||
both replayed and new messages whereas the channel only receives new messages, sent by the application. The processor
|
|
||||||
also receives replies from the destination, demonstrating that a channel preserves sender references.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
To run this example, go to the <a href="#run" class="shortcut">Run</a> tab, and run the application main class
|
|
||||||
<b><code>sample.persistence.ProcessorChannelExample</code></b> several times.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<h2>Processor snapshots</h2>
|
|
||||||
<p>
|
|
||||||
<a href="#code/src/main/java/sample/persistence/SnapshotExample.java" class="shortcut">SnapshotExample.java</a>
|
|
||||||
demonstrates how processors can take snapshots of application state and recover from previously stored snapshots.
|
|
||||||
Snapshots are offered to processors at the beginning of recovery, before any messages (younger than the snapshot)
|
|
||||||
are replayed.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
To run this example, go to the <a href="#run" class="shortcut">Run</a> tab, and run the application main class
|
|
||||||
<b><code>sample.persistence.SnapshotExample</code></b> several times. With every run, the state offered by the
|
|
||||||
most recent snapshot is printed to <code>stdout</code>, followed by the updated state after sending new persistent
|
|
||||||
messages to the processor.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<h2>Eventsourced processors</h2>
|
|
||||||
<p>
|
|
||||||
<a href="#code/src/main/java/sample/persistence/EventsourcedExample.java" class="shortcut">EventsourcedExample.java</a>
|
|
||||||
is described in detail in the <a href="http://doc.akka.io/docs/akka/2.4-SNAPSHOT/java/lambda-persistence.html#event-sourcing" target="_blank">Event sourcing</a>
|
|
||||||
section of the user documentation. With every application run, the <code>ExampleProcessor</code> is recovered from
|
|
||||||
events stored in previous application runs, processes new commands, stores new events and snapshots and prints the
|
events stored in previous application runs, processes new commands, stores new events and snapshots and prints the
|
||||||
current processor state to <code>stdout</code>.
|
current persistent actor state to <code>stdout</code>.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
|
|
@ -77,60 +41,48 @@ To run this example, go to the <a href="#run" class="shortcut">Run</a> tab, and
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h2>Processor failure handling</h2>
|
<h2>Persistent actor snapshots</h2>
|
||||||
<p>
|
<p>
|
||||||
<a href="#code/src/main/java/sample/persistence/ProcessorFailureExample.java" class="shortcut">ProcessorFailureExample.java</a>
|
<a href="#code/src/main/java/sample/persistence/SnapshotExample.java" class="shortcut">SnapshotExample.java</a>
|
||||||
shows how a processor can delete persistent messages from the journal if they threw an exception. Throwing an exception
|
demonstrates how persistent actors can take snapshots of application state and recover from previously stored snapshots.
|
||||||
restarts the processor and replays messages. In order to prevent that the message that caused the exception is replayed,
|
Snapshots are offered to persistent actors at the beginning of recovery, before any messages (younger than the snapshot)
|
||||||
it is marked as deleted in the journal (during invocation of <code>preRestart</code>). This is a common pattern in
|
are replayed.
|
||||||
command-sourcing to compensate write-ahead logging of messages.
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
To run this example, go to the <a href="#run" class="shortcut">Run</a> tab, and run the application main class
|
To run this example, go to the <a href="#run" class="shortcut">Run</a> tab, and run the application main class
|
||||||
<b><code>sample.persistence.ProcessorFailureExample</code></b> several times.
|
<b><code>sample.persistence.SnapshotExample</code></b> several times. With every run, the state offered by the
|
||||||
</p>
|
most recent snapshot is printed to <code>stdout</code>, followed by the updated state after sending new persistent
|
||||||
|
messages to the persistent actor.
|
||||||
<p>
|
|
||||||
<a href="http://doc.akka.io/docs/akka/2.4-SNAPSHOT/java/lambda-persistence.html#event-sourcing" target="_blank">Event sourcing</a>
|
|
||||||
on the other hand, does not persist commands directly but rather events that have been derived from received commands
|
|
||||||
(not shown here). These events are known to be successfully applicable to current processor state i.e. there's
|
|
||||||
no need for deleting them from the journal. Event sourced processors usually have a lower throughput than command
|
|
||||||
sourced processors, as the maximum size of a write batch is limited by the number of persisted events per received
|
|
||||||
command.
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h2>Processor views</h2>
|
<h2>Persistent actor recovery</h2>
|
||||||
|
<p>
|
||||||
|
<a href="#code/src/main/java/sample/persistence/PersistentActorFailureExample.java" class="shortcut">PersistentActorFailureExample.java</a>
|
||||||
|
shows how a persistent actor can throw an exception, restart and restore the state by replaying the events.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
To run this example, go to the <a href="#run" class="shortcut">Run</a> tab, and run the application main class
|
||||||
|
<b><code>sample.persistence.PersistentActorFailureExample</code></b> several times.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h2>Persistent actor views</h2>
|
||||||
<p>
|
<p>
|
||||||
<a href="#code/src/main/java/sample/persistence/ViewExample.java" class="shortcut">ViewExample.java</a> demonstrates
|
<a href="#code/src/main/java/sample/persistence/ViewExample.java" class="shortcut">ViewExample.java</a> demonstrates
|
||||||
how a view (<code>ExampleView</code>) is updated with the persistent message stream of a processor
|
how a view (<code>ExampleView</code>) is updated with the persistent message stream of a persistent actor
|
||||||
(<code>ExampleProcessor</code>). Messages sent to the processor are read from <code>stdin</code>. Views also support
|
(<code>ExamplePersistentActor</code>). Messages sent to the persistent actor are scheduled periodically. Views also support
|
||||||
snapshotting and can be used in combination with channels in the same way as processors.
|
snapshotting to reduce recovery time.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
To run this example, go to the <a href="#run" class="shortcut">Run</a> tab, and run the application main class
|
To run this example, go to the <a href="#run" class="shortcut">Run</a> tab, and run the application main class
|
||||||
<b><code>sample.persistence.ViewExample</code></b>.
|
<b><code>sample.persistence.ViewExample</code></b>.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
|
||||||
Views can also receive events that have been persisted by event sourced processors (not shown).
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<h2>Processor conversation recovery</h2>
|
|
||||||
<p>
|
|
||||||
<a href="#code/src/main/java/sample/persistence/ConversationRecoveryExample.java" class="shortcut">ConversationRecoveryExample.java</a>
|
|
||||||
defines two processors that send messages to each other via channels. The reliable delivery properties of channels,
|
|
||||||
in combination with processors, allow these processors to automatically resume their conversation after a JVM crash.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
To run this example, go to the <a href="#run" class="shortcut">Run</a> tab, and run the application main class
|
|
||||||
<b><code>sample.persistence.ConversationRecoveryExample</code></b> several times.
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|
|
||||||
|
|
@ -1,58 +0,0 @@
|
||||||
package sample.persistence;
|
|
||||||
|
|
||||||
import akka.actor.*;
|
|
||||||
import akka.persistence.*;
|
|
||||||
|
|
||||||
public class ConversationRecoveryExample {
|
|
||||||
public static String PING = "PING";
|
|
||||||
public static String PONG = "PONG";
|
|
||||||
|
|
||||||
public static class Ping extends UntypedProcessor {
|
|
||||||
private final ActorRef pongChannel = getContext().actorOf(Channel.props(), "pongChannel");
|
|
||||||
private int counter = 0;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onReceive(Object message) throws Exception {
|
|
||||||
if (message instanceof ConfirmablePersistent) {
|
|
||||||
ConfirmablePersistent msg = (ConfirmablePersistent)message;
|
|
||||||
if (msg.payload().equals(PING)) {
|
|
||||||
counter += 1;
|
|
||||||
System.out.println(String.format("received ping %d times", counter));
|
|
||||||
msg.confirm();
|
|
||||||
if (!recoveryRunning()) Thread.sleep(1000);
|
|
||||||
pongChannel.tell(Deliver.create(msg.withPayload(PONG), getSender().path()), getSelf());
|
|
||||||
}
|
|
||||||
} else if (message.equals("init") && counter == 0) {
|
|
||||||
pongChannel.tell(Deliver.create(Persistent.create(PONG), getSender().path()), getSelf());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Pong extends UntypedProcessor {
|
|
||||||
private final ActorRef pingChannel = getContext().actorOf(Channel.props(), "pingChannel");
|
|
||||||
private int counter = 0;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onReceive(Object message) throws Exception {
|
|
||||||
if (message instanceof ConfirmablePersistent) {
|
|
||||||
ConfirmablePersistent msg = (ConfirmablePersistent)message;
|
|
||||||
if (msg.payload().equals(PONG)) {
|
|
||||||
counter += 1;
|
|
||||||
System.out.println(String.format("received pong %d times", counter));
|
|
||||||
msg.confirm();
|
|
||||||
if (!recoveryRunning()) Thread.sleep(1000);
|
|
||||||
pingChannel.tell(Deliver.create(msg.withPayload(PING), getSender().path()), getSelf());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void main(String... args) throws Exception {
|
|
||||||
final ActorSystem system = ActorSystem.create("example");
|
|
||||||
|
|
||||||
final ActorRef ping = system.actorOf(Props.create(Ping.class), "ping");
|
|
||||||
final ActorRef pong = system.actorOf(Props.create(Pong.class), "pong");
|
|
||||||
|
|
||||||
ping.tell("init", pong);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -10,7 +10,6 @@ import akka.persistence.UntypedPersistentActor;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
import static java.util.Arrays.asList;
|
import static java.util.Arrays.asList;
|
||||||
|
|
||||||
class Cmd implements Serializable {
|
class Cmd implements Serializable {
|
||||||
|
|
@ -66,21 +65,25 @@ class ExampleState implements Serializable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ExampleProcessor extends UntypedPersistentActor {
|
class ExamplePersistentActor extends UntypedPersistentActor {
|
||||||
private ExampleState state = new ExampleState();
|
private ExampleState state = new ExampleState();
|
||||||
|
|
||||||
public int getNumEvents() {
|
public int getNumEvents() {
|
||||||
return state.size();
|
return state.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void onReceiveRecover(Object msg) {
|
public void onReceiveRecover(Object msg) {
|
||||||
if (msg instanceof Evt) {
|
if (msg instanceof Evt) {
|
||||||
state.update((Evt) msg);
|
state.update((Evt) msg);
|
||||||
} else if (msg instanceof SnapshotOffer) {
|
} else if (msg instanceof SnapshotOffer) {
|
||||||
state = (ExampleState)((SnapshotOffer)msg).snapshot();
|
state = (ExampleState)((SnapshotOffer)msg).snapshot();
|
||||||
|
} else {
|
||||||
|
unhandled(msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void onReceiveCommand(Object msg) {
|
public void onReceiveCommand(Object msg) {
|
||||||
if (msg instanceof Cmd) {
|
if (msg instanceof Cmd) {
|
||||||
final String data = ((Cmd)msg).getData();
|
final String data = ((Cmd)msg).getData();
|
||||||
|
|
@ -100,6 +103,8 @@ class ExampleProcessor extends UntypedPersistentActor {
|
||||||
saveSnapshot(state.copy());
|
saveSnapshot(state.copy());
|
||||||
} else if (msg.equals("print")) {
|
} else if (msg.equals("print")) {
|
||||||
System.out.println(state);
|
System.out.println(state);
|
||||||
|
} else {
|
||||||
|
unhandled(msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -108,14 +113,15 @@ class ExampleProcessor extends UntypedPersistentActor {
|
||||||
public class PersistentActorExample {
|
public class PersistentActorExample {
|
||||||
public static void main(String... args) throws Exception {
|
public static void main(String... args) throws Exception {
|
||||||
final ActorSystem system = ActorSystem.create("example");
|
final ActorSystem system = ActorSystem.create("example");
|
||||||
final ActorRef processor = system.actorOf(Props.create(ExampleProcessor.class), "processor-4-java");
|
final ActorRef persistentActor =
|
||||||
|
system.actorOf(Props.create(ExamplePersistentActor.class), "persistentActor-4-java");
|
||||||
|
|
||||||
processor.tell(new Cmd("foo"), null);
|
persistentActor.tell(new Cmd("foo"), null);
|
||||||
processor.tell(new Cmd("baz"), null);
|
persistentActor.tell(new Cmd("baz"), null);
|
||||||
processor.tell(new Cmd("bar"), null);
|
persistentActor.tell(new Cmd("bar"), null);
|
||||||
processor.tell("snap", null);
|
persistentActor.tell("snap", null);
|
||||||
processor.tell(new Cmd("buzz"), null);
|
persistentActor.tell(new Cmd("buzz"), null);
|
||||||
processor.tell("print", null);
|
persistentActor.tell("print", null);
|
||||||
|
|
||||||
Thread.sleep(1000);
|
Thread.sleep(1000);
|
||||||
system.shutdown();
|
system.shutdown();
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,71 @@
|
||||||
|
package sample.persistence;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import akka.japi.Procedure;
|
||||||
|
import akka.actor.*;
|
||||||
|
import akka.persistence.*;
|
||||||
|
|
||||||
|
public class PersistentActorFailureExample {
|
||||||
|
public static class ExamplePersistentActor extends UntypedPersistentActor {
|
||||||
|
private ArrayList<Object> received = new ArrayList<Object>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onReceiveCommand(Object message) throws Exception {
|
||||||
|
if (message.equals("boom")) {
|
||||||
|
throw new Exception("boom");
|
||||||
|
} else if (message.equals("print")) {
|
||||||
|
System.out.println("received " + received);
|
||||||
|
} else if (message instanceof String) {
|
||||||
|
String s = (String) message;
|
||||||
|
persist(s, new Procedure<String>() {
|
||||||
|
public void apply(String evt) throws Exception {
|
||||||
|
received.add(evt);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
unhandled(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onReceiveRecover(Object message) {
|
||||||
|
if (message instanceof String) {
|
||||||
|
received.add((String) message);
|
||||||
|
} else {
|
||||||
|
unhandled(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String... args) throws Exception {
|
||||||
|
final ActorSystem system = ActorSystem.create("example");
|
||||||
|
final ActorRef persistentActor = system.actorOf(Props.create(ExamplePersistentActor.class), "persistentActor-2");
|
||||||
|
|
||||||
|
persistentActor.tell("a", null);
|
||||||
|
persistentActor.tell("print", null);
|
||||||
|
persistentActor.tell("boom", null);
|
||||||
|
persistentActor.tell("print", null);
|
||||||
|
persistentActor.tell("b", null);
|
||||||
|
persistentActor.tell("print", null);
|
||||||
|
persistentActor.tell("c", null);
|
||||||
|
persistentActor.tell("print", null);
|
||||||
|
|
||||||
|
// Will print in a first run (i.e. with empty journal):
|
||||||
|
|
||||||
|
// received [a]
|
||||||
|
// received [a, b]
|
||||||
|
// received [a, b, c]
|
||||||
|
|
||||||
|
// Will print in a second run:
|
||||||
|
|
||||||
|
// received [a, b, c, a]
|
||||||
|
// received [a, b, c, a, b]
|
||||||
|
// received [a, b, c, a, b, c]
|
||||||
|
|
||||||
|
// etc ...
|
||||||
|
|
||||||
|
Thread.sleep(1000);
|
||||||
|
system.shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,51 +0,0 @@
|
||||||
package sample.persistence;
|
|
||||||
|
|
||||||
import akka.actor.*;
|
|
||||||
import akka.persistence.*;
|
|
||||||
|
|
||||||
public class ProcessorChannelExample {
|
|
||||||
public static class ExampleProcessor extends UntypedProcessor {
|
|
||||||
private ActorRef destination;
|
|
||||||
private ActorRef channel;
|
|
||||||
|
|
||||||
public ExampleProcessor(ActorRef destination) {
|
|
||||||
this.destination = destination;
|
|
||||||
this.channel = getContext().actorOf(Channel.props(), "channel");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onReceive(Object message) throws Exception {
|
|
||||||
if (message instanceof Persistent) {
|
|
||||||
Persistent msg = (Persistent)message;
|
|
||||||
System.out.println("processed " + msg.payload());
|
|
||||||
channel.tell(Deliver.create(msg.withPayload("processed " + msg.payload()), destination.path()), getSelf());
|
|
||||||
} else if (message instanceof String) {
|
|
||||||
System.out.println("reply = " + message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class ExampleDestination extends UntypedActor {
|
|
||||||
@Override
|
|
||||||
public void onReceive(Object message) throws Exception {
|
|
||||||
if (message instanceof ConfirmablePersistent) {
|
|
||||||
ConfirmablePersistent msg = (ConfirmablePersistent)message;
|
|
||||||
System.out.println("received " + msg.payload());
|
|
||||||
getSender().tell(String.format("re: %s (%d)", msg.payload(), msg.sequenceNr()), null);
|
|
||||||
msg.confirm();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void main(String... args) throws Exception {
|
|
||||||
final ActorSystem system = ActorSystem.create("example");
|
|
||||||
final ActorRef destination = system.actorOf(Props.create(ExampleDestination.class));
|
|
||||||
final ActorRef processor = system.actorOf(Props.create(ExampleProcessor.class, destination), "processor-1");
|
|
||||||
|
|
||||||
processor.tell(Persistent.create("a"), null);
|
|
||||||
processor.tell(Persistent.create("b"), null);
|
|
||||||
|
|
||||||
Thread.sleep(1000);
|
|
||||||
system.shutdown();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,71 +0,0 @@
|
||||||
package sample.persistence;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
|
|
||||||
import scala.Option;
|
|
||||||
|
|
||||||
import akka.actor.*;
|
|
||||||
import akka.persistence.*;
|
|
||||||
|
|
||||||
public class ProcessorFailureExample {
|
|
||||||
public static class ExampleProcessor extends UntypedProcessor {
|
|
||||||
private ArrayList<Object> received = new ArrayList<Object>();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onReceive(Object message) throws Exception {
|
|
||||||
if (message instanceof Persistent) {
|
|
||||||
Persistent persistent = (Persistent)message;
|
|
||||||
if (persistent.payload() == "boom") {
|
|
||||||
throw new Exception("boom");
|
|
||||||
} else {
|
|
||||||
received.add(persistent.payload());
|
|
||||||
}
|
|
||||||
} else if (message == "boom") {
|
|
||||||
throw new Exception("boom");
|
|
||||||
} else if (message == "print") {
|
|
||||||
System.out.println("received " + received);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void preRestart(Throwable reason, Option<Object> message) {
|
|
||||||
if (message.isDefined() && message.get() instanceof Persistent) {
|
|
||||||
deleteMessage(((Persistent) message.get()).sequenceNr(), false);
|
|
||||||
}
|
|
||||||
super.preRestart(reason, message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void main(String... args) throws Exception {
|
|
||||||
final ActorSystem system = ActorSystem.create("example");
|
|
||||||
final ActorRef processor = system.actorOf(Props.create(ExampleProcessor.class), "processor-2");
|
|
||||||
|
|
||||||
processor.tell(Persistent.create("a"), null);
|
|
||||||
processor.tell("print", null);
|
|
||||||
processor.tell("boom", null);
|
|
||||||
processor.tell("print", null);
|
|
||||||
processor.tell(Persistent.create("b"), null);
|
|
||||||
processor.tell("print", null);
|
|
||||||
processor.tell(Persistent.create("boom"), null);
|
|
||||||
processor.tell("print", null);
|
|
||||||
processor.tell(Persistent.create("c"), null);
|
|
||||||
processor.tell("print", null);
|
|
||||||
|
|
||||||
// Will print in a first run (i.e. with empty journal):
|
|
||||||
|
|
||||||
// received [a]
|
|
||||||
// received [a, b]
|
|
||||||
// received [a, b, c]
|
|
||||||
|
|
||||||
// Will print in a second run:
|
|
||||||
|
|
||||||
// received [a, b, c, a]
|
|
||||||
// received [a, b, c, a, b]
|
|
||||||
// received [a, b, c, a, b, c]
|
|
||||||
|
|
||||||
// etc ...
|
|
||||||
|
|
||||||
Thread.sleep(1000);
|
|
||||||
system.shutdown();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -5,6 +5,7 @@ import java.util.ArrayList;
|
||||||
|
|
||||||
import akka.actor.*;
|
import akka.actor.*;
|
||||||
import akka.persistence.*;
|
import akka.persistence.*;
|
||||||
|
import akka.japi.Procedure;
|
||||||
|
|
||||||
public class SnapshotExample {
|
public class SnapshotExample {
|
||||||
public static class ExampleState implements Serializable {
|
public static class ExampleState implements Serializable {
|
||||||
|
|
@ -32,38 +33,57 @@ public class SnapshotExample {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class ExampleProcessor extends UntypedProcessor {
|
public static class ExamplePersistentActor extends UntypedPersistentActor {
|
||||||
private ExampleState state = new ExampleState();
|
private ExampleState state = new ExampleState();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onReceive(Object message) throws Exception {
|
public void onReceiveCommand(Object message) {
|
||||||
if (message instanceof Persistent) {
|
if (message.equals("print")) {
|
||||||
Persistent persistent = (Persistent)message;
|
|
||||||
state.update(String.format("%s-%d", persistent.payload(), persistent.sequenceNr()));
|
|
||||||
} else if (message instanceof SnapshotOffer) {
|
|
||||||
ExampleState s = (ExampleState)((SnapshotOffer)message).snapshot();
|
|
||||||
System.out.println("offered state = " + s);
|
|
||||||
state = s;
|
|
||||||
} else if (message.equals("print")) {
|
|
||||||
System.out.println("current state = " + state);
|
System.out.println("current state = " + state);
|
||||||
} else if (message.equals("snap")) {
|
} else if (message.equals("snap")) {
|
||||||
// IMPORTANT: create a copy of snapshot
|
// IMPORTANT: create a copy of snapshot
|
||||||
// because ExampleState is mutable !!!
|
// because ExampleState is mutable !!!
|
||||||
saveSnapshot(state.copy());
|
saveSnapshot(state.copy());
|
||||||
|
} else if (message instanceof SaveSnapshotSuccess) {
|
||||||
|
// ...
|
||||||
|
} else if (message instanceof SaveSnapshotFailure) {
|
||||||
|
// ...
|
||||||
|
} else if (message instanceof String) {
|
||||||
|
String s = (String) message;
|
||||||
|
persist(s, new Procedure<String>() {
|
||||||
|
public void apply(String evt) throws Exception {
|
||||||
|
state.update(evt);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
unhandled(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onReceiveRecover(Object message) {
|
||||||
|
if (message instanceof SnapshotOffer) {
|
||||||
|
ExampleState s = (ExampleState)((SnapshotOffer)message).snapshot();
|
||||||
|
System.out.println("offered state = " + s);
|
||||||
|
state = s;
|
||||||
|
} else if (message instanceof String) {
|
||||||
|
state.update((String) message);
|
||||||
|
} else {
|
||||||
|
unhandled(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void main(String... args) throws Exception {
|
public static void main(String... args) throws Exception {
|
||||||
final ActorSystem system = ActorSystem.create("example");
|
final ActorSystem system = ActorSystem.create("example");
|
||||||
final ActorRef processor = system.actorOf(Props.create(ExampleProcessor.class), "processor-3-java");
|
final ActorRef persistentActor = system.actorOf(Props.create(ExamplePersistentActor.class), "persistentActor-3-java");
|
||||||
|
|
||||||
processor.tell(Persistent.create("a"), null);
|
persistentActor.tell("a", null);
|
||||||
processor.tell(Persistent.create("b"), null);
|
persistentActor.tell("b", null);
|
||||||
processor.tell("snap", null);
|
persistentActor.tell("snap", null);
|
||||||
processor.tell(Persistent.create("c"), null);
|
persistentActor.tell("c", null);
|
||||||
processor.tell(Persistent.create("d"), null);
|
persistentActor.tell("d", null);
|
||||||
processor.tell("print", null);
|
persistentActor.tell("print", null);
|
||||||
|
|
||||||
Thread.sleep(1000);
|
Thread.sleep(1000);
|
||||||
system.shutdown();
|
system.shutdown();
|
||||||
|
|
|
||||||
|
|
@ -1,31 +1,47 @@
|
||||||
package sample.persistence;
|
package sample.persistence;
|
||||||
|
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import scala.concurrent.duration.Duration;
|
import scala.concurrent.duration.Duration;
|
||||||
|
|
||||||
import akka.actor.*;
|
import akka.actor.*;
|
||||||
import akka.persistence.*;
|
import akka.persistence.*;
|
||||||
|
import akka.japi.Procedure;
|
||||||
|
|
||||||
public class ViewExample {
|
public class ViewExample {
|
||||||
public static class ExampleProcessor extends UntypedProcessor {
|
public static class ExamplePersistentActor extends UntypedPersistentActor {
|
||||||
|
private int count = 1;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String processorId() {
|
public String persistenceId() {
|
||||||
return "processor-5";
|
return "persistentActor-5";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onReceiveRecover(Object message) {
|
||||||
|
if (message instanceof String) {
|
||||||
|
count += 1;
|
||||||
|
} else {
|
||||||
|
unhandled(message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onReceive(Object message) throws Exception {
|
public void onReceiveCommand(Object message) {
|
||||||
if (message instanceof Persistent) {
|
if (message instanceof String) {
|
||||||
Persistent p = (Persistent)message;
|
String s = (String) message;
|
||||||
System.out.println(String.format("processor received %s (sequence nr = %d)", p.payload(), p.sequenceNr()));
|
System.out.println(String.format("persistentActor received %s (nr = %d)", s, count));
|
||||||
}
|
persist(s + count, new Procedure<String>() {
|
||||||
|
public void apply(String evt) {
|
||||||
|
count += 1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
unhandled(message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class ExampleView extends UntypedView {
|
public static class ExampleView extends UntypedView {
|
||||||
private final ActorRef destination = getContext().actorOf(Props.create(ExampleDestination.class));
|
|
||||||
private final ActorRef channel = getContext().actorOf(Channel.props("channel"));
|
|
||||||
|
|
||||||
private int numReplicated = 0;
|
private int numReplicated = 0;
|
||||||
|
|
||||||
|
|
@ -36,7 +52,7 @@ public class ViewExample {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String persistenceId() {
|
public String persistenceId() {
|
||||||
return "processor-5";
|
return "persistentActor-5";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -44,8 +60,7 @@ public class ViewExample {
|
||||||
if (message instanceof Persistent) {
|
if (message instanceof Persistent) {
|
||||||
Persistent p = (Persistent)message;
|
Persistent p = (Persistent)message;
|
||||||
numReplicated += 1;
|
numReplicated += 1;
|
||||||
System.out.println(String.format("view received %s (sequence nr = %d, num replicated = %d)", p.payload(), p.sequenceNr(), numReplicated));
|
System.out.println(String.format("view received %s (num replicated = %d)", p.payload(), numReplicated));
|
||||||
channel.tell(Deliver.create(p.withPayload("replicated-" + p.payload()), destination.path()), getSelf());
|
|
||||||
} else if (message instanceof SnapshotOffer) {
|
} else if (message instanceof SnapshotOffer) {
|
||||||
SnapshotOffer so = (SnapshotOffer)message;
|
SnapshotOffer so = (SnapshotOffer)message;
|
||||||
numReplicated = (Integer)so.snapshot();
|
numReplicated = (Integer)so.snapshot();
|
||||||
|
|
@ -56,23 +71,12 @@ public class ViewExample {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class ExampleDestination extends UntypedActor {
|
|
||||||
@Override
|
|
||||||
public void onReceive(Object message) throws Exception {
|
|
||||||
if (message instanceof ConfirmablePersistent) {
|
|
||||||
ConfirmablePersistent cp = (ConfirmablePersistent)message;
|
|
||||||
System.out.println(String.format("destination received %s (sequence nr = %s)", cp.payload(), cp.sequenceNr()));
|
|
||||||
cp.confirm();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void main(String... args) throws Exception {
|
public static void main(String... args) throws Exception {
|
||||||
final ActorSystem system = ActorSystem.create("example");
|
final ActorSystem system = ActorSystem.create("example");
|
||||||
final ActorRef processor = system.actorOf(Props.create(ExampleProcessor.class));
|
final ActorRef persistentActor = system.actorOf(Props.create(ExamplePersistentActor.class));
|
||||||
final ActorRef view = system.actorOf(Props.create(ExampleView.class));
|
final ActorRef view = system.actorOf(Props.create(ExampleView.class));
|
||||||
|
|
||||||
system.scheduler().schedule(Duration.Zero(), Duration.create(2, TimeUnit.SECONDS), processor, Persistent.create("scheduled"), system.dispatcher(), null);
|
system.scheduler().schedule(Duration.Zero(), Duration.create(2, TimeUnit.SECONDS), persistentActor, "scheduled", system.dispatcher(), null);
|
||||||
system.scheduler().schedule(Duration.Zero(), Duration.create(5, TimeUnit.SECONDS), view, "snap", system.dispatcher(), null);
|
system.scheduler().schedule(Duration.Zero(), Duration.create(5, TimeUnit.SECONDS), view, "snap", system.dispatcher(), null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,12 +12,10 @@ This tutorial contains examples that illustrate a subset of
|
||||||
<a href="http://doc.akka.io/docs/akka/2.4-SNAPSHOT/java/persistence.html" target="_blank">Akka Persistence</a> features.
|
<a href="http://doc.akka.io/docs/akka/2.4-SNAPSHOT/java/persistence.html" target="_blank">Akka Persistence</a> features.
|
||||||
</p>
|
</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li>Processors and channels</li>
|
<li>persistent actor</li>
|
||||||
<li>Processsor snapshots</li>
|
<li>persistent actor snapshots</li>
|
||||||
<li>Eventsourced processors</li>
|
<li>persistent actor recovery</li>
|
||||||
<li>Processor failure handling</li>
|
<li>persistent actor views</li>
|
||||||
<li>Processor views</li>
|
|
||||||
<li>Processor conversation recovery</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
|
|
@ -27,47 +25,13 @@ Custom storage locations for the journal and snapshots can be defined in
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h2>Processors and channels</h2>
|
<h2>Persistent actor</h2>
|
||||||
<p>
|
<p>
|
||||||
<a href="#code/src/main/java/sample/persistence/ProcessorChannelExample.java" class="shortcut">ProcessorChannelExample.java</a>
|
<a href="#code/src/main/java/sample/persistence/PersistentActorExample.java" class="shortcut">PersistentActorExample.java</a>
|
||||||
defines an <code>ExampleProcessor</code> and an <code>ExampleDestination</code>. The processor sends messages to a
|
is described in detail in the <a href="http://doc.akka.io/docs/akka/2.3-SNAPSHOT/java/persistence.html#event-sourcing-java" target="_blank">Event sourcing</a>
|
||||||
destination via a channel. The destination confirms the delivery of these messages so that replayed messages aren't
|
section of the user documentation. With every application run, the <code>ExamplePersistentActor</code> is recovered from
|
||||||
redundantly delivered to the destination. Repeated runs of the application demonstrates that the processor receives
|
|
||||||
both replayed and new messages whereas the channel only receives new messages, sent by the application. The processor
|
|
||||||
also receives replies from the destination, demonstrating that a channel preserves sender references.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
To run this example, go to the <a href="#run" class="shortcut">Run</a> tab, and run the application main class
|
|
||||||
<b><code>sample.persistence.ProcessorChannelExample</code></b> several times.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<h2>Processor snapshots</h2>
|
|
||||||
<p>
|
|
||||||
<a href="#code/src/main/java/sample/persistence/SnapshotExample.java" class="shortcut">SnapshotExample.java</a>
|
|
||||||
demonstrates how processors can take snapshots of application state and recover from previously stored snapshots.
|
|
||||||
Snapshots are offered to processors at the beginning of recovery, before any messages (younger than the snapshot)
|
|
||||||
are replayed.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
To run this example, go to the <a href="#run" class="shortcut">Run</a> tab, and run the application main class
|
|
||||||
<b><code>sample.persistence.SnapshotExample</code></b> several times. With every run, the state offered by the
|
|
||||||
most recent snapshot is printed to <code>stdout</code>, followed by the updated state after sending new persistent
|
|
||||||
messages to the processor.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<h2>Eventsourced processors</h2>
|
|
||||||
<p>
|
|
||||||
<a href="#code/src/main/java/sample/persistence/EventsourcedExample.java" class="shortcut">EventsourcedExample.java</a>
|
|
||||||
is described in detail in the <a href="http://doc.akka.io/docs/akka/2.4-SNAPSHOT/java/persistence.html#event-sourcing" target="_blank">Event sourcing</a>
|
|
||||||
section of the user documentation. With every application run, the <code>ExampleProcessor</code> is recovered from
|
|
||||||
events stored in previous application runs, processes new commands, stores new events and snapshots and prints the
|
events stored in previous application runs, processes new commands, stores new events and snapshots and prints the
|
||||||
current processor state to <code>stdout</code>.
|
current persistent actor state to <code>stdout</code>.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
|
|
@ -77,7 +41,37 @@ To run this example, go to the <a href="#run" class="shortcut">Run</a> tab, and
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h2>Processor failure handling</h2>
|
<h2>Persistent actor snapshots</h2>
|
||||||
|
<p>
|
||||||
|
<a href="#code/src/main/java/sample/persistence/SnapshotExample.java" class="shortcut">SnapshotExample.java</a>
|
||||||
|
demonstrates how persistent actors can take snapshots of application state and recover from previously stored snapshots.
|
||||||
|
Snapshots are offered to persistent actors at the beginning of recovery, before any messages (younger than the snapshot)
|
||||||
|
are replayed.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
To run this example, go to the <a href="#run" class="shortcut">Run</a> tab, and run the application main class
|
||||||
|
<b><code>sample.persistence.SnapshotExample</code></b> several times. With every run, the state offered by the
|
||||||
|
most recent snapshot is printed to <code>stdout</code>, followed by the updated state after sending new persistent
|
||||||
|
messages to the persistent actor.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h2>Persistent actor recovery</h2>
|
||||||
|
<p>
|
||||||
|
<a href="#code/src/main/java/sample/persistence/PersistentActorFailureExample.java" class="shortcut">PersistentActorFailureExample.java</a>
|
||||||
|
shows how a persistent actor can throw an exception, restart and restore the state by replaying the events.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
To run this example, go to the <a href="#run" class="shortcut">Run</a> tab, and run the application main class
|
||||||
|
<b><code>sample.persistence.PersistentActorFailureExample</code></b> several times.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h2>Persistent actor views</h2>
|
||||||
<p>
|
<p>
|
||||||
<a href="#code/src/main/java/sample/persistence/ProcessorFailureExample.java" class="shortcut">ProcessorFailureExample.java</a>
|
<a href="#code/src/main/java/sample/persistence/ProcessorFailureExample.java" class="shortcut">ProcessorFailureExample.java</a>
|
||||||
shows how a processor can delete persistent messages from the journal if they threw an exception. Throwing an exception
|
shows how a processor can delete persistent messages from the journal if they threw an exception. Throwing an exception
|
||||||
|
|
@ -105,32 +99,15 @@ command.
|
||||||
<h2>Processor views</h2>
|
<h2>Processor views</h2>
|
||||||
<p>
|
<p>
|
||||||
<a href="#code/src/main/java/sample/persistence/ViewExample.java" class="shortcut">ViewExample.java</a> demonstrates
|
<a href="#code/src/main/java/sample/persistence/ViewExample.java" class="shortcut">ViewExample.java</a> demonstrates
|
||||||
how a view (<code>ExampleView</code>) is updated with the persistent message stream of a processor
|
how a view (<code>ExampleView</code>) is updated with the persistent message stream of a persistent actor
|
||||||
(<code>ExampleProcessor</code>). Messages sent to the processor are read from <code>stdin</code>. Views also support
|
(<code>ExamplePersistentActor</code>). Messages sent to the persistent actor are scheduled periodically. Views also support
|
||||||
snapshotting and can be used in combination with channels in the same way as processors.
|
snapshotting to reduce recovery time.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
To run this example, go to the <a href="#run" class="shortcut">Run</a> tab, and run the application main class
|
To run this example, go to the <a href="#run" class="shortcut">Run</a> tab, and run the application main class
|
||||||
<b><code>sample.persistence.ViewExample</code></b>.
|
<b><code>sample.persistence.ViewExample</code></b>.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
|
||||||
Views can also receive events that have been persisted by event sourced processors (not shown).
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<h2>Processor conversation recovery</h2>
|
|
||||||
<p>
|
|
||||||
<a href="#code/src/main/java/sample/persistence/ConversationRecoveryExample.java" class="shortcut">ConversationRecoveryExample.java</a>
|
|
||||||
defines two processors that send messages to each other via channels. The reliable delivery properties of channels,
|
|
||||||
in combination with processors, allow these processors to automatically resume their conversation after a JVM crash.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
To run this example, go to the <a href="#run" class="shortcut">Run</a> tab, and run the application main class
|
|
||||||
<b><code>sample.persistence.ConversationRecoveryExample</code></b> several times.
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ version := "2.4-SNAPSHOT"
|
||||||
scalaVersion := "2.10.4"
|
scalaVersion := "2.10.4"
|
||||||
|
|
||||||
libraryDependencies ++= Seq(
|
libraryDependencies ++= Seq(
|
||||||
"com.typesafe.akka" %% "akka-persistence-experimental" % "2.4-SNAPSHOT"
|
"com.typesafe.akka" %% "akka-persistence-experimental" % "2.4-SNAPSHOT",
|
||||||
|
"com.typesafe.akka" %% "akka-stream-experimental" % "0.3"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,46 +0,0 @@
|
||||||
package sample.persistence
|
|
||||||
|
|
||||||
import akka.actor._
|
|
||||||
import akka.persistence._
|
|
||||||
|
|
||||||
object ConversationRecoveryExample extends App {
|
|
||||||
case object Ping
|
|
||||||
case object Pong
|
|
||||||
|
|
||||||
class Ping extends Processor {
|
|
||||||
val pongChannel = context.actorOf(Channel.props, "pongChannel")
|
|
||||||
var counter = 0
|
|
||||||
|
|
||||||
def receive = {
|
|
||||||
case m @ ConfirmablePersistent(Ping, _, _) =>
|
|
||||||
counter += 1
|
|
||||||
println(s"received ping ${counter} times ...")
|
|
||||||
m.confirm()
|
|
||||||
if (!recoveryRunning) Thread.sleep(1000)
|
|
||||||
pongChannel ! Deliver(m.withPayload(Pong), sender().path)
|
|
||||||
case "init" if (counter == 0) =>
|
|
||||||
pongChannel ! Deliver(Persistent(Pong), sender().path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Pong extends Processor {
|
|
||||||
val pingChannel = context.actorOf(Channel.props, "pingChannel")
|
|
||||||
var counter = 0
|
|
||||||
|
|
||||||
def receive = {
|
|
||||||
case m @ ConfirmablePersistent(Pong, _, _) =>
|
|
||||||
counter += 1
|
|
||||||
println(s"received pong ${counter} times ...")
|
|
||||||
m.confirm()
|
|
||||||
if (!recoveryRunning) Thread.sleep(1000)
|
|
||||||
pingChannel ! Deliver(m.withPayload(Ping), sender().path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val system = ActorSystem("example")
|
|
||||||
|
|
||||||
val ping = system.actorOf(Props(classOf[Ping]), "ping")
|
|
||||||
val pong = system.actorOf(Props(classOf[Pong]), "pong")
|
|
||||||
|
|
||||||
ping tell ("init", pong)
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
package sample.persistence
|
||||||
|
|
||||||
|
import akka.actor._
|
||||||
|
import akka.persistence._
|
||||||
|
|
||||||
|
object PersistentActorFailureExample extends App {
|
||||||
|
class ExamplePersistentActor extends PersistentActor {
|
||||||
|
var received: List[String] = Nil // state
|
||||||
|
|
||||||
|
def receiveCommand: Actor.Receive = {
|
||||||
|
case "print" => println(s"received ${received.reverse}")
|
||||||
|
case "boom" => throw new Exception("boom")
|
||||||
|
case payload: String =>
|
||||||
|
persist(payload) { p => received = p :: received }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
def receiveRecover: Actor.Receive = {
|
||||||
|
case s: String => received = s :: received
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val system = ActorSystem("example")
|
||||||
|
val persistentActor = system.actorOf(Props(classOf[ExamplePersistentActor]), "persistentActor-2")
|
||||||
|
|
||||||
|
persistentActor ! "a"
|
||||||
|
persistentActor ! "print"
|
||||||
|
persistentActor ! "boom" // restart and recovery
|
||||||
|
persistentActor ! "print"
|
||||||
|
persistentActor ! "b"
|
||||||
|
persistentActor ! "print"
|
||||||
|
persistentActor ! "c"
|
||||||
|
persistentActor ! "print"
|
||||||
|
|
||||||
|
// Will print in a first run (i.e. with empty journal):
|
||||||
|
|
||||||
|
// received List(a)
|
||||||
|
// received List(a, b)
|
||||||
|
// received List(a, b, c)
|
||||||
|
|
||||||
|
// Will print in a second run:
|
||||||
|
|
||||||
|
// received List(a, b, c, a)
|
||||||
|
// received List(a, b, c, a, b)
|
||||||
|
// received List(a, b, c, a, b, c)
|
||||||
|
|
||||||
|
// etc ...
|
||||||
|
|
||||||
|
Thread.sleep(1000)
|
||||||
|
system.shutdown()
|
||||||
|
}
|
||||||
|
|
@ -1,37 +0,0 @@
|
||||||
package sample.persistence
|
|
||||||
|
|
||||||
import akka.actor._
|
|
||||||
import akka.persistence._
|
|
||||||
|
|
||||||
object ProcessorChannelExample extends App {
|
|
||||||
class ExampleProcessor extends Processor {
|
|
||||||
val channel = context.actorOf(Channel.props, "channel")
|
|
||||||
val destination = context.actorOf(Props[ExampleDestination])
|
|
||||||
|
|
||||||
def receive = {
|
|
||||||
case p @ Persistent(payload, _) =>
|
|
||||||
println(s"processed ${payload}")
|
|
||||||
channel ! Deliver(p.withPayload(s"processed ${payload}"), destination.path)
|
|
||||||
case reply: String =>
|
|
||||||
println(s"reply = ${reply}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ExampleDestination extends Actor {
|
|
||||||
def receive = {
|
|
||||||
case p @ ConfirmablePersistent(payload, snr, _) =>
|
|
||||||
println(s"received ${payload}")
|
|
||||||
sender() ! s"re: ${payload} (${snr})"
|
|
||||||
p.confirm()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val system = ActorSystem("example")
|
|
||||||
val processor = system.actorOf(Props(classOf[ExampleProcessor]), "processor-1")
|
|
||||||
|
|
||||||
processor ! Persistent("a")
|
|
||||||
processor ! Persistent("b")
|
|
||||||
|
|
||||||
Thread.sleep(1000)
|
|
||||||
system.shutdown()
|
|
||||||
}
|
|
||||||
|
|
@ -1,89 +0,0 @@
|
||||||
package sample.persistence
|
|
||||||
|
|
||||||
import scala.concurrent.duration._
|
|
||||||
|
|
||||||
import com.typesafe.config._
|
|
||||||
|
|
||||||
import akka.actor._
|
|
||||||
import akka.persistence._
|
|
||||||
|
|
||||||
object ProcessorChannelRemoteExample {
|
|
||||||
val config = ConfigFactory.parseString(
|
|
||||||
"""
|
|
||||||
akka {
|
|
||||||
actor {
|
|
||||||
provider = "akka.remote.RemoteActorRefProvider"
|
|
||||||
}
|
|
||||||
remote {
|
|
||||||
enabled-transports = ["akka.remote.netty.tcp"]
|
|
||||||
netty.tcp.hostname = "127.0.0.1"
|
|
||||||
}
|
|
||||||
persistence {
|
|
||||||
journal.leveldb.dir = "target/example/journal"
|
|
||||||
snapshot-store.local.dir = "target/example/snapshots"
|
|
||||||
}
|
|
||||||
loglevel = INFO
|
|
||||||
log-dead-letters = 0
|
|
||||||
log-dead-letters-during-shutdown = off
|
|
||||||
|
|
||||||
}
|
|
||||||
""")
|
|
||||||
}
|
|
||||||
|
|
||||||
object SenderApp /*extends App*/ { // no app until https://github.com/typesafehub/activator/issues/287 is fixed
|
|
||||||
import ProcessorChannelRemoteExample._
|
|
||||||
|
|
||||||
class ExampleProcessor(destination: ActorPath) extends Processor {
|
|
||||||
val listener = context.actorOf(Props[ExampleListener])
|
|
||||||
val channel = context.actorOf(Channel.props(ChannelSettings(
|
|
||||||
redeliverMax = 15,
|
|
||||||
redeliverInterval = 3.seconds,
|
|
||||||
redeliverFailureListener = Some(listener))), "channel")
|
|
||||||
|
|
||||||
def receive = {
|
|
||||||
case p @ Persistent(payload, snr) =>
|
|
||||||
println(s"[processor] received payload: ${payload} (snr = ${snr}, replayed = ${recoveryRunning})")
|
|
||||||
channel ! Deliver(p.withPayload(s"processed ${payload}"), destination)
|
|
||||||
case "restart" =>
|
|
||||||
throw new Exception("restart requested")
|
|
||||||
case reply: String =>
|
|
||||||
println(s"[processor] received reply: ${reply}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ExampleListener extends Actor {
|
|
||||||
def receive = {
|
|
||||||
case RedeliverFailure(messages) =>
|
|
||||||
println(s"unable to deliver ${messages.length} messages, restarting processor to resend messages ...")
|
|
||||||
context.parent ! "restart"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val receiverPath = ActorPath.fromString("akka.tcp://receiver@127.0.0.1:44317/user/receiver")
|
|
||||||
val senderConfig = ConfigFactory.parseString("akka.remote.netty.tcp.port = 44316")
|
|
||||||
|
|
||||||
val system = ActorSystem("sender", config.withFallback(senderConfig))
|
|
||||||
val sender = system.actorOf(Props(classOf[ExampleProcessor], receiverPath))
|
|
||||||
|
|
||||||
import system.dispatcher
|
|
||||||
|
|
||||||
system.scheduler.schedule(Duration.Zero, 3.seconds, sender, Persistent("scheduled"))
|
|
||||||
}
|
|
||||||
|
|
||||||
object ReceiverApp /*extends App*/ { // no app until https://github.com/typesafehub/activator/issues/287 is fixed
|
|
||||||
import ProcessorChannelRemoteExample._
|
|
||||||
|
|
||||||
class ExampleDestination extends Actor {
|
|
||||||
def receive = {
|
|
||||||
case p @ ConfirmablePersistent(payload, snr, redel) =>
|
|
||||||
println(s"[destination] received payload: ${payload} (snr = ${snr}, redel = ${redel})")
|
|
||||||
sender() ! s"re: ${payload} (snr = ${snr})"
|
|
||||||
p.confirm()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val receiverConfig = ConfigFactory.parseString("akka.remote.netty.tcp.port = 44317")
|
|
||||||
val system = ActorSystem("receiver", config.withFallback(receiverConfig))
|
|
||||||
|
|
||||||
system.actorOf(Props[ExampleDestination], "receiver")
|
|
||||||
}
|
|
||||||
|
|
@ -1,56 +0,0 @@
|
||||||
package sample.persistence
|
|
||||||
|
|
||||||
import akka.actor._
|
|
||||||
import akka.persistence._
|
|
||||||
|
|
||||||
object ProcessorFailureExample extends App {
|
|
||||||
class ExampleProcessor extends Processor {
|
|
||||||
var received: List[String] = Nil // state
|
|
||||||
|
|
||||||
def receive = {
|
|
||||||
case "print" => println(s"received ${received.reverse}")
|
|
||||||
case "boom" => throw new Exception("boom")
|
|
||||||
case Persistent("boom", _) => throw new Exception("boom")
|
|
||||||
case Persistent(payload: String, _) => received = payload :: received
|
|
||||||
}
|
|
||||||
|
|
||||||
override def preRestart(reason: Throwable, message: Option[Any]) {
|
|
||||||
message match {
|
|
||||||
case Some(p: Persistent) if !recoveryRunning => deleteMessage(p.sequenceNr) // mark failing message as deleted
|
|
||||||
case _ => // ignore
|
|
||||||
}
|
|
||||||
super.preRestart(reason, message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val system = ActorSystem("example")
|
|
||||||
val processor = system.actorOf(Props(classOf[ExampleProcessor]), "processor-2")
|
|
||||||
|
|
||||||
processor ! Persistent("a")
|
|
||||||
processor ! "print"
|
|
||||||
processor ! "boom" // restart and recovery
|
|
||||||
processor ! "print"
|
|
||||||
processor ! Persistent("b")
|
|
||||||
processor ! "print"
|
|
||||||
processor ! Persistent("boom") // restart, recovery and deletion of message from journal
|
|
||||||
processor ! "print"
|
|
||||||
processor ! Persistent("c")
|
|
||||||
processor ! "print"
|
|
||||||
|
|
||||||
// Will print in a first run (i.e. with empty journal):
|
|
||||||
|
|
||||||
// received List(a)
|
|
||||||
// received List(a, b)
|
|
||||||
// received List(a, b, c)
|
|
||||||
|
|
||||||
// Will print in a second run:
|
|
||||||
|
|
||||||
// received List(a, b, c, a)
|
|
||||||
// received List(a, b, c, a, b)
|
|
||||||
// received List(a, b, c, a, b, c)
|
|
||||||
|
|
||||||
// etc ...
|
|
||||||
|
|
||||||
Thread.sleep(1000)
|
|
||||||
system.shutdown()
|
|
||||||
}
|
|
||||||
|
|
@ -5,34 +5,40 @@ import akka.persistence._
|
||||||
|
|
||||||
object SnapshotExample extends App {
|
object SnapshotExample extends App {
|
||||||
final case class ExampleState(received: List[String] = Nil) {
|
final case class ExampleState(received: List[String] = Nil) {
|
||||||
def update(s: String) = copy(s :: received)
|
def updated(s: String): ExampleState = copy(s :: received)
|
||||||
override def toString = received.reverse.toString
|
override def toString = received.reverse.toString
|
||||||
}
|
}
|
||||||
|
|
||||||
class ExampleProcessor extends Processor {
|
class ExamplePersistentActor extends PersistentActor {
|
||||||
var state = ExampleState()
|
var state = ExampleState()
|
||||||
|
|
||||||
def receive = {
|
def receiveCommand: Actor.Receive = {
|
||||||
case Persistent(s, snr) => state = state.update(s"${s}-${snr}")
|
case "print" => println("current state = " + state)
|
||||||
|
case "snap" => saveSnapshot(state)
|
||||||
case SaveSnapshotSuccess(metadata) => // ...
|
case SaveSnapshotSuccess(metadata) => // ...
|
||||||
case SaveSnapshotFailure(metadata, reason) => // ...
|
case SaveSnapshotFailure(metadata, reason) => // ...
|
||||||
|
case s: String =>
|
||||||
|
persist(s) { evt => state = state.updated(evt) }
|
||||||
|
}
|
||||||
|
|
||||||
|
def receiveRecover: Actor.Receive = {
|
||||||
case SnapshotOffer(_, s: ExampleState) =>
|
case SnapshotOffer(_, s: ExampleState) =>
|
||||||
println("offered state = " + s)
|
println("offered state = " + s)
|
||||||
state = s
|
state = s
|
||||||
case "print" => println("current state = " + state)
|
case evt: String =>
|
||||||
case "snap" => saveSnapshot(state)
|
state = state.updated(evt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val system = ActorSystem("example")
|
val system = ActorSystem("example")
|
||||||
val processor = system.actorOf(Props(classOf[ExampleProcessor]), "processor-3-scala")
|
val persistentActor = system.actorOf(Props(classOf[ExamplePersistentActor]), "persistentActor-3-scala")
|
||||||
|
|
||||||
processor ! Persistent("a")
|
persistentActor ! "a"
|
||||||
processor ! Persistent("b")
|
persistentActor ! "b"
|
||||||
processor ! "snap"
|
persistentActor ! "snap"
|
||||||
processor ! Persistent("c")
|
persistentActor ! "c"
|
||||||
processor ! Persistent("d")
|
persistentActor ! "d"
|
||||||
processor ! "print"
|
persistentActor ! "print"
|
||||||
|
|
||||||
Thread.sleep(1000)
|
Thread.sleep(1000)
|
||||||
system.shutdown()
|
system.shutdown()
|
||||||
|
|
|
||||||
|
|
@ -52,4 +52,4 @@ object StreamExample extends App {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -6,23 +6,30 @@ import akka.actor._
|
||||||
import akka.persistence._
|
import akka.persistence._
|
||||||
|
|
||||||
object ViewExample extends App {
|
object ViewExample extends App {
|
||||||
class ExampleProcessor extends Processor {
|
class ExamplePersistentActor extends PersistentActor {
|
||||||
override def processorId = "processor-5"
|
override def persistenceId = "persistentActor-5"
|
||||||
|
|
||||||
def receive = {
|
var count = 1
|
||||||
case Persistent(payload, sequenceNr) =>
|
|
||||||
println(s"processor received ${payload} (sequence nr = ${sequenceNr})")
|
def receiveCommand: Actor.Receive = {
|
||||||
|
case payload: String =>
|
||||||
|
println(s"persistentActor received ${payload} (nr = ${count})")
|
||||||
|
persist(payload + count) { evt =>
|
||||||
|
count += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def receiveRecover: Actor.Receive = {
|
||||||
|
case _: String => count += 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ExampleView extends View {
|
class ExampleView extends View {
|
||||||
private var numReplicated = 0
|
private var numReplicated = 0
|
||||||
|
|
||||||
override def persistenceId: String = "processor-5"
|
override def persistenceId: String = "persistentActor-5"
|
||||||
override def viewId = "view-5"
|
|
||||||
|
|
||||||
private val destination = context.actorOf(Props[ExampleDestination])
|
override def viewId = "view-5"
|
||||||
private val channel = context.actorOf(Channel.props("channel"))
|
|
||||||
|
|
||||||
def receive = {
|
def receive = {
|
||||||
case "snap" =>
|
case "snap" =>
|
||||||
|
|
@ -30,29 +37,20 @@ object ViewExample extends App {
|
||||||
case SnapshotOffer(metadata, snapshot: Int) =>
|
case SnapshotOffer(metadata, snapshot: Int) =>
|
||||||
numReplicated = snapshot
|
numReplicated = snapshot
|
||||||
println(s"view received snapshot offer ${snapshot} (metadata = ${metadata})")
|
println(s"view received snapshot offer ${snapshot} (metadata = ${metadata})")
|
||||||
case Persistent(payload, sequenceNr) =>
|
case Persistent(payload, _) =>
|
||||||
numReplicated += 1
|
numReplicated += 1
|
||||||
println(s"view received ${payload} (sequence nr = ${sequenceNr}, num replicated = ${numReplicated})")
|
println(s"view received ${payload} (num replicated = ${numReplicated})")
|
||||||
channel ! Deliver(Persistent(s"replicated-${payload}"), destination.path)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class ExampleDestination extends Actor {
|
|
||||||
def receive = {
|
|
||||||
case cp @ ConfirmablePersistent(payload, sequenceNr, _) =>
|
|
||||||
println(s"destination received ${payload} (sequence nr = ${sequenceNr})")
|
|
||||||
cp.confirm()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val system = ActorSystem("example")
|
val system = ActorSystem("example")
|
||||||
|
|
||||||
val processor = system.actorOf(Props(classOf[ExampleProcessor]))
|
val persistentActor = system.actorOf(Props(classOf[ExamplePersistentActor]))
|
||||||
val view = system.actorOf(Props(classOf[ExampleView]))
|
val view = system.actorOf(Props(classOf[ExampleView]))
|
||||||
|
|
||||||
import system.dispatcher
|
import system.dispatcher
|
||||||
|
|
||||||
system.scheduler.schedule(Duration.Zero, 2.seconds, processor, Persistent("scheduled"))
|
system.scheduler.schedule(Duration.Zero, 2.seconds, persistentActor, "scheduled")
|
||||||
system.scheduler.schedule(Duration.Zero, 5.seconds, view, "snap")
|
system.scheduler.schedule(Duration.Zero, 5.seconds, view, "snap")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,12 +11,10 @@ This tutorial contains examples that illustrate a subset of
|
||||||
<a href="http://doc.akka.io/docs/akka/2.4-SNAPSHOT/scala/persistence.html" target="_blank">Akka Persistence</a> features.
|
<a href="http://doc.akka.io/docs/akka/2.4-SNAPSHOT/scala/persistence.html" target="_blank">Akka Persistence</a> features.
|
||||||
</p>
|
</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li>Processors and channels</li>
|
<li>persistent actor</li>
|
||||||
<li>Processsor snapshots</li>
|
<li>persistent actor snapshots</li>
|
||||||
<li>Eventsourced processors</li>
|
<li>persistent actor recovery</li>
|
||||||
<li>Processor failure handling</li>
|
<li>persistent actor views</li>
|
||||||
<li>Processor views</li>
|
|
||||||
<li>Processor conversation recovery</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
|
|
@ -26,47 +24,27 @@ Custom storage locations for the journal and snapshots can be defined in
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h2>Processors and channels</h2>
|
<h2>Persistent actor</h2>
|
||||||
<p>
|
<p>
|
||||||
<a href="#code/src/main/scala/sample/persistence/ProcessorChannelExample.scala" class="shortcut">ProcessorChannelExample.scala</a>
|
<a href="#code/src/main/scala/sample/persistence/PersistentActorExample.scala" class="shortcut">PersistentActorExample.scala</a>
|
||||||
defines an <code>ExampleProcessor</code> and an <code>ExampleDestination</code>. The processor sends messages to a
|
is described in detail in the <a href="http://doc.akka.io/docs/akka/2.3-SNAPSHOT/scala/persistence.html#event-sourcing" target="_blank">Event sourcing</a>
|
||||||
destination via a channel. The destination confirms the delivery of these messages so that replayed messages aren't
|
section of the user documentation. With every application run, the <code>ExamplePersistentActor</code> is recovered from
|
||||||
redundantly delivered to the destination. Repeated runs of the application demonstrates that the processor receives
|
events stored in previous application runs, processes new commands, stores new events and snapshots and prints the
|
||||||
both replayed and new messages whereas the channel only receives new messages, sent by the application. The processor
|
current persistent actor state to <code>stdout</code>.
|
||||||
also receives replies from the destination, demonstrating that a channel preserves sender references.
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
To run this example, go to the <a href="#run" class="shortcut">Run</a> tab, and run the application main class
|
To run this example, go to the <a href="#run" class="shortcut">Run</a> tab, and run the application main class
|
||||||
<b><code>sample.persistence.ProcessorChannelExample</code></b> several times.
|
<b><code>sample.persistence.PersistentActorExample</code></b> several times.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<!-- do not show until https://github.com/typesafehub/activator/issues/287 is fixed
|
|
||||||
<p>
|
|
||||||
<a href="#code/src/main/scala/sample/persistence/ProcessorChannelRemoteExample.scala" class="shortcut">ProcessorChannelRemoteExample.scala</a>
|
|
||||||
is similar to the previous example. Here, processor and destination run in different JVMs. The processor JVM prompts the user for new
|
|
||||||
messages. By shutting down and restarting processor and/or destination JVMs, the reliable delivery properties of channels can
|
|
||||||
be examined. Furthermore, the application also registers an <code>ExampleListener</code> at the channel which
|
|
||||||
receives <code>RedeliverFailure</code> messages should the maximum number of redelivery attempts be reached.
|
|
||||||
In this case the sending processor is restarted and a new round of redelivery is started (triggered by un-confirmed,
|
|
||||||
replayed messages).
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
To run this example, go to the <a href="#run" class="shortcut">Run</a> tab, and start the processor JVM by running the
|
|
||||||
main class <b><code>sample.persistence.SenderApp</code></b>. The destination JVM can be starting by running the
|
|
||||||
main class <b><code>sample.persistence.ReceiverApp</code></b>
|
|
||||||
</p>
|
|
||||||
-->
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h2>Processor snapshots</h2>
|
<h2>Persistent actor snapshots</h2>
|
||||||
<p>
|
<p>
|
||||||
<a href="#code/src/main/scala/sample/persistence/SnapshotExample.scala" class="shortcut">SnapshotExample.scala</a>
|
<a href="#code/src/main/scala/sample/persistence/SnapshotExample.scala" class="shortcut">SnapshotExample.scala</a>
|
||||||
demonstrates how processors can take snapshots of application state and recover from previously stored snapshots.
|
demonstrates how persistent actors can take snapshots of application state and recover from previously stored snapshots.
|
||||||
Snapshots are offered to processors at the beginning of recovery, before any messages (younger than the snapshot)
|
Snapshots are offered to persistent actors at the beginning of recovery, before any messages (younger than the snapshot)
|
||||||
are replayed.
|
are replayed.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
@ -74,23 +52,20 @@ are replayed.
|
||||||
To run this example, go to the <a href="#run" class="shortcut">Run</a> tab, and run the application main class
|
To run this example, go to the <a href="#run" class="shortcut">Run</a> tab, and run the application main class
|
||||||
<b><code>sample.persistence.SnapshotExample</code></b> several times. With every run, the state offered by the
|
<b><code>sample.persistence.SnapshotExample</code></b> several times. With every run, the state offered by the
|
||||||
most recent snapshot is printed to <code>stdout</code>, followed by the updated state after sending new persistent
|
most recent snapshot is printed to <code>stdout</code>, followed by the updated state after sending new persistent
|
||||||
messages to the processor.
|
messages to the persistent actor.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h2>Eventsourced processors</h2>
|
<h2>Persistent actor recovery</h2>
|
||||||
<p>
|
<p>
|
||||||
<a href="#code/src/main/scala/sample/persistence/EventsourcedExample.scala" class="shortcut">EventsourcedExample.scala</a>
|
<a href="#code/src/main/scala/sample/persistence/PersistentActorFailureExample.scala" class="shortcut">PersistentActorFailureExample.scala</a>
|
||||||
is described in detail in the <a href="http://doc.akka.io/docs/akka/2.4-SNAPSHOT/scala/persistence.html#event-sourcing" target="_blank">Event sourcing</a>
|
shows how a persistent actor can throw an exception, restart and restore the state by replaying the events.
|
||||||
section of the user documentation. With every application run, the <code>ExampleProcessor</code> is recovered from
|
|
||||||
events stored in previous application runs, processes new commands, stores new events and snapshots and prints the
|
|
||||||
current processor state to <code>stdout</code>.
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
To run this example, go to the <a href="#run" class="shortcut">Run</a> tab, and run the application main class
|
To run this example, go to the <a href="#run" class="shortcut">Run</a> tab, and run the application main class
|
||||||
<b><code>sample.persistence.PersistentActorExample</code></b> several times.
|
<b><code>sample.persistence.PersistentActorFailureExample</code></b> several times.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -121,35 +96,20 @@ command.
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h2>Processor views</h2>
|
<h2>Processor views</h2>
|
||||||
|
<h2>Persistent actor views</h2>
|
||||||
<p>
|
<p>
|
||||||
<a href="#code/src/main/scala/sample/persistence/ViewExample.scala" class="shortcut">ViewExample.scala</a> demonstrates
|
<a href="#code/src/main/scala/sample/persistence/ViewExample.scala" class="shortcut">ViewExample.scala</a> demonstrates
|
||||||
how a view (<code>ExampleView</code>) is updated with the persistent message stream of a processor
|
how a view (<code>ExampleView</code>) is updated with the persistent message stream of a persistent actor
|
||||||
(<code>ExampleProcessor</code>). Messages sent to the processor are read from <code>stdin</code>. Views also support
|
(<code>ExamplePersistentActor</code>). Messages sent to the persistent actor are scheduled periodically. Views also support
|
||||||
snapshotting and can be used in combination with channels in the same way as processors.
|
snapshotting to reduce recovery time.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
To run this example, go to the <a href="#run" class="shortcut">Run</a> tab, and run the application main class
|
To run this example, go to the <a href="#run" class="shortcut">Run</a> tab, and run the application main class
|
||||||
<b><code>sample.persistence.ViewExample</code></b>.
|
<b><code>sample.persistence.ViewExample</code></b>.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
|
||||||
Views can also receive events that have been persisted by event sourced processors (not shown).
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
|
||||||
<h2>Processor conversation recovery</h2>
|
|
||||||
<p>
|
|
||||||
<a href="#code/src/main/scala/sample/persistence/ConversationRecoveryExample.scala" class="shortcut">ConversationRecoveryExample.scala</a>
|
|
||||||
defines two processors that send messages to each other via channels. The reliable delivery properties of channels,
|
|
||||||
in combination with processors, allow these processors to automatically resume their conversation after a JVM crash.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
To run this example, go to the <a href="#run" class="shortcut">Run</a> tab, and run the application main class
|
|
||||||
<b><code>sample.persistence.ConversationRecoveryExample</code></b> several times.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue