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中可能会有一个特殊的节点,叫做单例节点. ...
随机推荐
- linux cut-连接文件并打印到标准输出设备上
博主推荐:获取更多 linux文件内容查看命令 收藏:linux命令大全 cut命令用来显示行中的指定部分,删除文件中指定字段.cut经常用来显示文件的内容,类似于下的type命令. 说明:该命令有两 ...
- 在线安全清空慢查询日志slowlog
mysql> show variables like '%slow_query%';+------------------------------------+--------------- ...
- 00 大王警语--be_a_new_gentleman
大王博客:https://www.cnblogs.com/alex3714/ # 表面层次# 1,着装特体(服饰的牌子中高端)# 2,每天洗澡# 3,适当用香水# 4,女士优先# 5,不随地吐痰.不乱 ...
- java诗词横版--转为竖版
import java.util.Arrays; /* 诗句横版转成竖版输出 */public class PrintPoem { public static void main(String[] a ...
- LINUX应用开发工程师职位(含答案)
就业模拟测试题-LINUX应用开发工程师职位 本试卷从考试酷examcoo网站导出,文件格式为mht,请用WORD/WPS打开,并另存为doc/docx格式后再使用 试卷编号:143989试卷录入者: ...
- log 框架 之间的关系
日志框架分为两大部分 一部分是日志框架的抽象层,一部分是日志框架的具体实现 slf4j: 日志框架的抽象层 log4j,logback 日志框架的具体实现 如上图所示: slf4j的具体实现是:slf ...
- 百练4152:最佳加法表达式(dp+高精度)
描述 给定n个1到9的数字,要求在数字之间摆放m个加号(加号两边必须有数字),使得所得到的加法表达式的值最小,并输出该值.例如,在1234中摆放1个加号,最好的摆法就是12+34,和为36 输入有不超 ...
- Linux学习总结(18)——Linux使用init命令关机、重启、切换模式
reboot可能是每个用过Linux的人都知道的命令,但有一个命令"init"才是命令中的精英. 最近有个同事学习安装了CentOS,明明安装的是带桌面的系统,但是启动后进入了命令 ...
- nyoj 95 众数问题(set)
众数问题 时间限制:3000 ms | 内存限制:65535 KB 难度:3 描述 所谓众数,就是对于给定的含有N个元素的多重集合,每个元素在S中出现次数最多的成为该元素的重数, 多重集合S重 ...
- vue.js通讯----父亲拿儿子的数据
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...