!per #3707 Channel enhancements
- Persistent channel - ConfirmablePersistent message type delivered by channel - Sender resolution performance improvements * unstash() instead of unstashAll() These enhancements required the following changes - Unified implementation of processor stash and user stash - Persistence message plugin API separated from implementation - Physical deletion of messages
This commit is contained in:
parent
8fb59a0bc6
commit
ba9fc4da46
41 changed files with 2167 additions and 722 deletions
|
|
@ -4,13 +4,15 @@
|
|||
|
||||
package akka.persistence
|
||||
|
||||
import akka.AkkaException
|
||||
import akka.actor._
|
||||
|
||||
import akka.persistence.serialization.Message
|
||||
|
||||
/**
|
||||
* A channel is used by [[Processor]]s for sending received persistent messages to destinations.
|
||||
* It prevents redundant delivery of messages to these destinations when a processor is recovered
|
||||
* i.e. receives replayed messages. This requires that channel destinations confirm the receipt of
|
||||
* persistent messages by calling `confirm()` on the [[Persistent]] message.
|
||||
* A channel is used by [[Processor]]s for sending [[Persistent]] messages to destinations. The main
|
||||
* responsibility of a channel is to prevent redundant delivery of replayed messages to destinations
|
||||
* when a processor is recovered.
|
||||
*
|
||||
* A channel can be instructed to deliver a persistent message to a `destination` via the [[Deliver]]
|
||||
* command.
|
||||
|
|
@ -45,56 +47,69 @@ import akka.actor._
|
|||
* }
|
||||
* }}}
|
||||
*
|
||||
* Redundant delivery of messages to destinations is only prevented if the receipt of these messages
|
||||
* is explicitly confirmed. Therefore, persistent messages that are delivered via a channel are of type
|
||||
* [[ConfirmablePersistent]]. Their receipt can be confirmed by a destination by calling the `confirm()`
|
||||
* method on these messages.
|
||||
*
|
||||
* {{{
|
||||
* class MyDestination extends Actor {
|
||||
* def receive = {
|
||||
* case cp @ ConfirmablePersistent(payload, sequenceNr) => cp.confirm()
|
||||
* }
|
||||
* }
|
||||
* }}}
|
||||
*
|
||||
* A channel will only re-deliver messages if the sending processor is recovered and delivery of these
|
||||
* messages has not been confirmed yet. Hence, a channel can be used to avoid message loss in case of
|
||||
* sender JVM crashes, for example. A channel, however, does not attempt any re-deliveries should a
|
||||
* destination be unavailable. Re-delivery to destinations (in case of network failures or destination
|
||||
* JVM crashes) is an application-level concern and can be done by using a reliable proxy, for example.
|
||||
*
|
||||
* @see [[Deliver]]
|
||||
*/
|
||||
class Channel private (_channelId: Option[String]) extends Actor with Stash {
|
||||
sealed class Channel private[akka] (_channelId: Option[String]) extends Actor with Stash {
|
||||
private val extension = Persistence(context.system)
|
||||
private val id = _channelId match {
|
||||
case Some(cid) ⇒ cid
|
||||
case None ⇒ extension.channelId(self)
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new channel with a generated channel id.
|
||||
*/
|
||||
def this() = this(None)
|
||||
|
||||
/**
|
||||
* Creates a new channel with specified channel id.
|
||||
*
|
||||
* @param channelId channel id.
|
||||
*/
|
||||
def this(channelId: String) = this(Some(channelId))
|
||||
|
||||
import ResolvedDelivery._
|
||||
|
||||
private val delivering: Actor.Receive = {
|
||||
case Deliver(persistent: PersistentImpl, destination, resolve) ⇒ {
|
||||
case Deliver(persistent: PersistentRepr, destination, resolve) ⇒ {
|
||||
if (!persistent.confirms.contains(id)) {
|
||||
val msg = persistent.copy(channelId = id,
|
||||
confirmTarget = extension.journalFor(persistent.processorId),
|
||||
confirmMessage = Confirm(persistent.processorId, persistent.sequenceNr, id))
|
||||
val prepared = prepareDelivery(persistent)
|
||||
resolve match {
|
||||
case Resolve.Sender if !persistent.resolved ⇒ {
|
||||
context.actorOf(Props(classOf[ResolvedSenderDelivery], msg, destination, sender)) ! DeliverResolved
|
||||
case Resolve.Sender if !prepared.resolved ⇒ {
|
||||
context.actorOf(Props(classOf[ResolvedSenderDelivery], prepared, destination, sender)) ! DeliverResolved
|
||||
context.become(buffering, false)
|
||||
}
|
||||
case Resolve.Destination if !persistent.resolved ⇒ {
|
||||
context.actorOf(Props(classOf[ResolvedDestinationDelivery], msg, destination, sender)) ! DeliverResolved
|
||||
case Resolve.Destination if !prepared.resolved ⇒ {
|
||||
context.actorOf(Props(classOf[ResolvedDestinationDelivery], prepared, destination, sender)) ! DeliverResolved
|
||||
context.become(buffering, false)
|
||||
}
|
||||
case _ ⇒ destination tell (msg, sender)
|
||||
case _ ⇒ destination tell (prepared, sender)
|
||||
}
|
||||
}
|
||||
unstash()
|
||||
}
|
||||
}
|
||||
|
||||
private val buffering: Actor.Receive = {
|
||||
case DeliveredResolved | DeliveredUnresolved ⇒ { context.unbecome(); unstashAll() } // TODO: optimize
|
||||
case DeliveredResolved | DeliveredUnresolved ⇒ { context.unbecome(); unstash() }
|
||||
case _: Deliver ⇒ stash()
|
||||
}
|
||||
|
||||
def receive = delivering
|
||||
|
||||
private[akka] def prepareDelivery(persistent: PersistentRepr): PersistentRepr = {
|
||||
ConfirmablePersistentImpl(
|
||||
persistent = persistent,
|
||||
confirmTarget = extension.journalFor(persistent.processorId),
|
||||
confirmMessage = Confirm(persistent.processorId, persistent.sequenceNr, id))
|
||||
}
|
||||
}
|
||||
|
||||
object Channel {
|
||||
|
|
@ -102,7 +117,7 @@ object Channel {
|
|||
* Returns a channel configuration object for creating a [[Channel]] with a
|
||||
* generated id.
|
||||
*/
|
||||
def props(): Props = Props(classOf[Channel])
|
||||
def props(): Props = Props(classOf[Channel], None)
|
||||
|
||||
/**
|
||||
* Returns a channel configuration object for creating a [[Channel]] with the
|
||||
|
|
@ -110,12 +125,159 @@ object Channel {
|
|||
*
|
||||
* @param channelId channel id.
|
||||
*/
|
||||
def props(channelId: String): Props = Props(classOf[Channel], channelId)
|
||||
def props(channelId: String): Props = Props(classOf[Channel], Some(channelId))
|
||||
}
|
||||
|
||||
/**
|
||||
* Instructs a [[Channel]] to deliver `persistent` message to destination `destination`.
|
||||
* The `resolve` parameter can be:
|
||||
* A [[PersistentChannel]] implements the same functionality as a [[Channel]] but additionally
|
||||
* persists messages before they are delivered. Therefore, the main use case of a persistent
|
||||
* channel is standalone usage i.e. independent of a sending [[Processor]]. Messages that have
|
||||
* been persisted by a persistent channel are deleted again when destinations confirm the receipt
|
||||
* of these messages.
|
||||
*
|
||||
* Using a persistent channel in combination with a [[Processor]] can make sense if destinations
|
||||
* are unavailable for a long time and an application doesn't want to buffer all messages in
|
||||
* memory (but write them to a journal instead). In this case, delivery can be disabled with
|
||||
* [[DisableDelivery]] (to stop delivery and persist-only) and re-enabled with [[EnableDelivery]].
|
||||
*
|
||||
* A persistent channel can also be configured to reply whether persisting a message was successful
|
||||
* or not (see `PersistentChannel.props` methods). If enabled, the sender will receive the persisted
|
||||
* message as reply (i.e. a [[Persistent]] message), otherwise a [[PersistenceFailure]] message.
|
||||
*
|
||||
* A persistent channel will only re-deliver un-confirmed, stored messages if it is started or re-
|
||||
* enabled with [[EnableDelivery]]. Hence, a persistent channel can be used to avoid message loss
|
||||
* in case of sender JVM crashes, for example. A channel, however, does not attempt any re-deliveries
|
||||
* should a destination be unavailable. Re-delivery to destinations (in case of network failures or
|
||||
* destination JVM crashes) is an application-level concern and can be done by using a reliable proxy,
|
||||
* for example.
|
||||
*/
|
||||
final class PersistentChannel private[akka] (_channelId: Option[String], persistentReply: Boolean) extends EventsourcedProcessor {
|
||||
override val processorId = _channelId.getOrElse(super.processorId)
|
||||
|
||||
private val journal = Persistence(context.system).journalFor(processorId)
|
||||
private val channel = context.actorOf(Props(classOf[NoPrepChannel], processorId))
|
||||
|
||||
private var deliveryEnabled = true
|
||||
|
||||
def receiveReplay: Receive = {
|
||||
case Deliver(persistent: PersistentRepr, destination, resolve) ⇒ deliver(prepareDelivery(persistent), destination, resolve)
|
||||
}
|
||||
|
||||
def receiveCommand: Receive = {
|
||||
case d @ Deliver(persistent: PersistentRepr, destination, resolve) ⇒ {
|
||||
if (!persistent.confirms.contains(processorId)) {
|
||||
persist(d) { _ ⇒
|
||||
val prepared = prepareDelivery(persistent)
|
||||
|
||||
if (persistent.processorId != PersistentRepr.Undefined)
|
||||
journal ! Confirm(persistent.processorId, persistent.sequenceNr, processorId)
|
||||
|
||||
if (persistentReply)
|
||||
sender ! prepared
|
||||
|
||||
if (deliveryEnabled)
|
||||
deliver(prepared, destination, resolve)
|
||||
}
|
||||
}
|
||||
}
|
||||
case c: Confirm ⇒ deleteMessage(c.sequenceNr, true)
|
||||
case DisableDelivery ⇒ deliveryEnabled = false
|
||||
case EnableDelivery if (!deliveryEnabled) ⇒ throw new ChannelRestartRequiredException
|
||||
case p: PersistenceFailure if (persistentReply) ⇒ sender ! p
|
||||
}
|
||||
|
||||
private def prepareDelivery(persistent: PersistentRepr): PersistentRepr = currentPersistentMessage.map { current ⇒
|
||||
val sequenceNr = if (persistent.sequenceNr == 0L) current.sequenceNr else persistent.sequenceNr
|
||||
val resolved = persistent.resolved && current.asInstanceOf[PersistentRepr].resolved
|
||||
persistent.update(sequenceNr = sequenceNr, resolved = resolved)
|
||||
} getOrElse (persistent)
|
||||
|
||||
private def deliver(persistent: PersistentRepr, destination: ActorRef, resolve: Resolve.ResolveStrategy) = currentPersistentMessage.foreach { current ⇒
|
||||
channel forward Deliver(persistent = ConfirmablePersistentImpl(persistent,
|
||||
confirmTarget = self,
|
||||
confirmMessage = Confirm(processorId, current.sequenceNr, PersistentRepr.Undefined)), destination, resolve)
|
||||
}
|
||||
}
|
||||
|
||||
object PersistentChannel {
|
||||
/**
|
||||
* Returns a channel configuration object for creating a [[PersistentChannel]] with a
|
||||
* generated id. The sender will not receive persistence completion replies.
|
||||
*/
|
||||
def props(): Props = props(persistentReply = false)
|
||||
|
||||
/**
|
||||
* Returns a channel configuration object for creating a [[PersistentChannel]] with a
|
||||
* generated id.
|
||||
*
|
||||
* @param persistentReply if `true` the sender will receive the successfully stored
|
||||
* [[Persistent]] message that has been submitted with a
|
||||
* [[Deliver]] request, or a [[PersistenceFailure]] message
|
||||
* in case of a persistence failure.
|
||||
*/
|
||||
def props(persistentReply: Boolean): Props = Props(classOf[PersistentChannel], None, persistentReply)
|
||||
|
||||
/**
|
||||
* Returns a channel configuration object for creating a [[PersistentChannel]] with the
|
||||
* specified id. The sender will not receive persistence completion replies.
|
||||
*
|
||||
* @param channelId channel id.
|
||||
*/
|
||||
def props(channelId: String): Props = props(channelId, persistentReply = false)
|
||||
|
||||
/**
|
||||
* Returns a channel configuration object for creating a [[PersistentChannel]] with the
|
||||
* specified id.
|
||||
*
|
||||
* @param channelId channel id.
|
||||
* @param persistentReply if `true` the sender will receive the successfully stored
|
||||
* [[Persistent]] message that has been submitted with a
|
||||
* [[Deliver]] request, or a [[PersistenceFailure]] message
|
||||
* in case of a persistence failure.
|
||||
*/
|
||||
def props(channelId: String, persistentReply: Boolean): Props = Props(classOf[PersistentChannel], Some(channelId), persistentReply)
|
||||
}
|
||||
|
||||
/**
|
||||
* Instructs a [[PersistentChannel]] to disable the delivery of [[Persistent]] messages to their destination.
|
||||
* The persistent channel, however, continues to persist messages (for later delivery).
|
||||
*
|
||||
* @see [[EnableDelivery]]
|
||||
*/
|
||||
@SerialVersionUID(1L)
|
||||
case object DisableDelivery {
|
||||
/**
|
||||
* Java API.
|
||||
*/
|
||||
def getInstance = this
|
||||
}
|
||||
|
||||
/**
|
||||
* Instructs a [[PersistentChannel]] to re-enable the delivery of [[Persistent]] messages to their destination.
|
||||
* This will first deliver all messages that have been stored by a persistent channel for which no confirmation
|
||||
* is available yet. New [[Deliver]] requests are processed after all stored messages have been delivered. This
|
||||
* request only has an effect if a persistent channel has previously been disabled with [[DisableDelivery]].
|
||||
*
|
||||
* @see [[DisableDelivery]]
|
||||
*/
|
||||
@SerialVersionUID(1L)
|
||||
case object EnableDelivery {
|
||||
/**
|
||||
* Java API.
|
||||
*/
|
||||
def getInstance = this
|
||||
}
|
||||
|
||||
/**
|
||||
* Thrown by a persistent channel when [[EnableDelivery]] has been requested and delivery has been previously
|
||||
* disabled for that channel.
|
||||
*/
|
||||
@SerialVersionUID(1L)
|
||||
class ChannelRestartRequiredException extends AkkaException("channel restart required for enabling delivery")
|
||||
|
||||
/**
|
||||
* Instructs a [[Channel]] or [[PersistentChannel]] to deliver `persistent` message to
|
||||
* destination `destination`. The `resolve` parameter can be:
|
||||
*
|
||||
* - `Resolve.Destination`: will resolve a new destination reference from the specified
|
||||
* `destination`s path. The `persistent` message will be sent to the newly resolved
|
||||
|
|
@ -160,7 +322,7 @@ object Channel {
|
|||
* @param resolve resolve strategy.
|
||||
*/
|
||||
@SerialVersionUID(1L)
|
||||
case class Deliver(persistent: Persistent, destination: ActorRef, resolve: Resolve.ResolveStrategy = Resolve.Off)
|
||||
case class Deliver(persistent: Persistent, destination: ActorRef, resolve: Resolve.ResolveStrategy = Resolve.Off) extends Message
|
||||
|
||||
object Deliver {
|
||||
/**
|
||||
|
|
@ -253,9 +415,9 @@ private object ResolvedDelivery {
|
|||
* Resolves `destination` before sending `persistent` message to the resolved destination using
|
||||
* the specified sender (`sdr`) as message sender.
|
||||
*/
|
||||
private class ResolvedDestinationDelivery(persistent: PersistentImpl, destination: ActorRef, sdr: ActorRef) extends ResolvedDelivery {
|
||||
private class ResolvedDestinationDelivery(persistent: PersistentRepr, destination: ActorRef, sdr: ActorRef) extends ResolvedDelivery {
|
||||
val path = destination.path
|
||||
def onResolveSuccess(ref: ActorRef) = ref tell (persistent.copy(resolved = true), sdr)
|
||||
def onResolveSuccess(ref: ActorRef) = ref tell (persistent.update(resolved = true), sdr)
|
||||
def onResolveFailure() = destination tell (persistent, sdr)
|
||||
}
|
||||
|
||||
|
|
@ -263,9 +425,15 @@ private class ResolvedDestinationDelivery(persistent: PersistentImpl, destinatio
|
|||
* Resolves `sdr` before sending `persistent` message to specified `destination` using
|
||||
* the resolved sender as message sender.
|
||||
*/
|
||||
private class ResolvedSenderDelivery(persistent: PersistentImpl, destination: ActorRef, sdr: ActorRef) extends ResolvedDelivery {
|
||||
private class ResolvedSenderDelivery(persistent: PersistentRepr, destination: ActorRef, sdr: ActorRef) extends ResolvedDelivery {
|
||||
val path = sdr.path
|
||||
def onResolveSuccess(ref: ActorRef) = destination tell (persistent.copy(resolved = true), ref)
|
||||
def onResolveSuccess(ref: ActorRef) = destination tell (persistent.update(resolved = true), ref)
|
||||
def onResolveFailure() = destination tell (persistent, sdr)
|
||||
}
|
||||
|
||||
/**
|
||||
* [[Channel]] specialization used by [[PersistentChannel]] to deliver stored messages.
|
||||
*/
|
||||
private class NoPrepChannel(channelId: String) extends Channel(Some(channelId)) {
|
||||
override private[akka] def prepareDelivery(persistent: PersistentRepr) = persistent
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue