You are on page 1of 23

Distributed

Publish
Subscribe in
Akka Cluster
Agenda
• Classic Actors

• Receptionist

• Typed Actors

• Reliable Delivery

2
Motivation

• How do I send a message to an actor without knowing which node it is running on?

• How do I send messages to all actors in the cluster that have registered interest in a named
topic?

3
Classic Actors

4
Introduction
• Mediator actor, akka.cluster.pubsub.DistributedPubSubMediator - registry of actor references and replicates
across cluster.
• It is supposed to be started on all nodes, or all nodes with specified role, in the cluster.
• Can be started with the DistributedPubSub extension or as an ordinary actor.
• Registry  eventually consistent. Changes are only performed in the own part of the registry and those
changes are versioned. Deltas are disseminated in a scalable way to other nodes with a gossip protocol.
• Cluster members with status WeaklyUp will participate in Distributed Publish Subscribe, i.e. subscribers
on nodes with WeaklyUp status will receive published messages if the publisher and subscriber are on
same side of a network partition.
• You can send messages via the mediator on any node to registered actors on any other node.
• There a two different modes of message delivery, Publish and Send.

5
6
Publish
• Actors are registered to a named topic. Mediator
• This enables many subscribers on each node.
• The message will be delivered to all
subscribers of the topic. SubscribeAck Subscribe(topic)
ActorSystem
• Message is sent over the wire only once per Subscriber
node(that has a matching topic)
• You register actors to the local mediator Mediator
with DistributedPubSubMediator.Subscribe Mediator
acknowledged with
DistributedPubSubMediator.SubscribeAck and
DistributedPubSubMediator.UnsubscribeAck ActorSystem ActorSystem SubscribeAck
• DistributedPubSubMediator.Publish message Subscribe(topic)
to the local mediator for publishing.
• Actors removed from the registry when
• Terminated Subscriber
• DistributedPubSubMediator.Unsubscribe.

7
Mediator

ActorSystem
Subscriber
gossip
gossip Mediator
Mediator

ActorSystem ActorSystem
Subscriber

gossip

8
Send
• Point-to-point mode, each message is delivered to one destination(unknown).
• The message will be delivered to one recipient with a matching path, if any such exists in
the registry.
• If several entries (registered on several nodes) message sent via the
supplied RoutingLogic (default random) to one destination.
• If sender specifies local affinity - the message is sent to an actor in the same local actor
system as the used mediator actor, if not exists routed to any other matching entry.
• You register actors to the local mediator with DistributedPubSubMediator.Put.
• The ActorRef in Put must belong to the same local actor system as the mediator. The path
without address information is the key to which you send messages.
• Send DistributedPubSubMediator.Send message to the local mediator with the path (without
address information) of the destination actors.
• DistributedPubSubMediator.SendToAll - delivered to all recipients with a matching path.
• allButSelf - message should be sent to a matching path on the self node or not.
• Actors removed from the registry when
• Terminated
• DistributedPubSubMediator.Remove.
Mediator

ActorSystem Put(ActorRef)

user/my-actor
Mediator
Mediator

ActorSystem ActorSystem Put(ActorRef)


Send(“user/my-actor)
SendToAll(“user/my-actor)
user/my-actor

Sender
Code for Publish
class Subscriber extends Actor with ActorLogging {
import akka.cluster.pubsub.DistributedPubSubMediator.{ Subscribe, SubscribeAck }
val mediator = DistributedPubSub(context.system).mediator
mediator ! Subscribe("content", self) // subscribe to the topic named "content"
def receive = {
case s: String =>
case SubscribeAck(Subscribe("content", None, `self`)) => log.info("subscribing")
} class Publisher extends Actor {
} import akka.cluster.pubsub.DistributedPubSubMediator
// activate the extension
runOn("first") { val mediator = DistributedPubSub(context.system).mediator
context.actorOf(Props[Subscriber](), "subscriber1") def receive = {
} case in: String =>
runOn("second") { val out = in.toUpperCase
context.actorOf(Props[Subscriber](), "subscriber2") mediator ! Publish("content", out)
context.actorOf(Props[Subscriber](), "subscriber3") }
} }
runOn("third") {
val publisher = context.actorOf(Props[Publisher](), "publisher")
later()
// after a while the subscriptions are replicated
publisher ! "hello"
}
Code for Send
class Destination extends Actor with ActorLogging {
import akka.cluster.pubsub.DistributedPubSubMediator.Put
val mediator = DistributedPubSub(context.system).mediator
// register to the path
mediator ! Put(self)
def receive = {
case s: String =>
log.info("Got {}", s)
}
}

