Akka程序的特点之一就是高弹性或者强韧性(resilient)的,因为Actor具有自我修复的能力。当Actor模式的程序出现中断情况如:系统崩溃、人为终结等,系统在重启后有关Actor可以恢复之前的状态。这项功能是通过PersistentActor实现的。PersistentActor是一种特殊的带有内部状态的Actor,它是以事件来源模式(event-sourcing)来进行内部状态持久化的。持久化是指存入由文件或数据库形成的日志。事件来源模式具体的工作原理原则上就是把使状态产生变化的事件按发生时间顺序持久化,而不是把当前整个状态存储起来。在恢复状态时把日志中这些事件按原来的时间顺序重演一遍回到原来的状态。但为了减少事件的长久积存和加快恢复(recovery)效率,PersistentActor也可以在一个时间间隔后把完整的当前状态作为快照(snapshop)存储起来。这样在状态恢复时就可以最新的快照为起点,只需对日志中快照时间之后的事件进行重演。按照事件来源模式,PersistentActor在收到外部指令消息后首先对指令进行验证然后把命令转换成更新状态的事件,成功存储事件后再更新当前状态。指令验证的目的是保证指令所产生的事件在更新状态时不会造成异常(exception)。在事件持久化之前经过了验证可以保证造成系统异常的事件不会被写入日志从而造成同样异常在恢复过程中的不断重演。

PersistentActor trait定义如下:

/**
* Scala API: A persistent Actor - can be used to implement command or event sourcing.
*/
trait PersistentActor extends Eventsourced with PersistenceIdentity {
def receive = receiveCommand /**
* Asynchronously persists `event`. On successful persistence, `handler` is called with the
* persisted event. It is guaranteed that no new commands will be received by a persistent actor
* between a call to `persist` and the execution of its `handler`. This also holds for
* multiple `persist` calls per received command. Internally, this is achieved by stashing new
* commands and unstashing them when the `event` has been persisted and handled. The stash used
* for that is an internal stash which doesn't interfere with the inherited user stash.
*
* An event `handler` may close over persistent actor state and modify it. The `sender` of a persisted
* event is the sender of the corresponding command. This means that one can reply to a command
* sender within an event `handler`.
*
* Within an event handler, applications usually update persistent actor state using persisted event
* data, notify listeners and reply to command senders.
*
* If persistence of an event fails, [[#onPersistFailure]] will be invoked and the actor will
* unconditionally be stopped. The reason that it cannot resume when persist fails is that it
* is unknown if the even was actually persisted or not, and therefore it is in an inconsistent
* state. Restarting on persistent failures will most likely fail anyway, since the journal
* is probably unavailable. It is better to stop the actor and after a back-off timeout start
* it again.
*
* @param event event to be persisted
* @param handler handler for each persisted `event`
*/
def persist[A](event: A)(handler: A ⇒ Unit): Unit = {
internalPersist(event)(handler)
} /**
* Asynchronously persists `events` in specified order. This is equivalent to calling
* `persist[A](event: A)(handler: A => Unit)` multiple times with the same `handler`,
* except that `events` are persisted atomically with this method.
*
* @param events events to be persisted
* @param handler handler for each persisted `events`
*/
def persistAll[A](events: immutable.Seq[A])(handler: A ⇒ Unit): Unit = {
internalPersistAll(events)(handler)
} /**
* Asynchronously persists `event`. On successful persistence, `handler` is called with the
* persisted event.
*
* Unlike `persist` the persistent actor will continue to receive incoming commands between the
* call to `persist` and executing it's `handler`. This asynchronous, non-stashing, version of
* of persist should be used when you favor throughput over the "command-2 only processed after
* command-1 effects' have been applied" guarantee, which is provided by the plain `persist` method.
*
* An event `handler` may close over persistent actor state and modify it. The `sender` of a persisted
* event is the sender of the corresponding command. This means that one can reply to a command
* sender within an event `handler`.
*
* If persistence of an event fails, [[#onPersistFailure]] will be invoked and the actor will
* unconditionally be stopped. The reason that it cannot resume when persist fails is that it
* is unknown if the even was actually persisted or not, and therefore it is in an inconsistent
* state. Restarting on persistent failures will most likely fail anyway, since the journal
* is probably unavailable. It is better to stop the actor and after a back-off timeout start
* it again.
*
* @param event event to be persisted
* @param handler handler for each persisted `event`
*/
def persistAsync[A](event: A)(handler: A ⇒ Unit): Unit = {
internalPersistAsync(event)(handler)
} /**
* Asynchronously persists `events` in specified order. This is equivalent to calling
* `persistAsync[A](event: A)(handler: A => Unit)` multiple times with the same `handler`,
* except that `events` are persisted atomically with this method.
*
* @param events events to be persisted
* @param handler handler for each persisted `events`
*/
def persistAllAsync[A](events: immutable.Seq[A])(handler: A ⇒ Unit): Unit = {
internalPersistAllAsync(events)(handler)
} /**
* Defer the handler execution until all pending handlers have been executed.
* Allows to define logic within the actor, which will respect the invocation-order-guarantee
* in respect to `persistAsync` or `persist` calls. That is, if `persistAsync` or `persist` was invoked before `deferAsync`,
* the corresponding handlers will be invoked in the same order as they were registered in.
*
* This call will NOT result in `event` being persisted, use `persist` or `persistAsync` instead
* if the given event should possible to replay.
*
* If there are no pending persist handler calls, the handler will be called immediately.
*
* If persistence of an earlier event fails, the persistent actor will stop, and the `handler`
* will not be run.
*
* @param event event to be handled in the future, when preceding persist operations have been processes
* @param handler handler for the given `event`
*/
def deferAsync[A](event: A)(handler: A ⇒ Unit): Unit = {
internalDeferAsync(event)(handler)
}
}

