Akka源码分析-Actor发消息(续)
上一篇博客我们分析道mailbox同时也是一个forkjointask,run方法中,调用了processMailbox处理一定数量的消息,然后最终调用dispatcher的registerForExecution重新进行线程调度,达到循环处理邮箱消息的功能。接下来我们分析一下processMailbox函数的功能。
/**
* Process the messages in the mailbox
*/
@tailrec private final def processMailbox(
left: Int = java.lang.Math.max(dispatcher.throughput, 1),
deadlineNs: Long = if (dispatcher.isThroughputDeadlineTimeDefined == true) System.nanoTime + dispatcher.throughputDeadlineTime.toNanos else 0L): Unit =
if (shouldProcessMessage) {
val next = dequeue()
if (next ne null) {
if (Mailbox.debug) println(actor.self + " processing message " + next)
actor invoke next
if (Thread.interrupted())
throw new InterruptedException("Interrupted while processing actor messages")
processAllSystemMessages()
if ((left > 1) && ((dispatcher.isThroughputDeadlineTimeDefined == false) || (System.nanoTime - deadlineNs) < 0))
processMailbox(left - 1, deadlineNs)
}
}
上面是processMailbox的代码,很明显这是一个尾递归,它一次性处理dispatcher.throughput个消息,并在dispatcher.throughputDeadlineTime时间内退出此次递归。源码也比较简单,大致的处理过程就是:队列取出消息,调用actor的invoke函数,处理所有系统消息,如果还有剩余消息或者未达到时间长度限制,则继续下一条消息处理。
代码虽然简单,但有几个隐含的点,需要说明。这是一个尾递归,且在线程池分配的某个线程中执行,则可以确保,actor处理此次批量消息时,一定是在某一个线程中,不会出现并行的情况。且该函数处理完毕后,会再被线程池进行调度,则下一次的线程跟此次应该不同。另外处理完每一条消息后都会把系统消息处理完毕,也就是说系统消息的优先级非常高。
override final def run(): Unit = {
try {
if (!isClosed) { //Volatile read, needed here
processAllSystemMessages() //First, deal with any system messages
processMailbox() //Then deal with messages
}
} finally {
setAsIdle() //Volatile write, needed here
dispatcher.registerForExecution(this, false, false)
}
}
我们再回到run方法,会发现,没有对异常进行处理。也就是说如果处理用户消息或者系统消息出现异常时,依然会进入下一次调度。我们接下来看一下ActorCell的invoke代码。
//Memory consistency is handled by the Mailbox (reading mailbox status then processing messages, then writing mailbox status
final def invoke(messageHandle: Envelope): Unit = {
val influenceReceiveTimeout = !messageHandle.message.isInstanceOf[NotInfluenceReceiveTimeout]
try {
currentMessage = messageHandle
if (influenceReceiveTimeout)
cancelReceiveTimeout()
messageHandle.message match {
case msg: AutoReceivedMessage ⇒ autoReceiveMessage(messageHandle)
case msg ⇒ receiveMessage(msg)
}
currentMessage = null // reset current message after successful invocation
} catch handleNonFatalOrInterruptedException { e ⇒
handleInvokeFailure(Nil, e)
} finally {
if (influenceReceiveTimeout)
checkReceiveTimeout // Reschedule receive timeout
}
}
我们暂时先忽略对超时消息的处理,发现invoke最终调用了receiveMessage(msg)。
final def receiveMessage(msg: Any): Unit = actor.aroundReceive(behaviorStack.head, msg)
receiveMessage又转而调用了actor的aroundReceive方法,actor是什么呢?
private[this] var _actor: Actor = _
def actor: Actor = _actor
protected def actor_=(a: Actor): Unit = _actor = a
通过定义我们知道这就是一个普通的Actor,追踪到这里终于调用了Actor特质的函数了,但仅仅调用了aroundReceive,而不是我们常见的receive函数。
/**
* INTERNAL API.
*
* Can be overridden to intercept calls to this actor's current behavior.
*
* @param receive current behavior.
* @param msg current message.
*/
@InternalApi
protected[akka] def aroundReceive(receive: Actor.Receive, msg: Any): Unit = {
// optimization: avoid allocation of lambda
if (receive.applyOrElse(msg, Actor.notHandledFun).asInstanceOf[AnyRef] eq Actor.NotHandled) {
unhandled(msg)
}
}
那就看看aroundReceive的源码喽。通过官方源码说明和代码分析,我们知道这里调用了我们最终定义的receive函数,如果receive没有处理该类型的消息,则调用unhandled。默认情况下unhandled应该会发送给deadletters。
/**
* User overridable callback.
* <p/>
* Is called when a message isn't handled by the current behavior of the actor
* by default it fails with either a [[akka.actor.DeathPactException]] (in
* case of an unhandled [[akka.actor.Terminated]] message) or publishes an [[akka.actor.UnhandledMessage]]
* to the actor's system's [[akka.event.EventStream]]
*/
def unhandled(message: Any): Unit = {
message match {
case Terminated(dead) ⇒ throw DeathPactException(dead)
case _ ⇒ context.system.eventStream.publish(UnhandledMessage(message, sender(), self))
}
}
在invoke方法中,还有一个函数的调用也不得不说:autoReceiveMessage。通过名字我们知道它应该是自动处理的消息,而不需要自己处理。哪些消息不需要我们处理呢?
def autoReceiveMessage(msg: Envelope): Unit = {
if (system.settings.DebugAutoReceive)
publish(Debug(self.path.toString, clazz(actor), "received AutoReceiveMessage " + msg)) msg.message match {
case t: Terminated ⇒ receivedTerminated(t)
case AddressTerminated(address) ⇒ addressTerminated(address)
case Kill ⇒ throw ActorKilledException("Kill")
case PoisonPill ⇒ self.stop()
case sel: ActorSelectionMessage ⇒ receiveSelection(sel)
case Identify(messageId) ⇒ sender() ! ActorIdentity(messageId, Some(self))
}
}
Terminated、AddressTerminated、Kill、PoisonPill、ActorSelectionMessage、Identify。怎么样这几个消息是不是比较眼熟呢。当然我们这里就不再展开分析了,不过读者如果对Terminated、AddressTerminated、Kill、PoisonPill几个消息的区别感兴趣的话,可以继续研究一下对应的处理过程。有些是抛异常,有些是调函数,还是有一些区别的。
另外在invoke方法中,还有一个字段我们也不应该忽略:currentMessage。通过其名称以及相关的代码逻辑,我们知道这仅仅是标志当前消息。那么它有什么用呢?
final def sender(): ActorRef = currentMessage match {
case null ⇒ system.deadLetters
case msg if msg.sender ne null ⇒ msg.sender
case _ ⇒ system.deadLetters
}
翻了一下源码,最终定位到以上代码。sender()是不是比较眼熟呢?currentMessage是为了给sender()返回对应的值的。不过有几点需要注意。currentMessage只是一个“临时”值,也就意味着sender()也会是一个“临时”值。临时的意思是在此次消息处理结束之后,对应的currentMessage会变化,sender返回的值也会变化。这也就是告诉读者,sender函数返回的值只有在此次消息处理过程中有效,且只在当前线程有效。如果消息处理时,出现了Future代码块,则在Future代码块内部,不要再调用sender函数!
补充:其实细心的读者一定会发现sender是一个闭包。简单来说闭包是一个函数,它的返回值取决于此函数之外声明一个或多个变量的值。sender的返回值取决于currentMessage变量。
invoke方法中还有一段代码同样很重要:handleNonFatalOrInterruptedException。这段代码逻辑比较简单,大概是先中止当前的消息处理,然后中止children的消息处理,最后发送一个系统消息给监督者(也就是父actor)。如果中止当前或children消息过程中也出现了异常,则就是stop所有子actor了。
final def handleInvokeFailure(childrenNotToSuspend: immutable.Iterable[ActorRef], t: Throwable): Unit = {
// prevent any further messages to be processed until the actor has been restarted
if (!isFailed) try {
suspendNonRecursive()
// suspend children
val skip: Set[ActorRef] = currentMessage match {
case Envelope(Failed(_, _, _), child) ⇒ { setFailed(child); Set(child) }
case _ ⇒ { setFailed(self); Set.empty }
}
suspendChildren(exceptFor = skip ++ childrenNotToSuspend)
t match {
// tell supervisor
case _: InterruptedException ⇒
// ➡➡➡ NEVER SEND THE SAME SYSTEM MESSAGE OBJECT TO TWO ACTORS ⬅⬅⬅
parent.sendSystemMessage(Failed(self, new ActorInterruptedException(t), uid))
case _ ⇒
// ➡➡➡ NEVER SEND THE SAME SYSTEM MESSAGE OBJECT TO TWO ACTORS ⬅⬅⬅
parent.sendSystemMessage(Failed(self, t, uid))
}
} catch handleNonFatalOrInterruptedException { e ⇒
publish(Error(e, self.path.toString, clazz(actor),
"emergency stop: exception in failure handling for " + t.getClass + Logging.stackTraceFor(t)))
try children foreach stop
finally finishTerminate()
}
}
到此为止,我们就把消息的处理过程分析完毕了。但这只是本地actor的大致处理逻辑,并不涉及remote和cluster的处理过程。还有dispatch对线程的调度也并没有深入分析,后面我会根据需要深入分析这些技术细节。
Akka源码分析-Actor发消息(续)的更多相关文章
- Akka源码分析-Actor发消息
前面两篇文章简单介绍了ActorSystem.actor以及dispatcher和mailbox的创建,下面我们就来看一下actor发消息的内部机制. val system = ActorSystem ...
- Akka源码分析-Remote-收发消息UL图
- Akka源码分析-Actor创建(续)
在上一遍博客中,我们已经分析了actor创建的大致过程,但只是涉及到了Dipatcher/Mailbox/ActorCell/InternalActorRef等对象的创建,并没有介绍我们自定义的继承A ...
- Akka源码分析-Actor创建
上一篇博客我们介绍了ActorSystem的创建过程,下面我们就研究一下actor的创建过程. val system = ActorSystem("firstActorSystem" ...
- Akka源码分析-Actor&ActorContext&ActorRef&ActorCell
分析源码的过程中我们发现,Akka出现了Actor.ActorRef.ActorCell.ActorContext等几个相似的概念,它们之间究竟有什么区别和联系呢? /** * Actor base ...
- Akka源码分析-Remote-发消息
上一篇博客我们介绍了remote模式下Actor的创建,其实与local的创建并没有太大区别,一般情况下还是使用LocalActorRef创建了Actor.那么发消息是否意味着也是相同的呢? 既然ac ...
- Akka源码分析-Akka Typed
对不起,akka typed 我是不准备进行源码分析的,首先这个库的API还没有release,所以会may change,也就意味着其概念和设计包括API都会修改,基本就没有再深入分析源码的意义了. ...
- Akka源码分析-Cluster-Distributed Publish Subscribe in Cluster
在ClusterClient源码分析中,我们知道,他是依托于“Distributed Publish Subscribe in Cluster”来实现消息的转发的,那本文就来分析一下Pub/Sub是如 ...
- Akka源码分析-Cluster-Singleton
akka Cluster基本实现原理已经分析过,其实它就是在remote基础上添加了gossip协议,同步各个节点信息,使集群内各节点能够识别.在Cluster中可能会有一个特殊的节点,叫做单例节点. ...
随机推荐
- Educational Codeforces Round 57 (Rated for Div. 2) 前三个题补题
感慨 最终就做出来一个题,第二题差一点公式想错了,又是一波掉分,不过我相信我一定能爬上去的 A Find Divisible(思维) 上来就T了,后来直接想到了题解的O(1)解法,直接输出左边界和左边 ...
- linux sar-系统运行状态统计工具
推荐:更多linux 性能监测与优化 关注:linux命令大全 sar命令是Linux下系统运行状态统计工具,它将指定的操作系统状态计数器显示到标准输出设备.sar工具将对系统当前的状态进行取样,然后 ...
- 版本优化-test
版本优化 标签(空格分隔): 测试 需求经手人太多,直接提bug,开发不乐意,跟Leader确认不靠谱,跟PM确认,不熟悉流程,跟第三方PM确认靠谱了,结果被开发三言两语,变成了不改bug 而改需求 ...
- idea 快捷键设置
通过 点击放大镜然后按当前需要修改的快捷键找到需要修改的快捷键,更改成希望的快捷键
- image url to base64
image url to base64 https://www.base64-image.de/ https://www.browserling.com/tools/image-to-base64 h ...
- Calculate S(n)
Problem Description Calculate S(n). S(n)=13+23 +33 +......+n3 . Input Each line will contain one i ...
- Python: 解决simple-db-migrate的"No module named 'MySQLdb'错误
sudo apt-get install libmysqlclient-dev sudo -H pip3 install mysqlclient
- Java中基于HTTP协议网络编程
java中为我们的网络支持提供了java.net包,能够使我们以编程的方式来訪问Web服务功能,这篇博客,就跟大家分享一下.Java中的网络编程的知识.主要是学习下该java.net包下的API. U ...
- C#中方法的详解
访问修饰符 修饰符 返回值类型 方法名(参数列表){ 语句块;} 访问修饰符:所有类成员访问修饰符都可以使用,如果省略访问修饰符,默认是private. 修饰符:在定义方法时修饰符包括virtual( ...
- iOS8開始默认语言有变化
[问题] 測试组发现APP在iOS8及以上系统设备上,语言设置为我们不支持的小语种时.APP没有使用默认的英文,而是选择了上一次设置的语言. [分析] 经过研究发现,在iOS8系统開始,在设备语言设置 ...