class Sender extends Actor {


import akka.cluster.pubsub.DistributedPubSubMediator.Send
// activate the extension
val mediator = DistributedPubSub(context.system).mediator
def receive = {
case in: String =>
val out = in.toUpperCase
mediator ! Send(path = "/user/destination", msg = out, localAffinity = true)
}
}
Configurations
# Settings for the DistributedPubSub extension
akka.cluster.pub-sub {
name = distributedPubSubMediator
# Start the mediator on members tagged with this role.
# All members are used if undefined or empty.
role = ""
# The routing logic to use for 'Send'
# Possible values: random, round-robin, broadcast
routing-logic = random
# How often the DistributedPubSubMediator should send out gossip information
gossip-interval = 1s
# Removed entries are pruned after this duration
removed-time-to-live = 120s
# Maximum number of elements to transfer in one message when synchronizing the registries.
# Next chunk will be transferred in next round of gossip.
max-delta-elements = 3000
# When a message is published to a topic with no subscribers send it to the dead letters.
send-to-dead-letters-when-no-subscribers = on
# The id of the dispatcher to use for DistributedPubSubMediator actors.
use-dispatcher = "akka.actor.internal-dispatcher"
}
Receptionist

14
Receptionist
• You register the specific actors that should be discoverable from each node in the
local Receptionist instance.
• This registry of actor references is then automatically distributed to all other nodes.
• You can lookup such actors with the key that was used when they were registered. The reply to such
a Find request is a Listing, which contains a Set of actor references that are registered for the key. Note that
several actors can be registered to the same key.
• The registry is dynamic. New actors can be registered during the lifecycle of the system.
• Entries are removed –
• registered actors are stopped
• manually deregistered using Receptionist.Deregister
• node they live on is removed from the Cluster.
• Subscribe to changes with the Receptionist.Subscribe message. It will send Listing messages to the subscriber,
first with the set of entries upon subscription, then whenever the entries for a key are changed.
• Subscriptions and Find queries to a clustered receptionist will keep track of cluster reachability and only
list registered actors that are reachable. The full set of actors, including unreachable ones, is available
through Listing.allServiceInstances.
15
Typed Actors

16
The Topic Actor
• Each pub/sub topic represented with an actor, akka.actor.typed.pubsub.Topic.
• Topic actor needs to run on each node where subscribers / publishers live for the topic.
• Identity of the topic - tuple of the type of messages that can be published and a string topic name
• Each topic is represented by one Receptionist service key
• The topic actor acts as a proxy and delegates to the local subscribers handling deduplication.
• When a topic actor has no subscribers for a topic it will deregister itself from the receptionist meaning
published messages for the topic will not be sent to it.
import akka.actor.typed.pubsub.Topic
Behaviors.setup[Command] { context =>
val topic = context.spawn(Topic[Message]("my-topic"),
"MyTopic")
topic ! Topic.Subscribe(subscriberActor)
topic ! Topic.Unsubscribe(subscriberActor)
topic ! Topic.Publish(Message("Hello Subscribers!"))
}

17
Reliable
Delivery

18
Introduction
• Normal message delivery reliability is at-most-once delivery.
• Reliable delivery can’t be achieved automatically under the hood without collaboration from the application.

• There are 3 supported patterns, which are described in the following sections:
• Point-to-point
• Work pulling
• Sharding

19
•For the ProducerController to know where to send the messages, it
must be connected with the ConsumerController. This can be done

Point-to-point with the ProducerController.RegisterConsumer or


ConsumerController.RegisterToProducerController  messages.
•It is the application’s responsibility to connect them together. For
example, this can be done by sending the ActorRef in an ordinary
message to the other side, or by registering the ActorRef in
the Receptionist so it can be found on the other side.
20
Work pulling
• Worker actors pull tasks at their own pace from
a shared work manager.
• A worker actor (consumer) and
its ConsumerController is dynamically
registered to the WorkPullingProducerController via
a ServiceKey. It will register itself to
the Receptionist, and
the WorkPullingProducerController subscribes to the
same key to find active workers. In this way
workers can be dynamically added or removed
from any node in the cluster.
• WorkPullingProducerController will send a
new RequestNext when there is a demand from
any worker. It’s possible that all workers with
demand are deregistered after the RequestNext is
sent and before the actual messages is sent to
the WorkPullingProducerController. In that case the
message is buffered and will be delivered when
a new worker is registered or when there is a
new demand.

21
Sharding
• ShardingConsumerController is identified by
an entityId. A
single ShardingProducerController per
ActorSystem (node) can be shared for sending to
all entities of a certain entity type. No explicit
registration is needed between
the ShardingConsumerController and ShardingPr
oducerController.
• In the
ShardingProducerController.RequestNext messag
e there is information about which entities that
have demand. It is allowed to send to a
new entityId that is not included in
the RequestNext.entitiesWithDemand.
• If sending to an entity that doesn’t have demand
the message will be buffered. Allowed to send
several messages in response to
one RequestNext but it’s recommended to only
send one message and wait for
next RequestNext before sending more messages.

22
Thank You

23

You might also like