在这里定义了几个持久化函数persist???。persist的主要方式是保证在事件成功持久化后再运算事后程序handler。上面是PersistentActor提供的几种形式的persist。

PersistentActor又继承了EventSourced:

/**
* INTERNAL API.
*
* Scala API and implementation details of [[PersistentActor]] and [[AbstractPersistentActor]].
*/
private[persistence] trait Eventsourced extends Snapshotter with PersistenceStash with PersistenceIdentity with PersistenceRecovery {
...
/**
* Recovery handler that receives persisted events during recovery. If a state snapshot
* has been captured and saved, this handler will receive a [[SnapshotOffer]] message
* followed by events that are younger than the offered snapshot.
*
* This handler must not have side-effects other than changing persistent actor state i.e. it
* should not perform actions that may fail, such as interacting with external services,
* for example.
*
* If there is a problem with recovering the state of the actor from the journal, the error
* will be logged and the actor will be stopped.
*
* @see [[Recovery]]
*/
def receiveRecover: Receive /**
* Command handler. Typically validates commands against current state (and/or by
* communication with other actors). On successful validation, one or more events are
* derived from a command and these events are then persisted by calling `persist`.
*/
def receiveCommand: Receive
...
}

特别注意receiveRecover和receiveCommand这两个抽象函数,将由用户提供具体的实现。这两个函数代表了PersistentActor的两大功能:状态复原和消息处理。状态复原是通过receiveRecover对在日志读取过程中产生的消息进行处理实现的。而PersistentActor的receiveCommand就是普通Actor的receive消息处理函数。用户可以通过PersistentActor提供的回调(callback)函数来进行事件读取过程前的事前准备和后面的事后处理。用户可以对这些函数进行重载(override)来自定义这些处理程序,如:

  /**
* Called whenever a message replay fails. By default it logs the error.
*
* Subclass may override to customize logging.
*
* The actor is always stopped after this method has been invoked.
*
* @param cause failure cause.
* @param event the event that was processed in `receiveRecover`, if the exception
* was thrown there
*/
protected def onRecoveryFailure(cause: Throwable, event: Option[Any]): Unit =
...
/**
* Called when persist fails. By default it logs the error.
* Subclass may override to customize logging and for example send negative
* acknowledgment to sender.
*
* The actor is always stopped after this method has been invoked.
*
* Note that the event may or may not have been saved, depending on the type of
* failure.
*
* @param cause failure cause.
* @param event the event that was to be persisted
*/
protected def onPersistFailure(cause: Throwable, event: Any, seqNr: Long): Unit = {
...
/**
* Called when the journal rejected `persist` of an event. The event was not
* stored. By default this method logs the problem as a warning, and the actor continues.
* The callback handler that was passed to the `persist` method will not be invoked.
*
* @param cause failure cause
* @param event the event that was to be persisted
*/
protected def onPersistRejected(cause: Throwable, event: Any, seqNr: Long): Unit = {
...

用户也可以通过函数重载来改变日志读取行为:

trait PersistenceRecovery {
//#persistence-recovery
/**
* Called when the persistent actor is started for the first time.
* The returned [[Recovery]] object defines how the Actor will recover its persistent state before
* handling the first incoming message.
*
* To skip recovery completely return `Recovery.none`.
*/
def recovery: Recovery = Recovery()
//#persistence-recovery
}

整个恢复过程是在EventSourced.scala里下面这个函数实现的:

      override def stateReceive(receive: Receive, message: Any) = try message match {
case ReplayedMessage(p) ⇒
try {
eventSeenInInterval = true
updateLastSequenceNr(p)
Eventsourced.super.aroundReceive(recoveryBehavior, p)
} catch {
case NonFatal(t) ⇒
timeoutCancellable.cancel()
try onRecoveryFailure(t, Some(p.payload)) finally context.stop(self)
returnRecoveryPermit()
}
case RecoverySuccess(highestSeqNr) ⇒
timeoutCancellable.cancel()
onReplaySuccess() // callback for subclass implementation
sequenceNr = highestSeqNr
setLastSequenceNr(highestSeqNr)
_recoveryRunning = false
try Eventsourced.super.aroundReceive(recoveryBehavior, RecoveryCompleted)
finally transitToProcessingState()
case ReplayMessagesFailure(cause) ⇒
timeoutCancellable.cancel()
try onRecoveryFailure(cause, event = None) finally context.stop(self)
case RecoveryTick(false) if !eventSeenInInterval ⇒
timeoutCancellable.cancel()
try onRecoveryFailure(
new RecoveryTimedOut(s"Recovery timed out, didn't get event within $timeout, highest sequence number seen $lastSequenceNr"),
event = None)
finally context.stop(self)
case RecoveryTick(false) ⇒
eventSeenInInterval = false
case RecoveryTick(true) ⇒
// snapshot tick, ignore
case other ⇒
stashInternally(other)
} catch {
case NonFatal(e) ⇒
returnRecoveryPermit()
throw e
}

函数通过super.aroundReceive把消息传给了receiveRecovery:

  /**
* INTERNAL API.
*
* Can be overridden to intercept calls to this actor's current behavior.
*
* @param receive current behavior.
* @param msg current message.
*/
protected[akka] def aroundReceive(receive: Actor.Receive, msg: Any): Unit = {
// optimization: avoid allocation of lambda
if (receive.applyOrElse(msg, Actor.notHandledFun).asInstanceOf[AnyRef] eq Actor.NotHandled) {
unhandled(msg)
}
}

EventSourced又继承了PersistenceRecovery trait,所以重载recovery函数可以改变状态恢复行为。默认的模式是:

/**
* Recovery mode configuration object to be returned in [[PersistentActor#recovery]].
*
* By default recovers from latest snapshot replays through to the last available event (last sequenceId).
*
* Recovery will start from a snapshot if the persistent actor has previously saved one or more snapshots
* and at least one of these snapshots matches the specified `fromSnapshot` criteria.
* Otherwise, recovery will start from scratch by replaying all stored events.
*
* If recovery starts from a snapshot, the persistent actor is offered that snapshot with a [[SnapshotOffer]]
* message, followed by replayed messages, if any, that are younger than the snapshot, up to the
* specified upper sequence number bound (`toSequenceNr`).
*
* @param fromSnapshot criteria for selecting a saved snapshot from which recovery should start. Default
* is latest (= youngest) snapshot.
* @param toSequenceNr upper sequence number bound (inclusive) for recovery. Default is no upper bound.
* @param replayMax maximum number of messages to replay. Default is no limit.
*/
@SerialVersionUID(1L)
final case class Recovery(
fromSnapshot: SnapshotSelectionCriteria = SnapshotSelectionCriteria.Latest,
toSequenceNr: Long = Long.MaxValue,
replayMax: Long = Long.MaxValue)

下面是状态恢复过程中产生的消息:

/**
* Sent to a [[PersistentActor]] when the journal replay has been finished.
*/
@SerialVersionUID(1L)
case object RecoveryCompleted extends RecoveryCompleted {
...
final class RecoveryTimedOut(message: String) extends RuntimeException(message) with NoStackTrace

恢复进程阶段可用用下面的方法检测:

 /**
* Returns `true` if this persistent actor is currently recovering.
*/
def recoveryRunning: Boolean = {
// currentState is null if this is called from constructor
if (currentState == null) true else currentState.recoveryRunning
} /**
* Returns `true` if this persistent actor has successfully finished recovery.
*/
def recoveryFinished: Boolean = !recoveryRunning

用户可以删除日志:

  /**
* Permanently deletes all persistent messages with sequence numbers less than or equal `toSequenceNr`.
*
* If the delete is successful a [[DeleteMessagesSuccess]] will be sent to the actor.
* If the delete fails a [[DeleteMessagesFailure]] will be sent to the actor.
*
* @param toSequenceNr upper sequence number bound of persistent messages to be deleted.
*/
def deleteMessages(toSequenceNr: Long): Unit =
journal ! DeleteMessagesTo(persistenceId, toSequenceNr, self)

删除区域是用SequenceNr来代表的,下面是一些可用的序号:

  /**
* Returns `persistenceId`.
*/
override def snapshotterId: String = persistenceId /**
* Highest received sequence number so far or `0L` if this actor hasn't replayed
* or stored any persistent events yet.
*/
def lastSequenceNr: Long = _lastSequenceNr /**
* Returns `lastSequenceNr`.
*/
def snapshotSequenceNr: Long = lastSequenceNr

日志删除过程可用下面的消息监控:

/**
* Reply message to a successful [[Eventsourced#deleteMessages]] request.
*/
final case class DeleteMessagesSuccess(toSequenceNr: Long) /**
* Reply message to a failed [[Eventsourced#deleteMessages]] request.
*/
final case class DeleteMessagesFailure(cause: Throwable, toSequenceNr: Long)

日志快照的持久化维护方法清单如下:

 /**
* Snapshotter id.
*/
def snapshotterId: String /**
* Sequence number to use when taking a snapshot.
*/
def snapshotSequenceNr: Long /**
* Instructs the snapshot store to load the specified snapshot and send it via an [[SnapshotOffer]]
* to the running [[PersistentActor]].
*/
def loadSnapshot(persistenceId: String, criteria: SnapshotSelectionCriteria, toSequenceNr: Long) =
snapshotStore ! LoadSnapshot(persistenceId, criteria, toSequenceNr) /**
* Saves a `snapshot` of this snapshotter's state.
*
* The [[PersistentActor]] will be notified about the success or failure of this
* via an [[SaveSnapshotSuccess]] or [[SaveSnapshotFailure]] message.
*/
def saveSnapshot(snapshot: Any): Unit = {
snapshotStore ! SaveSnapshot(SnapshotMetadata(snapshotterId, snapshotSequenceNr), snapshot)
} /**
* Deletes the snapshot identified by `sequenceNr`.
*
* The [[PersistentActor]] will be notified about the status of the deletion
* via an [[DeleteSnapshotSuccess]] or [[DeleteSnapshotFailure]] message.
*/
def deleteSnapshot(sequenceNr: Long): Unit = {
snapshotStore ! DeleteSnapshot(SnapshotMetadata(snapshotterId, sequenceNr))
} /**
* Deletes all snapshots matching `criteria`.
*
* The [[PersistentActor]] will be notified about the status of the deletion
* via an [[DeleteSnapshotsSuccess]] or [[DeleteSnapshotsFailure]] message.
*/
def deleteSnapshots(criteria: SnapshotSelectionCriteria): Unit = {
snapshotStore ! DeleteSnapshots(snapshotterId, criteria)
}

快照维护数据结构和消息如下:

/**
* Snapshot metadata.
*
* @param persistenceId id of persistent actor from which the snapshot was taken.
* @param sequenceNr sequence number at which the snapshot was taken.
* @param timestamp time at which the snapshot was saved, defaults to 0 when unknown.
*/
@SerialVersionUID(1L) //#snapshot-metadata
final case class SnapshotMetadata(persistenceId: String, sequenceNr: Long, timestamp: Long = 0L)
//#snapshot-metadata object SnapshotMetadata {
implicit val ordering: Ordering[SnapshotMetadata] = Ordering.fromLessThan[SnapshotMetadata] { (a, b) ⇒
if (a eq b) false
else if (a.persistenceId != b.persistenceId) a.persistenceId.compareTo(b.persistenceId) <
else if (a.sequenceNr != b.sequenceNr) a.sequenceNr < b.sequenceNr
else if (a.timestamp != b.timestamp) a.timestamp < b.timestamp
else false
}
} /**
* Sent to a [[PersistentActor]] after successful saving of a snapshot.
*
* @param metadata snapshot metadata.
*/
@SerialVersionUID(1L)
final case class SaveSnapshotSuccess(metadata: SnapshotMetadata)
extends SnapshotProtocol.Response /**
* Sent to a [[PersistentActor]] after successful deletion of a snapshot.
*
* @param metadata snapshot metadata.
*/
@SerialVersionUID(1L)
final case class DeleteSnapshotSuccess(metadata: SnapshotMetadata)
extends SnapshotProtocol.Response /**
* Sent to a [[PersistentActor]] after successful deletion of specified range of snapshots.
*
* @param criteria snapshot selection criteria.
*/
@SerialVersionUID(1L)
final case class DeleteSnapshotsSuccess(criteria: SnapshotSelectionCriteria)
extends SnapshotProtocol.Response /**
* Sent to a [[PersistentActor]] after failed saving of a snapshot.
*
* @param metadata snapshot metadata.
* @param cause failure cause.
*/
@SerialVersionUID(1L)
final case class SaveSnapshotFailure(metadata: SnapshotMetadata, cause: Throwable)
extends SnapshotProtocol.Response /**
* Sent to a [[PersistentActor]] after failed deletion of a snapshot.
*
* @param metadata snapshot metadata.
* @param cause failure cause.
*/
@SerialVersionUID(1L)
final case class DeleteSnapshotFailure(metadata: SnapshotMetadata, cause: Throwable)
extends SnapshotProtocol.Response /**
* Sent to a [[PersistentActor]] after failed deletion of a range of snapshots.
*
* @param criteria snapshot selection criteria.
* @param cause failure cause.
*/
@SerialVersionUID(1L)
final case class DeleteSnapshotsFailure(criteria: SnapshotSelectionCriteria, cause: Throwable)
extends SnapshotProtocol.Response /**
* Offers a [[PersistentActor]] a previously saved `snapshot` during recovery. This offer is received
* before any further replayed messages.
*/
@SerialVersionUID(1L)
final case class SnapshotOffer(metadata: SnapshotMetadata, snapshot: Any) /**
* Selection criteria for loading and deleting snapshots.
*
* @param maxSequenceNr upper bound for a selected snapshot's sequence number. Default is no upper bound,
* i.e. `Long.MaxValue`
* @param maxTimestamp upper bound for a selected snapshot's timestamp. Default is no upper bound,
* i.e. `Long.MaxValue`
* @param minSequenceNr lower bound for a selected snapshot's sequence number. Default is no lower bound,
* i.e. `0L`
* @param minTimestamp lower bound for a selected snapshot's timestamp. Default is no lower bound,
* i.e. `0L`
*
* @see [[Recovery]]
*/
@SerialVersionUID(1L)
final case class SnapshotSelectionCriteria(
maxSequenceNr: Long = Long.MaxValue,
maxTimestamp: Long = Long.MaxValue,
minSequenceNr: Long = 0L,
minTimestamp: Long = 0L) { /**
* INTERNAL API.
*/
private[persistence] def limit(toSequenceNr: Long): SnapshotSelectionCriteria =
if (toSequenceNr < maxSequenceNr) copy(maxSequenceNr = toSequenceNr) else this /**
* INTERNAL API.
*/
private[persistence] def matches(metadata: SnapshotMetadata): Boolean =
metadata.sequenceNr <= maxSequenceNr && metadata.timestamp <= maxTimestamp &&
metadata.sequenceNr >= minSequenceNr && metadata.timestamp >= minTimestamp
} object SnapshotSelectionCriteria {
/**
* The latest saved snapshot.
*/
val Latest = SnapshotSelectionCriteria() /**
* No saved snapshot matches.
*/
val None = SnapshotSelectionCriteria(0L, 0L) /**
* Java API.
*/
def create(maxSequenceNr: Long, maxTimestamp: Long) =
SnapshotSelectionCriteria(maxSequenceNr, maxTimestamp) /**
* Java API.
*/
def create(maxSequenceNr: Long, maxTimestamp: Long,
minSequenceNr: Long, minTimestamp: Long) =
SnapshotSelectionCriteria(maxSequenceNr, maxTimestamp, minSequenceNr, minTimestamp) /**
* Java API.
*/
def latest() = Latest /**
* Java API.
*/
def none() = None
} /**
* Plugin API: a selected snapshot matching [[SnapshotSelectionCriteria]].
*
* @param metadata snapshot metadata.
* @param snapshot snapshot.
*/
final case class SelectedSnapshot(metadata: SnapshotMetadata, snapshot: Any) object SelectedSnapshot {
/**
* Java API, Plugin API.
*/
def create(metadata: SnapshotMetadata, snapshot: Any): SelectedSnapshot =
SelectedSnapshot(metadata, snapshot)
} /**
* INTERNAL API.
*
* Defines messages exchanged between persistent actors and a snapshot store.
*/
private[persistence] object SnapshotProtocol { /** Marker trait shared by internal snapshot messages. */
sealed trait Message extends Protocol.Message
/** Internal snapshot command. */
sealed trait Request extends Message
/** Internal snapshot acknowledgement. */
sealed trait Response extends Message /**
* Instructs a snapshot store to load a snapshot.
*
* @param persistenceId persistent actor id.
* @param criteria criteria for selecting a snapshot from which recovery should start.
* @param toSequenceNr upper sequence number bound (inclusive) for recovery.
*/
final case class LoadSnapshot(persistenceId: String, criteria: SnapshotSelectionCriteria, toSequenceNr: Long)
extends Request /**
* Response message to a [[LoadSnapshot]] message.
*
* @param snapshot loaded snapshot, if any.
*/
final case class LoadSnapshotResult(snapshot: Option[SelectedSnapshot], toSequenceNr: Long)
extends Response /**
* Reply message to a failed [[LoadSnapshot]] request.
* @param cause failure cause.
*/
final case class LoadSnapshotFailed(cause: Throwable) extends Response /**
* Instructs snapshot store to save a snapshot.
*
* @param metadata snapshot metadata.
* @param snapshot snapshot.
*/
final case class SaveSnapshot(metadata: SnapshotMetadata, snapshot: Any)
extends Request /**
* Instructs snapshot store to delete a snapshot.
*
* @param metadata snapshot metadata.
*/
final case class DeleteSnapshot(metadata: SnapshotMetadata)
extends Request /**
* Instructs snapshot store to delete all snapshots that match `criteria`.
*
* @param persistenceId persistent actor id.
* @param criteria criteria for selecting snapshots to be deleted.
*/
final case class DeleteSnapshots(persistenceId: String, criteria: SnapshotSelectionCriteria)
extends Request
}

好了,下面我们再分析一下这个事件来源模式(event-sourcing):首先对指令消息进行验证,然后把指令转换成事件,把事件写入日志然后再更新内部状态。其中验证是为了保证该事件在更新内部状态时不会出现异常。这个验证必须在事件写入日志前,否则造成异常的事件就会被写入日志,就会在恢复时的事件重演中重复发生异常。我们用个例子来示范如何来设计指令消息到事件的转换的:

import akka.actor._
import akka.persistence._ object Calculator {
sealed trait Command
case class Operand(x: Int) extends Command
case class Add(x: Int) extends Command
case class Sub(x: Int) extends Command
case class Mul(x: Int) extends Command
case class Div(x: Int) extends Command
case class ShowResult(x: Double) extends Command sealed trait Event
case class SetNum(x: Int) extends Event
case class Added(x: Int) extends Event
case class Subtracted(x: Int) extends Event
case class Multiplied(x: Int) extends Event
case class Divided(x: Int) extends Event case class State(result: Int) {
def updateState(evt: Event): State = evt match {
case SetNum(x) => copy(result = x)
case Added(x) => copy(result = this.result + x)
case Subtracted(x) => copy(result = this.result - x)
case Multiplied(x) => copy(result = this.result * x)
case Divided(x) => copy(result = this.result / x)
}
}
}
class Calculator extends PersistentActor with ActorLogging {
import Calculator._
var state: State = State() override def persistenceId: String = "persistence-actor"
override def receiveCommand: Receive = {
case Operand(x) =>
persist(SetNum(x)){evt => state = state.updateState(evt)}
case Add(x) =>
persist(Added(x)){evt => state = state.updateState(evt)}
case Sub(x) =>
persist(Subtracted(x)){evt => state = state.updateState(evt)}
case Mul(x) =>
persist(Multiplied(x)){evt => state = state.updateState(evt)}
case Divided(x) if (x != ) =>
persist(Added(x)){evt => state = state.updateState(evt)}
} override def receiveRecover: Receive = {
case evt: Event => state = state.updateState(evt)
case SnapshotOffer(_, sts: State) => state = sts.copy(sts.result)
}
}

以上代码基本上进行了Command和Event的直接对应。这是一种比较直观的关系对应方式。我们注意到只有在收到Div(x)指令时才进行了指令验证(x == 0)。因为这个例子比较简单,所以我们可以肯定只有指令Div对状态进行施用时才有可能造成异常。这样才能使我们比较直观地进行Command与Event关系对应。假如对内部状态的更新涉及到一些非常复杂的算法,我们无法肯定哪个指令会产生异常,那我们只有先进行运算指令得出一个结果,然后直接替换状态,这个动作肯定是安全的了。按这个逻辑,我们把上面的例子调整一下:

import akka.actor._
import akka.persistence._ object Calculator {
sealed trait Command
case class Operand(x: Int) extends Command
case class Add(x: Int) extends Command
case class Sub(x: Int) extends Command
case class Mul(x: Int) extends Command
case class Div(x: Int) extends Command
case class ShowResult(x: Int) extends Command sealed trait Event
case class SetResult(x: Int) extends Event def getResult(res: Int, cmd: Command): Int = cmd match {
case Operand(x) => x
case Add(x) => res + x
case Sub(x) => res - x
case Mul(x) => res * x
case Div(x) => res / x
case _ =>
} case class State(result: Int) {
def updateState(evt: Event): State = evt match {
case SetResult(x) => copy(result = x)
}
}
}
class Calculator extends PersistentActor with ActorLogging {
import Calculator._
var state: State = State() override def persistenceId: String = "persistence-actor"
override def receiveCommand: Receive = {
case opr: Operand =>
persist(SetResult(getResult(state.result,opr)))(evt => state = state.updateState(evt))
case add: Add =>
persist(SetResult(getResult(state.result,add)))(evt => state = state.updateState(evt))
case sub: Sub =>
persist(SetResult(getResult(state.result,sub)))(evt => state = state.updateState(evt))
case mul: Mul =>
persist(SetResult(getResult(state.result,mul)))(evt => state = state.updateState(evt))
case div: Div =>
persist(SetResult(getResult(state.result,div)))(evt => state = state.updateState(evt)) } override def onPersistFailure(cause: Throwable, event: Any, seqNr: Long): Unit = {
log.info(s"Persistence Error: ${cause.getMessage}")
}
override def receiveRecover: Receive = {
case evt: Event => state = state.updateState(evt)
case SnapshotOffer(_, sts: State) => state = sts.copy(sts.result)
}
}

现在在写入日志前进行了验证。注意如果验证结果异常可以在onPersistFailure中处理。

下面我们就在Calculator里增加一些消息发布机制把恢复过程、消息处理过程和当前状态都发布出去:

class Calculator extends PersistentActor with ActorLogging {
import Calculator._
var state: State = State() override def persistenceId: String = "persistence-actor"
val snapShotInterval =
override def receiveCommand: Receive = {
case Operand(x) => persist(SetNum(x))(handleEvent)
case Add(x) => persist(Added(x))(handleEvent)
case Sub(x) => persist(Subtracted(x))(handleEvent)
case Mul(x) => persist(Multiplied(x))(handleEvent)
case Div(x) if (x != ) => persist(Divided(x))(handleEvent) case ShowResult =>
context.system.eventStream.publish(LogMessage(s"Current state: $state"))
case BackupResult =>
saveSnapshot(state)
context.system.eventStream.publish(LogMessage(s"Manual saving snapshot: $state")) case SaveSnapshotSuccess(metadata) =>
context.system.eventStream.publish(LogMessage(s"Successfully saved state: $state"))
case SaveSnapshotFailure(metadata, reason) =>
context.system.eventStream.publish(LogMessage(s"Saving state: $state failed!")) }
def handleEvent(evt: Event) = { //update state and publish progress
state = state.updateState(evt)
context.system.eventStream.publish(LogMessage(s"Logged event: $evt"))
if (lastSequenceNr % snapShotInterval == && lastSequenceNr != ) {
saveSnapshot(state)
context.system.eventStream.publish(LogMessage(s"Saving snapshot: $state after $snapShotInterval events"))
}
}
override def receiveRecover: Receive = {
case evt: Event => {
state = state.updateState(evt)
context.system.eventStream.publish(LogMessage(s"Restoring event: $evt"))
}
case SnapshotOffer(mdata, sts: State) => {
state = sts.copy(sts.result)
context.system.eventStream.publish(LogMessage(s"Restoring snapshot: $mdata"))
}
case RecoveryCompleted => log.info(s"Recovery completed with starting state: $state")
} override def onPersistRejected(cause: Throwable, event: Any, seqNr: Long): Unit = {
log.info(s"Persistence Rejected: ${cause.getMessage}")
}
override def onPersistFailure(cause: Throwable, event: Any, seqNr: Long): Unit = {
log.info(s"Persistence Error: ${cause.getMessage}")
}
}

发布消息内容已经可以说明每段代码的作用。

下面是测试运算Calculator的代码:

package persistence.demo
import akka.actor._
import persistence.calculator.Calculator
import persistence.tracker.EventTracker object persistenceDemo extends App {
val persistenceSystem = ActorSystem("persistenceSystem") persistenceSystem.actorOf(EventTracker.props,"stateTeller")
val calculator = persistenceSystem.actorOf(Calculator.props,"Calculator") calculator ! Calculator.Add()
calculator ! Calculator.Add()
calculator ! Calculator.Mul()
calculator ! Calculator.Div()
calculator ! Calculator.Sub()
calculator ! Calculator.Mul()
calculator ! Calculator.ShowResult scala.io.StdIn.readLine() persistenceSystem.terminate() }

连续几次运算的结果显示如下:

Restoring event: Divided()
Restoring event: Subtracted()
Restoring event: Multiplied()
[INFO] [// ::51.593] [persistenceSystem-akka.actor.default-dispatcher-] [akka://persistenceSystem/user/Calculator] Recovery completed with starting state: State(732)
Logged event: Added()
Logged event: Added()
Saving snapshot: State() after events
Logged event: Multiplied()
Logged event: Divided()
Logged event: Subtracted()
Logged event: Multiplied()
Current state: State()
Successfully saved state: State()
...
Restoring event: Multiplied()
Restoring event: Divided()
Restoring event: Subtracted()
Restoring event: Multiplied()
[INFO] [// ::50.068] [persistenceSystem-akka.actor.default-dispatcher-] [akka://persistenceSystem/user/Calculator] Recovery completed with starting state: State(13260)
Logged event: Added()
Saving snapshot: State() after events
Logged event: Added()
Logged event: Multiplied()
Logged event: Divided()
Logged event: Subtracted()
Logged event: Multiplied()
Saving snapshot: State() after events
Current state: State()
Successfully saved state: State()
Successfully saved state: State()
...
Restoring snapshot: SnapshotMetadata(persistence-actor,,)
[INFO] [// ::20.585] [persistenceSystem-akka.actor.default-dispatcher-] [akka://persistenceSystem/user/Calculator] Recovery completed with starting state: State(238764)
::20.730 [persistenceSystem-cassandra-plugin-default-dispatcher-] INFO com.datastax.driver.core.utils.UUIDs - PID obtained through native call to getpid():
Logged event: Added()
Logged event: Added()
Logged event: Multiplied()
Logged event: Divided()
Logged event: Subtracted()
Saving snapshot: State() after events
Logged event: Multiplied()
Current state: State()
Successfully saved state: State()

从以上的运算结果来看:在系统重启后持久化的状态正确得到了恢复。

下面是本次示范的源代码:

build.sbt

name := "persistence-actor"

version := "1.0"

scalaVersion := "2.11.9"

sbtVersion := "0.13.5"

libraryDependencies ++= Seq(
"com.typesafe.akka" %% "akka-actor" % "2.5.3",
"com.typesafe.akka" %% "akka-persistence" % "2.5.3",
"ch.qos.logback" % "logback-classic" % "1.1.7",
"com.typesafe.akka" %% "akka-persistence-cassandra" % "0.54",
"com.typesafe.akka" %% "akka-persistence-cassandra-launcher" % "0.54" % Test
)

application.conf

akka {
persistence {
journal.plugin = "cassandra-journal"
snapshot-store.plugin = "cassandra-snapshot-store"
}
}
akka.actor.warn-about-java-serializer-usage = off

Calculator.scala

package persistence.calculator
import akka.actor._
import akka.persistence._ object Calculator {
sealed trait Command
case class Operand(x: Int) extends Command
case class Add(x: Int) extends Command
case class Sub(x: Int) extends Command
case class Mul(x: Int) extends Command
case class Div(x: Int) extends Command
case class ShowResult(x: Double) extends Command
case object BackupResult extends Command //saveSnapshot sealed trait Event
case class SetNum(x: Int) extends Event
case class Added(x: Int) extends Event
case class Subtracted(x: Int) extends Event
case class Multiplied(x: Int) extends Event
case class Divided(x: Int) extends Event case class State(result: Int) {
def updateState(evt: Event): State = evt match {
case SetNum(x) => copy(result = x)
case Added(x) => copy(result = this.result + x)
case Subtracted(x) => copy(result = this.result - x)
case Multiplied(x) => copy(result = this.result * x)
case Divided(x) => copy(result = this.result / x)
}
} case class LogMessage(msg: String) //broadcase message type
def props = Props(new Calculator)
}
class Calculator extends PersistentActor with ActorLogging {
import Calculator._
var state: State = State() override def persistenceId: String = "persistence-actor"
val snapShotInterval =
override def receiveCommand: Receive = {
case Operand(x) => persist(SetNum(x))(handleEvent)
case Add(x) => persist(Added(x))(handleEvent)
case Sub(x) => persist(Subtracted(x))(handleEvent)
case Mul(x) => persist(Multiplied(x))(handleEvent)
case Div(x) if (x != ) => persist(Divided(x))(handleEvent) case ShowResult =>
context.system.eventStream.publish(LogMessage(s"Current state: $state"))
case BackupResult =>
saveSnapshot(state)
context.system.eventStream.publish(LogMessage(s"Manual saving snapshot: $state")) case SaveSnapshotSuccess(metadata) =>
context.system.eventStream.publish(LogMessage(s"Successfully saved state: $state"))
case SaveSnapshotFailure(metadata, reason) =>
context.system.eventStream.publish(LogMessage(s"Saving state: $state failed!")) }
def handleEvent(evt: Event) = { //update state and publish progress
state = state.updateState(evt)
context.system.eventStream.publish(LogMessage(s"Producing event: $evt"))
if (lastSequenceNr % snapShotInterval == && lastSequenceNr != ) {
saveSnapshot(state)
context.system.eventStream.publish(LogMessage(s"Saving snapshot: $state after $snapShotInterval events"))
}
}
override def receiveRecover: Receive = {
case evt: Event => {
state = state.updateState(evt)
context.system.eventStream.publish(LogMessage(s"Restoring event: $evt"))
}
case SnapshotOffer(mdata, sts: State) => {
state = sts.copy(sts.result)
context.system.eventStream.publish(LogMessage(s"Restoring snapshot: $mdata"))
}
case RecoveryCompleted => log.info(s"Recovery completed with starting state: $state")
} override def onPersistRejected(cause: Throwable, event: Any, seqNr: Long): Unit = {
log.info(s"Persistence Rejected: ${cause.getMessage}")
}
override def onPersistFailure(cause: Throwable, event: Any, seqNr: Long): Unit = {
log.info(s"Persistence Error: ${cause.getMessage}")
}
}

StateTracker.scala

package persistence.tracker
import akka.actor._
import persistence.calculator.Calculator
object EventTracker {
def props = Props(new EventTracker)
}
class EventTracker extends Actor {
override def preStart(): Unit = {
context.system.eventStream.subscribe(self,classOf[Calculator.LogMessage])
super.preStart()
} override def postStop(): Unit = {
context.system.eventStream.unsubscribe(self)
super.postStop()
} override def receive: Receive = {
case Calculator.LogMessage(msg) => println(msg)
} }

persistenceDemo.scala

package persistence.demo
import akka.actor._
import persistence.calculator.Calculator
import persistence.tracker.EventTracker object persistenceDemo extends App {
val persistenceSystem = ActorSystem("persistenceSystem") persistenceSystem.actorOf(EventTracker.props,"stateTeller")
val calculator = persistenceSystem.actorOf(Calculator.props,"Calculator") calculator ! Calculator.Add()
calculator ! Calculator.Add()
calculator ! Calculator.Mul()
calculator ! Calculator.Div()
calculator ! Calculator.Sub()
calculator ! Calculator.Mul()
calculator ! Calculator.ShowResult scala.io.StdIn.readLine() persistenceSystem.terminate() }

Akka(14): 持久化模式:PersistentActor的更多相关文章

  1. Redis 2种持久化模式的缺陷

    http://blog.csdn.net/hexieshangwang/article/details/47254087 一.RDB持久化模式缺陷 1.问题描述: 并发200路,模拟不断写Redis, ...

  2. Akka(15): 持久化模式:AtLeastOnceDelivery-消息保证送达模式

    消息保证送达是指消息发送方保证在任何情况下都会至少一次确定的消息送达.AtleastOnceDelivery是一个独立的trait,主要作用是对不确定已送达的消息进行补发,这是一种自动的操作,无需用户 ...

  3. Akka(16): 持久化模式:PersistentFSM-可以自动修复的状态机器

    前面我们讨论过FSM,一种专门为维护内部状态而设计的Actor,它的特点是一套特殊的DSL能很方便地进行状态转换.FSM的状态转换模式特别适合对应现实中的业务流程,因为它那套DSL可以更形象的描述业务 ...

  4. 14.命令模式(Command Pattern)

    耦合与变化:    耦合是软件不能抵御变化灾难的根本性原因.不仅实体对象与实体对象之间存在耦合关系,实体对象与行为操作之间也存在耦合关系.                                ...

  5. 设计模式-14 MVC模式

    一 MVC设计模式 MVC 模式代表 Model-View-Controller(模型-视图-控制器) 模式,它是一个存在于服务器 表达层的模型,它将应用分开,改变应用之间的高度耦合 MVC设计模式将 ...

  6. 14.模板方法模式(Template Method)

    using System; namespace ConsoleApplication7 { class Program { /// <summary> /// 模板方法模式——在一个抽象类 ...

  7. C#设计模式(14)——模板方法模式(Template Method)

    一.引言 提到模板,大家肯定不免想到生活中的“简历模板”.“论文模板”.“Word中模版文件”等,在现实生活中,模板的概念就是——有一个规定的格式,然后每个人都可以根据自己的需求或情况去更新它,例如简 ...

  8. JS常用的设计模式(14)—— 备忘录模式

    备忘录模式在js中经常用于数据缓存. 比如一个分页控件, 从服务器获得某一页的数据后可以存入缓存.以后再翻回这一页的时候,可以直接使用缓存里的数据而无需再次请求服务器. 实现比较简单,伪代码: var ...

  9. [设计模式] 14 命令模式 Command

    Command 模式通过将请求封装到一个对象(Command)中,并将请求的接受者存放到具体的 ConcreteCommand 类中(Receiver)中,从而实现调用操作的对象和操作的具体实现者之间 ...

随机推荐

  1. Swoole笔记(三)

    WebSocket 使用Swoole可以很简单的搭建异步非阻塞多进程的WebSocket服务器. WebSocket服务器 <?php $server = new swoole_websocke ...

  2. IBM WebSphere ESB入门指南

    [TOC] 第一章 ESB介绍 本博客介绍一款ESB产品,IBM WebSphere ESB.ESB(Enterprise Service Bus)也即企业服务总线.ESB有很多产品,IBM的IBM ...

  3. 每篇半小时1天入门MongoDB——2.MongoDB环境变量配置和Shell操作

    上一篇:每篇半小时1天入门MongoDB——1.MongoDB介绍和安装 配置环境变量 Win10系统为例 右键单击“此电脑”——属性——高级系统设置——高级——环境变量,添加C:\Program F ...

  4. oracle日期时间函数 总结

    表中存在伪列:sysdate,systimestamp 伪列存在但是不显示 select sysdate from dual; select systimestamp from dual; 日期计算公 ...

  5. php 中的closure用法

    Closure,匿名函数,是php5.3的时候引入的,又称为Anonymous functions.字面意思也就是没有定义名字的函数.比如以下代码(文件名是do.php) <?php funct ...

  6. 水平方向的RecyclerView

    最近做了一个项目需要实现一个卡片式的水平滑动,但是不能手势滑动,点击卡片上的按钮之后滑动到下一个卡片,所以想到用RecyclerView实现,去掉它的手势滑动,点击按钮之后再代码控制滑动到下一个卡片. ...

  7. python学习-面向对象

    面向对象 编程方式的区别 过程式编程 函数式编程 面向对象式编程 面向对象编程 对象是类的一个实例 创建 class foo(): def __init__(self): #类的构造方法 pass d ...

  8. ArcGIS API for JavaScript根据两个点坐标在地图上画线

    ArcGIS API for JavaScript根据两个点坐标在地图上画线比如说a(xxxx,xxxxx),b(xxxx,xxxxx).利用这两个点画一条线 var polyline = new e ...

  9. PHP基础入门(四)---PHP数组实用基础知识

    PHP数组 数组是特殊的变量,它可以同时保存一个以上的值. ***关键词:数组基础.数组遍历.超全局数组.数组功能.数组函数. 下面来和大家分享一下有关PHP的数组基础知识,希望对你PHP的学习有所帮 ...

  10. centos yum 没有可用软件包 nginx。

    新装的centos7中没有nginx的镜像源 因为nginx位于第三方的yum源里面,而不在centos官方yum源里面 解决方案: 安装epel: 去epel官网: http://fedorapro ...