I am trying to find out what message delivery guarantees Akka supports. I came to the following conclusion:
At-most-once : Supported by default
At-least-once : Supported with Akka Persistence
Exactly-once : ?
Does Akka support exactly-once? How would I be able to achieve this if it doesn't?
It is important to note that Akka’s guarantee applies to the order in which messages are enqueued into the recipient’s mailbox. If the mailbox implementation does not respect FIFO order (e.g. a PriorityMailbox ), then the order of processing by the actor can deviate from the enqueueing order.
Akka embraces distributed computing and makes the fallibility of communication explicit through message passing, therefore it does not try to lie and emulate a leaky abstraction. This is a model that has been used with great success in Erlang and requires the users to design their applications around it.
The rule that for a given pair of actors, messages sent directly from the first to the second will not be received out-of-order holds for messages sent over the network with the TCP based Akka remote transport protocol. As explained in the previous section local message sends obey transitive causal ordering under certain conditions.
Since there is no guaranteed delivery, any of the messages may be dropped, i.e. not arrive at A2 It is important to note that Akka’s guarantee applies to the order in which messages are enqueued into the recipient’s mailbox.
Akka out of the box provides At-Most-Once delivery, as you've discovered. At-Least-Once is available in some libraries such as Akka Persistence, and you can create it yourself fairly easily by creating an ACK-RETRY protocol in your actors. The Sender keeps periodically sending the message until the receiver acknowledges receipt of it.
Put simply, for At-Least-Once the responsibility is with the Sender. E.g in Scala:
class Sender(receiver: ActorRef) extends Actor {
var acknowledged = false
override def preStart() {
receiver ! "Do Work"
system.scheduler.scheduleOnce(50 milliseconds, self, "Retry")
}
def receive = {
case "Retry" =>
if(!acknowledged) {
receiver ! "Do Work"
system.scheduler.scheduleOnce(50 milliseconds, self, "Retry")
}
case "Ack" => acknowledged = true
}
}
class Receiver extends Actor {
def receive = {
case "Do Work" =>
doWork()
sender ! "Ack"
}
def doWork() = {...}
}
But with At-Most-Once processing, the receiver has to ensure that repeated instances of the same message only result in work being done once. This could be achieved through making the work done by the receiver idempotent so it can be repeatedly applied, or by having the receiver keep a record of what it has processed already. For At-Most-Once the responsibility is with the receiver:
class AtMostOnceReceiver extends Actor {
var workDone = false
def receive = {
case "Do Work" =>
if(!workDone) {
doWork()
workDone = true
}
sender ! Ack
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With