Akka源码分析-深入ActorRef&ActorPath
上一节我们深入讨论了ActorRef等相关的概念及其关系,但ActorRef和ActorPath的关系还需要再加以分析说明。其实还是官网说的比较清楚。
“A path in an actor system represents a “place” which might be occupied by a living actor. Initially (apart from system initialized actors) a path is empty. When actorOf()
is called it assigns an incarnation of the actor described by the passed Props
to the given path. An actor incarnation is identified by the path and a UID.”
“A restart only swaps the Actor
instance defined by the Props
but the incarnation and hence the UID remains the same. As long as the incarnation is same, you can keep using the same ActorRef
. Restart is handled by the Supervision Strategy of actor’s parent actor, and there is more discussion about what restart means.
The lifecycle of an incarnation ends when the actor is stopped. At that point the appropriate lifecycle events are called and watching actors are notified of the termination. After the incarnation is stopped, the path can be reused again by creating an actor with actorOf()
. In this case the name of the new incarnation will be the same as the previous one but the UIDs will differ. An actor can be stopped by the actor itself, another actor or the ActorSystem
”
“An ActorRef
always represents an incarnation (path and UID) not just a given path. Therefore if an actor is stopped and a new one with the same name is created an ActorRef
of the old incarnation will not point to the new one.”
我们总结一下官网的说明。开发者自定义的Actor通过actorOf创建的时候,都会分配一个UID,actor的路径(层级关系)+UID唯一标志一个Actor实例,也就是所谓的ActorRef。
@tailrec final def newUid(): Int = {
// Note that this uid is also used as hashCode in ActorRef, so be careful
// to not break hashing if you change the way uid is generated
val uid = ThreadLocalRandom.current.nextInt()
if (uid == undefinedUid) newUid
else uid
}
上面是UID的生成方法,其实就是一个随机数,这样可以保证每次生成的UID不重复。从官网描述来看,这个uid就是ActorRef的hashCode值。
在Actor完整的生命周期过程中,也就是没有被terminate,UID不会发生变化,即使Actor发生了restart。但如要注意理解此处的restart,是指actor处理消息时抛出了异常,被监督者处理并调用了restart方法。这与Actor先stop,再actorOf创建是截然不同的。actorOf用同样的名字重新创建actor会导致Actor的UID发生变化,也就会导致ActorRef不会重新指向新创建的Actor,其实此时Actor的路径(层级关系)是相同的。
final override def hashCode: Int = {
if (path.uid == ActorCell.undefinedUid) path.hashCode
else path.uid
} /**
* Equals takes path and the unique id of the actor cell into account.
*/
final override def equals(that: Any): Boolean = that match {
case other: ActorRef ⇒ path.uid == other.path.uid && path == other.path
case _ ⇒ false
}
我们来看一下abstract class ActorRef对hasCode和equals的定义就大概明白,UID的具体作用了,跟我们分析的是一致的。那我们来看看ActorPath的equals是如何定义的。
override def equals(other: Any): Boolean = {
@tailrec
def rec(left: ActorPath, right: ActorPath): Boolean =
if (left eq right) true
else if (left.isInstanceOf[RootActorPath]) left equals right
else if (right.isInstanceOf[RootActorPath]) right equals left
else left.name == right.name && rec(left.parent, right.parent) other match {
case p: ActorPath ⇒ rec(this, p)
case _ ⇒ false
}
}
对上面的代码简单分析一下就会发下,ActorPath在计算是否相等时,除了判断当前的hashCode是否相同外,就是在递归的判断当前ActorPath的name是否相同,跟UID没有关系,虽然在ActorPath的定义中也有uid值,且Actor的uid就是保存在ActorPath中,但该uid是一个内部变量,且只提供给ActorRef使用。
我们再来看看Actor的restart过程。
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大家应该知道这是做什么的了吧,所有发送个mailbox的用户消息都会通过调用invoke来处理。很明显在receiveMessage发生异常的过程中,如果不是致命错误,就会去调用handleInvokeFailure处理。
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()
}
}
handleInvokeFailure我们也分析过,它给父actor发送了一个Failed消息,表明某个子actor发生了异常。
Failed属于系统消息,会去调用invokeAll,很显然调用了handleFailure处理异常。
final protected def handleFailure(f: Failed): Unit = {
currentMessage = Envelope(f, f.child, system)
getChildByRef(f.child) match {
/*
* only act upon the failure, if it comes from a currently known child;
* the UID protects against reception of a Failed from a child which was
* killed in preRestart and re-created in postRestart
*/
case Some(stats) if stats.uid == f.uid ⇒
if (!actor.supervisorStrategy.handleFailure(this, f.child, f.cause, stats, getAllChildStats)) throw f.cause
case Some(stats) ⇒
publish(Debug(self.path.toString, clazz(actor),
"dropping Failed(" + f.cause + ") from old child " + f.child + " (uid=" + stats.uid + " != " + f.uid + ")"))
case None ⇒
publish(Debug(self.path.toString, clazz(actor), "dropping Failed(" + f.cause + ") from unknown child " + f.child))
}
}
handleFailure中通过发生异常的Actor的ActorRef找到对应的Actor实例,然后去调用该Actor的监督策略对异常的处理方案,如果该actor无法处理该异常,则继续throw。
def handleFailure(context: ActorContext, child: ActorRef, cause: Throwable, stats: ChildRestartStats, children: Iterable[ChildRestartStats]): Boolean = {
val directive = decider.applyOrElse(cause, escalateDefault)
directive match {
case Resume ⇒
logFailure(context, child, cause, directive)
resumeChild(child, cause)
true
case Restart ⇒
logFailure(context, child, cause, directive)
processFailure(context, true, child, cause, stats, children)
true
case Stop ⇒
logFailure(context, child, cause, directive)
processFailure(context, false, child, cause, stats, children)
true
case Escalate ⇒
logFailure(context, child, cause, directive)
false
}
}
通过当前监督策略来判断如何处理异常,默认情况下,都是Restart,所以调用了processFailure方法。默认的监督策略一般是OneForOneStrategy
def processFailure(context: ActorContext, restart: Boolean, child: ActorRef, cause: Throwable, stats: ChildRestartStats, children: Iterable[ChildRestartStats]): Unit = {
if (restart && stats.requestRestartPermission(retriesWindow))
restartChild(child, cause, suspendFirst = false)
else
context.stop(child) //TODO optimization to drop child here already?
}
上面是OneForOneStrategy的processFailure方法实现,就是去调用restarChild。
final def restartChild(child: ActorRef, cause: Throwable, suspendFirst: Boolean): Unit = {
val c = child.asInstanceOf[InternalActorRef]
if (suspendFirst) c.suspend()
c.restart(cause)
}
restarChild最终又调用了发生异常的Actor的restart方法,是通过ActorRef调用的。通过前面的分析我们知道,这个ActorRef最终是一个RepointableActorRef。
def restart(cause: Throwable): Unit = underlying.restart(cause)
上面是restart的定义,我们发现又去调用了underlying的restart,真是很绕啊。underlying是啥?当然是ActorRef引用的ActorCell了啊。但是我们翻了ActorCell的代码并没有发现restart的实现!但是我们却在ActorCell混入的Dispatch中发现了restart的踪影!
final def restart(cause: Throwable): Unit = try dispatcher.systemDispatch(this, Recreate(cause)) catch handleException
很简单,就是使用dispatcher给当前的ActorCell发送了一个Recreate消息。通过前面invokeAll我们知道收到Recreate后调用了faultRecreate,这个函数我们也分析过,就是调用了原有Actor的aroundPreRestart函数,然后调用finishRecreate函数。
private def finishRecreate(cause: Throwable, failedActor: Actor): Unit = {
// need to keep a snapshot of the surviving children before the new actor instance creates new ones
val survivors = children try {
try resumeNonRecursive()
finally clearFailed() // must happen in any case, so that failure is propagated val freshActor = newActor()
actor = freshActor // this must happen before postRestart has a chance to fail
if (freshActor eq failedActor) setActorFields(freshActor, this, self) // If the creator returns the same instance, we need to restore our nulled out fields. freshActor.aroundPostRestart(cause)
if (system.settings.DebugLifecycle) publish(Debug(self.path.toString, clazz(freshActor), "restarted")) // only after parent is up and running again do restart the children which were not stopped
survivors foreach (child ⇒
try child.asInstanceOf[InternalActorRef].restart(cause)
catch handleNonFatalOrInterruptedException { e ⇒
publish(Error(e, self.path.toString, clazz(freshActor), "restarting " + child))
})
} catch handleNonFatalOrInterruptedException { e ⇒
clearActorFields(actor, recreate = false) // in order to prevent preRestart() from happening again
handleInvokeFailure(survivors, PostRestartException(self, e, cause))
}
}
finishRecreate中调用newActor产生了一个新的Actor实例,调用了该实例的aroundPostRestart函数,最后如果可能则循环调用子actor的restart函数。
在actor的restart的工程中,我们发现没有任何涉及ActorPath和ActorRef修改或更新的地方,更没有uid变更的地方。这样就意味着,Actor的restart过程中,ActorRef不会失效,ActorPath更不会失效。还记得actorOf的过程么,其中有一步调用了makeChild,里面调用newUid产生了一个新的uid值给ActorRef,所以Actor被stop掉,然后用actorOf重建之后,actorRef当然会失效了。
其实我们可以这样简单的理解,ActorRef = “ActorPathString” + UID。开发者自定义的Actor类是一个静态的概念,当类通过actorOf创建的时候,就会产生一个Actor实例,如果该Actor由于某种原因失败,被系统restart,系统会新生成一个Actor实例,但该实例的UID不变,所以ActorRef指向相同路径下的actor实例。ActorPath标志Actor的树形路径,通过它可以找到这个路径下的实例,但实例的UID是不是相同则不关心。
Akka源码分析-深入ActorRef&ActorPath的更多相关文章
- Akka源码分析-Cluster-Distributed Publish Subscribe in Cluster
在ClusterClient源码分析中,我们知道,他是依托于“Distributed Publish Subscribe in Cluster”来实现消息的转发的,那本文就来分析一下Pub/Sub是如 ...
- Akka源码分析-local-DeathWatch
生命周期监控,也就是死亡监控,是akka编程中常用的机制.比如我们有了某个actor的ActorRef之后,希望在该actor死亡之后收到响应的消息,此时我们就可以使用watch函数达到这一目的. c ...
- Akka源码分析-Cluster-ActorSystem
前面几篇博客,我们依次介绍了local和remote的一些内容,其实再分析cluster就会简单很多,后面关于cluster的源码分析,能够省略的地方,就不再贴源码而是一句话带过了,如果有不理解的地方 ...
- Akka源码分析-Akka Typed
对不起,akka typed 我是不准备进行源码分析的,首先这个库的API还没有release,所以会may change,也就意味着其概念和设计包括API都会修改,基本就没有再深入分析源码的意义了. ...
- Akka源码分析-Cluster-Metrics
一个应用软件维护的后期一定是要做监控,akka也不例外,它提供了集群模式下的度量扩展插件. 其实如果读者读过前面的系列文章的话,应该是能够自己写一个这样的监控工具的.简单来说就是创建一个actor,它 ...
- Akka源码分析-Cluster-Singleton
akka Cluster基本实现原理已经分析过,其实它就是在remote基础上添加了gossip协议,同步各个节点信息,使集群内各节点能够识别.在Cluster中可能会有一个特殊的节点,叫做单例节点. ...
- Akka源码分析-Persistence
在学习akka过程中,我们了解了它的监督机制,会发现actor非常可靠,可以自动的恢复.但akka框架只会简单的创建新的actor,然后调用对应的生命周期函数,如果actor有状态需要回复,我们需要h ...
- Akka源码分析-Akka-Streams-概念入门
今天我们来讲解akka-streams,这应该算akka框架下实现的一个很高级的工具.之前在学习akka streams的时候,我是觉得云里雾里的,感觉非常复杂,而且又难学,不过随着对akka源码的深 ...
- Akka源码分析-Actor&ActorContext&ActorRef&ActorCell
分析源码的过程中我们发现,Akka出现了Actor.ActorRef.ActorCell.ActorContext等几个相似的概念,它们之间究竟有什么区别和联系呢? /** * Actor base ...
随机推荐
- Python学习-变量
什么是变量? 概念:变量就是会变化的量,主要是“变”与“量”二字.变即是“变化”. 特点:与其他编程语言相同,变量是最基本的存储单位,是用来存放数据的容器.可以引用一个具体的数值,进而直接去改变这个引 ...
- np.tile(), np.repeat() 和 tf.tile()
以上三个函数,主要区别在于能够拓展维度上和重复方式: np.tile() 能够拓展维度,并且整体重复: a = np.array([0,1,2]) np.tile(a,(2,2)) # out # a ...
- [bzoj4027][HEOI2015][兔子与樱花] (树形dp思想+玄学贪心)
Description 很久很久之前,森林里住着一群兔子.有一天,兔子们突然决定要去看樱花.兔子们所在森林里的樱花树很特殊.樱花树由n个树枝分叉点组成,编号从0到n-1,这n个分叉点由n-1个树枝连接 ...
- Unity对象的所有组件深拷贝与粘贴
本文章由cartzhang编写,转载请注明出处. 所有权利保留. 文章链接:http://blog.csdn.net/cartzhang/article/details/51454847 作者:car ...
- js中复制功能总结
目前copy主流有四种方式:ZeroClipboard,Clipboard.js,execCommand,setData,clipboardData 概况: ZeroClipboard 就是常说的Fl ...
- 有关HTML的相关基础问题:
有关HTML的相关基础问题:1.Doctype作用?严格模式与混杂模式如何区分?它们有何意义? 1)<!DICTYPE>声明位于文档中的最前面,处于<html>标签之前,告 ...
- BZOJ3238:[AHOI 2013]差异
求一个字符串的∑ ∑ len[i] + len[j] - 2 * lcp(i, j),其中i,j表示从i,j开始的后缀. 方法一:SA+单调栈,自行yy. 方法二:SAM构造出来,然后每个状态对答案的 ...
- MYSQL常用的字符串函数
#字符串函数 #返回字符串的字符数量,对于此例而言,结果为5SELECT CHAR_LENGTH('计算机编程'); #合并字符串,对于此例而言,结果为‘我喜欢计算机’SELECT CONCAT('我 ...
- 【python】range的用法
range的用法: >>> range(1,5) #代表从1到5(不包含5)[1, 2, 3, 4]>>> range(1,5,2) #代表从1到5,间隔2(不包含 ...
- MVC中动作方法三个特性以及解决同名方法冲突
一.Http请求谓词特性(解决方法同名冲突问题的一个方案) 关于Http谓词特点:经常使用,如果不加上该特性,默认动作方法接收所有谓词的请求一般开发中都会加上谓词,限定请求谓词类型 二.NonActi ...