Akka源码分析-Event Bus
akka中的EventBus其实是不常用,也最容易被忽略的一个组件。
但如果你深入Cluster的实现就会发现,这个东西其实还挺有用的,而且它是ActorSystem系统中所有事件消息的一个横切面,通过它你可以订阅特定类型的消息,然后做出相应的动作。那读者可能会问了,这个订阅消息也很简单的啊,我自己实现不就好了。嗯,其实你这个想法是对的,akka所有的功能都是基于actor和Actor模型的,所有复杂的功能实现起来都不是特别麻烦,至少实现的模型不会很复杂。不过你可能用不好这个EventBus,因为你并不一定会用,或者说不知道什么时候用。
对于Event Bus,也就是事件总线,普通场景下个人建议不要使用。Event Bus会使本来就复杂的消息通信更加复杂, 如果不用,开发过程中你明确知道跟某个actor通信的都有哪些actor,也就是说他们之间的通信协议是明确的。仅仅做到这一点,就会使actor系统很复杂了,再用个Event Bus把事件发送出去,会导致消息更加分散,某种意义上也是一种耦合。比如你把消息A发布出去,但却不知道谁在订阅它,如果某个版本升级你不消息忘了发布这个消息,那其他actor还能正常工作吗?这明显是给自己找麻烦。
那什么时候用呢?或者说使用的时候都有哪些限制呢?大概有两种情况吧:1.发布的都是系统消息,跟业务无关;2.为了考虑系统后期的扩展和升级(当然了需要满足第一个条件)。第一个规则是啥意思呢?就是你发布的消息不会变化或者不会有大的变化,比如只是发布了某个特定actor启动、停止、退出的系统消息,这些消息无论格式还是内容都是固定的。如果后期系统功能升级,需要监控这些消息,由于消息固定,所以不会给版本带来很大的问题。再加上不是业务消息,所以也不会给业务造成什么影响。
废话不多说,来看看它的实现。当然EventBus实现比较复杂,简单起见,我们只分析Event Stream。
// this provides basic logging (to stdout) until .start() is called below
val eventStream = new EventStream(this, DebugEventStream)
eventStream.startStdoutLogger(settings)
在ActorSystemImpl中有上面两行代码,创建了一个eventStream,官方文档说,提供了一个基本的日志功能。其实这句话我觉得不应该说,容易给大家造成误解。大家肯定想,既然这个是用来做日志的,就没啥用了呗。如果有这个认识的话,再对akka做扩展的时候会走很大的弯路。其实akka系统通过eventStream发布了很多重要的系统消息,比如actor生命周期状态、remote模式下网络生命周期事件,如果能够合理的使用好这些系统消息,会给我们带来极大的方便,偷偷的告诉你,cluster就是订阅了一些网络状态事件实现了许多重要的功能。
/**
* An Akka EventStream is a pub-sub stream of events both system and user generated,
* where subscribers are ActorRefs and the channels are Classes and Events are any java.lang.Object.
* EventStreams employ SubchannelClassification, which means that if you listen to a Class,
* you'll receive any message that is of that type or a subtype.
*
* The debug flag in the constructor toggles if operations on this EventStream should also be published
* as Debug-Events
*/
class EventStream(sys: ActorSystem, private val debug: Boolean) extends LoggingBus with SubchannelClassification
Akka EventStream是一个发布-订阅事件流,包括系统和用户产生的数据。订阅某个特定类型的消息,不一定会收到对应的消息,前提是你自己或系统调用EventStream的发布接口把消息发布了出去。
/**
* Classification which respects relationships between channels: subscribing
* to one channel automatically and idempotently subscribes to all sub-channels.
*/
trait SubchannelClassification { this: EventBus ⇒
SubchannelClassification,子频道分类器,根据官方描述大概知道,它会自动的订阅所有子频道的消息。大概是会自动订阅某个父类所有子类的消息吧。频道是啥?当然是一个类或者接口了啊。
LoggingBus具体做啥的就不分析了,反正是跟记日志有关的。不过从它的继承关系来看,它直接决定了EventStream是一个EventBus的某个子类。这个继承关系我觉得官方实现的不够合理,毕竟记日志只是EventStream一个功能。EventStream首先应该是一个EventBus,只不过混入了Logging的功能而已,现在直接继承LoggingBus从而继承EventBus,显得不够优化!
class DeadLetterListener extends Actor {
def receive = {
case d: DeadLetter ⇒ println(d)
}
} val listener = system.actorOf(Props[DeadLetterListener])
system.eventStream.subscribe(listener, classOf[DeadLetter])
这是官方的一个例子,非常简单,就是调用subscribe方法,订阅了DeadLetter类型的消息,把消息发送给DeadLetterListener这个actor。那么来看看subscribe如何实现,不过在这之前还需要看看它是如何初始化的。在ActorSystem的start方法中调用了eventStream.startUnsubscriber(),对eventStream实现了初始化。
/**
* ''Must'' be called after actor system is "ready".
* Starts system actor that takes care of unsubscribing subscribers that have terminated.
*/
def startUnsubscriber(): Unit =
// sys may be null for backwards compatibility reasons
if (sys ne null) EventStreamUnsubscriber.start(sys, this)
其中sys就是我们传入的ActorSystem实例。
/**
* INTERNAL API
*
* Provides factory for [[akka.event.EventStreamUnsubscriber]] actors with **unique names**.
* This is needed if someone spins up more [[EventStream]]s using the same [[akka.actor.ActorSystem]],
* each stream gets it's own unsubscriber.
*/
private[akka] object EventStreamUnsubscriber { private val unsubscribersCount = new AtomicInteger(0) final case class Register(actor: ActorRef) final case class UnregisterIfNoMoreSubscribedChannels(actor: ActorRef) private def props(eventStream: EventStream, debug: Boolean) =
Props(classOf[EventStreamUnsubscriber], eventStream, debug) def start(system: ActorSystem, stream: EventStream) = {
val debug = system.settings.config.getBoolean("akka.actor.debug.event-stream")
system.asInstanceOf[ExtendedActorSystem]
.systemActorOf(props(stream, debug), "eventStreamUnsubscriber-" + unsubscribersCount.incrementAndGet())
} }
官方说EventStreamUnsubscriber是个工厂类,用来给EventStreamUnsubscriber提供一个唯一的名字,如果开发者启动了多个EventStream不至于会出现冲突。其实吧,个人觉得完全没必要,多创建一个EventStream,这都属于高级用法了,akka还没普及,远到不了这个地步。
/**
* INTERNAL API
*
* Watches all actors which subscribe on the given eventStream, and unsubscribes them from it when they are Terminated.
*
* Assumptions note:
* We do not guarantee happens-before in the EventStream when 2 threads subscribe(a) / unsubscribe(a) on the same actor,
* thus the messages sent to this actor may appear to be reordered - this is fine, because the worst-case is starting to
* needlessly watch the actor which will not cause trouble for the stream. This is a trade-off between slowing down
* subscribe calls * because of the need of linearizing the history message sequence and the possibility of sometimes
* watching a few actors too much - we opt for the 2nd choice here.
*/
protected[akka] class EventStreamUnsubscriber(eventStream: EventStream, debug: Boolean = false) extends Actor
从官方注释来看,EventStreamUnsubscriber是所有订阅eventStream的监督者,当订阅者(也就是某个actor)stop的时候,把对应的订阅消息移除,以便发送不必要的消息。那EventStreamUnsubscriber和EventStream的关系是怎么样的呢?其实吧,这里又做了一个分层,EventStreamUnsubscriber负责监控对应的actor,把消息发送个它,而EventStream负责订阅相关的状态维护。
初始化完成后,下面来看subscribe的实现。
override def subscribe(subscriber: ActorRef, channel: Class[_]): Boolean = {
if (subscriber eq null) throw new IllegalArgumentException("subscriber is null")
if (debug) publish(Logging.Debug(simpleName(this), this.getClass, "subscribing " + subscriber + " to channel " + channel))
registerWithUnsubscriber(subscriber)
super.subscribe(subscriber, channel)
}
@tailrec
private def registerWithUnsubscriber(subscriber: ActorRef): Unit = {
// sys may be null for backwards compatibility reasons
if (sys ne null) initiallySubscribedOrUnsubscriber.get match {
case value @ Left(subscribers) ⇒
if (!initiallySubscribedOrUnsubscriber.compareAndSet(value, Left(subscribers + subscriber)))
registerWithUnsubscriber(subscriber) case Right(unsubscriber) ⇒
unsubscriber ! EventStreamUnsubscriber.Register(subscriber)
}
}
/** Either the list of subscribed actors, or a ref to an [[akka.event.EventStreamUnsubscriber]] */
private val initiallySubscribedOrUnsubscriber = new AtomicReference[Either[Set[ActorRef], ActorRef]](Left(Set.empty))
initiallySubscribedOrUnsubscriber的定义还是很奇怪的,不过根据上下文来分析,registerWithUnsubscriber应该就是给EventStreamUnsubscriber发送EventStreamUnsubscriber.Register(subscriber)消息,然后调用super.subscribe
def subscribe(subscriber: Subscriber, to: Classifier): Boolean = subscriptions.synchronized {
val diff = subscriptions.addValue(to, subscriber)
addToCache(diff)
diff.nonEmpty
}
super.subscribe是在SubchannelClassification中实现的。
// must be lazy to avoid initialization order problem with subclassification
private lazy val subscriptions = new SubclassifiedIndex[Classifier, Subscriber]()
第一行的addVelue,应该就是把类型和对应的Subscriber做索引,当然了同一个Classifier是可以有多个订阅者的。Subscriber是啥?当然是一个ActorRef了。这个在EventStream继承的ActorEventBus中定义。
@volatile
private var cache = Map.empty[Classifier, Set[Subscriber]]
cache其实就是一个map,保存类型与订阅者集合的映射。逻辑是不是也很清晰呢?简单来说,订阅某个消息,就是把消息的类型和对应的actorRef做一个绑定,然后在某个对应类型的消息产生时,调用actorRef的tell函数就行了。
def publish(event: Event): Unit = {
val c = classify(event)
val recv =
if (cache contains c) cache(c) // c will never be removed from cache
else subscriptions.synchronized {
if (cache contains c) cache(c)
else {
addToCache(subscriptions.addKey(c))
cache(c)
}
}
recv foreach (publish(event, _))
}
那我们来看看publish的具体实现,EventStream中定义了Event就是一个AnyRef,其实就是可以发布任意引用类型的消息。这段代码也比较容易理解,在分析classify之前可以猜一猜,其实就是找出传入的AnyRef具体类型,然后从cache中找到对应的订阅者,在调用publish发布消息。
protected def classify(event: AnyRef): Class[_] = event.getClass
EventStream重写了classify函数,很简单,就是getClass。
protected def publish(event: AnyRef, subscriber: ActorRef) = {
if (sys == null && subscriber.isTerminated) unsubscribe(subscriber)
else subscriber ! event
}
publish呢?就是调用subscriber的! 方法,把消息发送出去。
其实分析到这里,基本就结束了,特别简单。订阅消息就是把对应的类型和actor关联起来,publish的时候通过消息的类型找到对应的订阅者(也就是actor),把消息发给订阅者就结束了,自己实现也特别简单。不过为了通用和稳定,akka还是做了很多工作的。比如某个actor被Terminat的时候,可以自动取消订阅,毕竟actor还可能意外终止,没有来得及调用unsubscribe方法取消订阅。
EventStream就分析到这里了,不过介绍这个知识点有两个出发点。首先这个EventStream作为所有消息的截面,特殊情况下,还是很有用的。另外就是在分析cluster的时候,这个点还是比较重要的,毕竟cluster用eventStream实现了某些特殊功能,虽然这点我不太喜欢。
Akka源码分析-Event Bus的更多相关文章
- Akka源码分析-Cluster-Metrics
一个应用软件维护的后期一定是要做监控,akka也不例外,它提供了集群模式下的度量扩展插件. 其实如果读者读过前面的系列文章的话,应该是能够自己写一个这样的监控工具的.简单来说就是创建一个actor,它 ...
- Akka源码分析-Cluster-Singleton
akka Cluster基本实现原理已经分析过,其实它就是在remote基础上添加了gossip协议,同步各个节点信息,使集群内各节点能够识别.在Cluster中可能会有一个特殊的节点,叫做单例节点. ...
- Akka源码分析-Persistence
在学习akka过程中,我们了解了它的监督机制,会发现actor非常可靠,可以自动的恢复.但akka框架只会简单的创建新的actor,然后调用对应的生命周期函数,如果actor有状态需要回复,我们需要h ...
- Akka源码分析-Cluster-ActorSystem
前面几篇博客,我们依次介绍了local和remote的一些内容,其实再分析cluster就会简单很多,后面关于cluster的源码分析,能够省略的地方,就不再贴源码而是一句话带过了,如果有不理解的地方 ...
- Akka源码分析-Akka Typed
对不起,akka typed 我是不准备进行源码分析的,首先这个库的API还没有release,所以会may change,也就意味着其概念和设计包括API都会修改,基本就没有再深入分析源码的意义了. ...
- Akka源码分析-Akka-Streams-概念入门
今天我们来讲解akka-streams,这应该算akka框架下实现的一个很高级的工具.之前在学习akka streams的时候,我是觉得云里雾里的,感觉非常复杂,而且又难学,不过随着对akka源码的深 ...
- Akka源码分析-Cluster-Distributed Publish Subscribe in Cluster
在ClusterClient源码分析中,我们知道,他是依托于“Distributed Publish Subscribe in Cluster”来实现消息的转发的,那本文就来分析一下Pub/Sub是如 ...
- Akka源码分析-local-DeathWatch
生命周期监控,也就是死亡监控,是akka编程中常用的机制.比如我们有了某个actor的ActorRef之后,希望在该actor死亡之后收到响应的消息,此时我们就可以使用watch函数达到这一目的. c ...
- Zepto源码分析-event模块
源码注释 // Zepto.js // (c) 2010-2015 Thomas Fuchs // Zepto.js may be freely distributed under the MIT l ...
随机推荐
- Django REST framework 数据处理api
一.url分发 以防有其他业务线的需要,导致url杂乱,将每个app用到的url都设置在自己的应用中. # 项目下的url url(r"^api/(?P<version>\w+) ...
- python使用xlrd和xlwt读写Excel文件
版权声明:本文为博主原创文章,未经允许不得转载. 安装模块 如果使用的是Linux系统,并且安装了pip,可以直接使用pip安装xlrd, xlwt: pip install xlwt pip ins ...
- linux whereis-查找二进制程序、代码等相关文件路径
推荐:更多Linux 文件查找和比较 命令关注:linux命令大全 whereis命令用来定位指令的二进制程序.源代码文件和man手册页等相关文件的路径. whereis命令只能用于程序名的搜索,而且 ...
- python 基础知识及运算符
可变类型:列表.字典 不可变类型:整形.字符串.元组 标示符: 1.字母数字和下划线组成 2.不能以数字开头 3.区分大小写 4.不能以保留字命名 变量: 1.用描述性的单词命名变量,不要用保留字.汉 ...
- 第十四节:Web爬虫之Ajax数据爬取
有时候在爬取数据的时候我们需要手动向上滑一下,网页才加载一定量的数据,但是网页的url并没有发生变化,这时我们就要考虑使用ajax进行数据爬取了...
- enum 的使用
public enum Color { RED(), GREEN(), BLANK(), YELLO(); // 成员变量 private String name; private int index ...
- hammerjs & Swiper & touch & gesture
hammerjs https://hammerjs.github.io/getting-started/ http://hammerjs.github.io/recognizer-swipe/ Swi ...
- hdu2852 KiKi's K-Number
题意:给定三个操作添加删除查询大于a的的第k大值----树状数组的逆向操作 给定a利用BIT查询有多少值比a小,这样比a大的k大值就应该有k+sum(a)个小于他的值 因此可以二分枚举k大值看看是不是 ...
- git常见操作---由简入深
常用命令 常用指令 ls 显示文件或目录 -l 列出文件详细信息l(list) -a 列出当前目录下所有文件及目录,包括隐藏的a(all) mkdir 创建目录 -p 创建目录,若无父目录,则创建p( ...
- HDU——1267 下沙的沙子有几粒?
下沙的沙子有几粒? Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others) Total ...