DPS可以回答两个问题:
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?
类似分片、DData一样的套路:每节点机有一个DistributedPubSubMediator. 我们称之为Mediator actor,它自动管理一个actorRef的注册表,所有Mediators 也构成一个分布式中介(发布订阅)层,共同同步、维护这张表,peer监视其中注册的actors.
你可以在任意节点上、通过本地mediator给任意节点上的另一个注册的订阅者即目标actor发消息,即使目标actor会迁移,也就是说,你可以随时随地发布也可以随时随地订阅,发布有两种:Publish或Send
Publish
典型例子就是在线聊天室,为了效率考量,发布的消息送达一台节点机(that has a matching topic)只会发生一次,之后会投递到该节点机上所有的订阅者actors. DistributedPubSubMediator.Subscribe做订阅,实际代码:
DistributedPubSub(context.system).mediator ! Subscribe("主题名", self)
这一步即将self注册到Mediator的注册表上去、并且关联一个topic. DistributedPubSubMediator.Publish做发布:
DistributedPubSub(context.system).mediator ! Publish("主题名", out)
Mediator注册表peer监视注册actors的含义是:Actors are automatically removed from the registry when they are terminated,注册actor即订阅者,当然你也可以直接手动解除订阅:
mediator.Unsubscribe
DistributedPubSub(context.system)是Akka Extention扩展的用法,我们看到由于Akka的很多功能组件本身是基于actor的、这些actor往往是每节点都有的一种驻点Stub、或者说一种代理actor,要使用什么功能,就找到什么代理,这里往往使用Extention.
如果有新的订阅者,由于分布式中介层是基于gossip收敛的,所以新订阅者可能不会第一时间发布到整个集群,由此可能会造成消息丢失;如果是已有的订阅者下线,由于宕机下线需要判定时间,这期间的异步消息仍然会发往失效地址却无人接收,由此也可能会造成消息丢失。所以严格来说DPS对消息投递是没有保障的,也就是 at-most-once delivery. In other words, messages can be lost over the wire. 要做到at-least-once投递保障,Akka官方推荐:Kafka Akka Streams integration.
主题组
Actors也可以用一个groupid参数订阅topic. 使用一个groupid的订阅actor属于一个共同订阅组,发布到该主题的消息如果携带有标记sendOneMessageToEachGroup=true会保证发送给订阅组的至少一个actor. 这一个actor的选择策略是RoutingLogic(default random) 随机选择。因此:
1、If all the subscribed actors have the same group id, then this works just like Send and each message is only delivered to one subscriber.
2、If all the subscribed actors have different group names, then this works like normal Publish and each message is broadcasted to all subscribers.
Send
点对点point-to-point发布模式,也有好处:你仍然不必知道目标actor的实际位置,典型例子是开小窗私聊,还可以用于对一批worker分发任务, like a cluster aware router where the routees dynamically can register themselves.
The message will be delivered to one recipient with a matching path, if any such exists in the registry. If several entries match the path because it has been registered on several nodes the message will be sent via the supplied RoutingLogic(default random) to one destination. The sender of the message can specify that local affinity is preferred, i.e. the message is sent to an actor in the same local actor system as the used mediator actor, if any such exists, otherwise route 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. On each node there can only be one actor for a given path, since the path is unique within one local actor system.
用send发布消息只需要通过本地Mediator向目标actor 的 逻辑路径path (without address information) 发布即可,所以DPS的发布目标要么是主题名字、要么是目标actor path,都是逻辑路径,实现与物理位置彻底解耦,这也是发布订阅这个传统功能的核心意义,目标actor代码示例。
发送者actor的消息发布代码示例:
mediator ! Send(path = "/user/destination", msg = out, localAffinity = true)
可以看到path实际上和主题named topic一样都只是个字符串,具备这个path的actor在一个ActorSys中只会有一个:On each node there can only be one such actor, since the path is unique within one local actor system. 但是在多台节点机就可以有多个同样路径的actors,此时把发送消息从Send改为SendToAll,即可实现向所有这些actor广播消息,用途:
Typical usage of this mode is to broadcast messages to all replicas with the same path, e.g. 3 actors on different nodes that all perform the same actions, for redundancy. You can also optionally specify a property (allButSelf) deciding if the message should be sent to a matching path on the self node or not.
消息投递保障
Akka帮助构建充分并发的单机程序(“scaling up”)、并且可以平滑过渡到并行(“scaling out”). 关键抽象是把所有业务对象(类实例)之间的同步/阻塞方法调用变为了actors间的异步消息交互,所以消息投递保障是十分重要的话题。本机本进程内的方法调用可靠性当然很高,如果是本地actor消息交互(In-JVM)可靠性与之相当;远程则应与传统RPC的可靠性相当。本地消息交互传递消息的引用,对所传递消息的类型没有限制(Any),远程消息交互则会对消息尺寸有限制。在本地用!/tell操作符发消息,其可能的失败原因与传统的本地方法调用一样:
1、StackOverflowError
2、OutOfMemoryError
3、otherVirtualMachineError
这些基本都是外来的系统原因,除此以外,还有少数Akka特有的可能失败原因:
1、mailbox不接受消息比如说邮箱满了 (e.g. full BoundedMailbox)
2、目标actor处理消息异常或者已经terminated
1更多是一个配置问题,2中消息的发送者将得不到feedback,异常会抛给接收者的上级,对发送者来说这和消息丢失没什么差别。
DPS乃至Akka原生功能不保障消息可靠投递:at-most-once = fire-and-forget,不止Akka,所有actor模式系统均是如此。对于投递顺序Akka的保障是:message ordering per sender–receiver pair.
更高的可靠投递保障是:at-least-once—消息可能重复投递但不会丢,通过同步ack+超时timeout重试达成;exactly-once—消息保证投递恰好一次,必须在前者基础上加上消息接收端去重才能保证。
Nobody Needs Reliable Messaging.
Erlang documentation(section 10.9 and 10.10)
情况就是这么个情况,要保障消息投递你得自己去做。
消息投递顺序
The rule more specifically is thatfor a given pair of actors, messages sent directly from the first to the second will not be received out-of-order.The word directly emphasizes that this guarantee only applies when sending with the tell operator to the final destination, not when employing mediators or other message dissemination features (unless stated otherwise). DPS不能够严格保障消息投递顺序。
要在大规模高并发业务数据分布式处理场景严格保障消息投递次序,可能不得不在Sender端自行维护订阅者actorRef的列表。
一对确定的actors之间的消息交互顺序是确定的,即使这俩不在一个JVM上也就是跨网络,比如直接用基于TCP的Akka远程来发消息,但如下消息传递可能乱序:
1、节点机1上的Actor A发了一条M1消息给节点机3上的actor C
2、之后A又发了一条M2给节点机2上的actor B
3、之后Actor B把消息M2转发给actor C
Actor C收到的 M1 和 M2 可能是任意顺序,也就是不保证先收到M1. M2可能先到达C.