在akka官网中关于远程actor交互,介绍了两种方法,一种是通过actorSelection查询,另一种是通过actorOf在远程节点创建一个actor。actorSelection我们之前的博客中已经介绍过,创建远程actor也有过简要说明,但其原理并没有做过多分析。下面就来分析一下如何在远程节点部署actor。

  官网介绍了两种部署远程actor的方法,一种是通过akka.actor.deployment配置,另一种是编程,都比较简单。考虑到代码完整性,下面摘录akka官方样例库中的代码做分析。

def startRemoteCreationSystem(): Unit = {
val system =
ActorSystem("CreationSystem", ConfigFactory.load("remotecreation"))
val actor = system.actorOf(Props[CreationActor],
name = "creationActor") println("Started CreationSystem")
import system.dispatcher
system.scheduler.schedule(1.second, 1.second) {
if (Random.nextInt(100) % 2 == 0)
actor ! Multiply(Random.nextInt(20), Random.nextInt(20))
else
actor ! Divide(Random.nextInt(10000), (Random.nextInt(99) + 1))
} }

  在sample.remote.calculator.CreationApplication中有以上函数,是用来远程部署actor的,当然是以配置的方式实现。

include "common"

akka {
actor {
deployment {
"/creationActor/*" {
remote = "akka.tcp://CalculatorWorkerSystem@127.0.0.1:2552"
}
}
} remote.netty.tcp.port = 2554
}

  配置文件如上。细心的读者一定发现远程部署actor跟正常actor的创建基本没有区别,代码可以完全一致,只需要修改配置。那是如何做到在远程节点部署actor的呢?

    val ref = system.actorOf(Props[SampleActor].
withDeploy(Deploy(scope = RemoteScope(address))))

  上面代码是通过编程的方式来创建远程actor,可以发现跟创建普通的actor还是多少有点区别,那就是有withDeploy代码块。其实不管是配置,还是withDeploy都是在给创建actor时配置相关参数。那这些参数具体是在哪里生效,生效后又是如何处理的呢?既然最终都会影响Props的deploy配置,那就从Props入手。

  /**
* Returns a new Props with the specified deployment configuration.
*/
def withDeploy(d: Deploy): Props = copy(deploy = d withFallback deploy)

  withDeploy比较简单,就是把最新的Deploy与Props原来的deploy变量进行合并,Deploy就是配置的一个类,不再过多分析,但有一点需要注意scope的值默认是NoScopeGiven,routerConfig默认是NoRouter。既然远程创建actor与Props有关,那就要找到在actorOf调用中涉及到Props使用的地方。

  前面的博客已经分析过ActorSystem.actorOf的调用流程,为了便于理解,这里再贴出相关代码。

def actorOf(props: Props, name: String): ActorRef =
if (guardianProps.isEmpty) guardian.underlying.attachChild(props, name, systemService = false)
else throw new UnsupportedOperationException(
s"cannot create top-level actor [$name] from the outside on ActorSystem with custom user guardian")

  guardian是provider的一个字段,而remote模式下provider是RemoteActorRefProvider,而RemoteActorRefProvider中guardian定义如下。

  override val deployer: Deployer = createDeployer

  /**
* Factory method to make it possible to override deployer in subclass
* Creates a new instance every time
*/
protected def createDeployer: RemoteDeployer = new RemoteDeployer(settings, dynamicAccess) private val local = new LocalActorRefProvider(systemName, settings, eventStream, dynamicAccess, deployer,
Some(deadLettersPath ⇒ new RemoteDeadLetterActorRef(this, deadLettersPath, eventStream)))
override def guardian: LocalActorRef = local.guardian

  其实还是在调用local的guardian相关的函数。其实还是在调用local的attachChild函数,注意调用过程中参数的值,其中async是true,systemService是false。

  private[akka] def attachChild(props: Props, name: String, systemService: Boolean): ActorRef =
makeChild(this, props, checkName(name), async = true, systemService = systemService)
private def makeChild(cell: ActorCell, props: Props, name: String, async: Boolean, systemService: Boolean): ActorRef = {
if (cell.system.settings.SerializeAllCreators && !systemService && props.deploy.scope != LocalScope) {
val oldInfo = Serialization.currentTransportInformation.value
try {
val ser = SerializationExtension(cell.system)
if (oldInfo eq null)
Serialization.currentTransportInformation.value = system.provider.serializationInformation props.args forall (arg ⇒
arg == null ||
arg.isInstanceOf[NoSerializationVerificationNeeded] || {
val o = arg.asInstanceOf[AnyRef]
val serializer = ser.findSerializerFor(o)
val bytes = serializer.toBinary(o)
val ms = Serializers.manifestFor(serializer, o)
ser.deserialize(bytes, serializer.identifier, ms).get != null
})
} catch {
case NonFatal(e) ⇒ throw new IllegalArgumentException(s"pre-creation serialization check failed at [${cell.self.path}/$name]", e)
} finally Serialization.currentTransportInformation.value = oldInfo
} /*
* in case we are currently terminating, fail external attachChild requests
* (internal calls cannot happen anyway because we are suspended)
*/
if (cell.childrenRefs.isTerminating) throw new IllegalStateException("cannot create children while terminating or terminated")
else {
reserveChild(name)
// this name will either be unreserved or overwritten with a real child below
val actor =
try {
val childPath = new ChildActorPath(cell.self.path, name, ActorCell.newUid())
cell.provider.actorOf(cell.systemImpl, props, cell.self, childPath,
systemService = systemService, deploy = None, lookupDeploy = true, async = async)
} catch {
case e: InterruptedException ⇒
unreserveChild(name)
Thread.interrupted() // clear interrupted flag before throwing according to java convention
throw e
case NonFatal(e) ⇒
unreserveChild(name)
throw e
}
// mailbox==null during RoutedActorCell constructor, where suspends are queued otherwise
if (mailbox ne null) for (_ ← 1 to mailbox.suspendCount) actor.suspend()
initChild(actor)
actor.start()
actor
}
}

  而且默认配置下cell.system.settings.SerializeAllCreators是false,所以最终会调用cell.provider.actorOf(cell.systemImpl, props, cell.self, childPath,systemService = systemService, deploy = None, lookupDeploy = true, async = async),其中systemService是false,deploy是None,lookupDeploy是true,async是true。

  RemoteActorRefProvider的actorOf代码如下

def actorOf(system: ActorSystemImpl, props: Props, supervisor: InternalActorRef, path: ActorPath,
systemService: Boolean, deploy: Option[Deploy], lookupDeploy: Boolean, async: Boolean): InternalActorRef =
if (systemService) local.actorOf(system, props, supervisor, path, systemService, deploy, lookupDeploy, async)
else { if (!system.dispatchers.hasDispatcher(props.dispatcher))
throw new ConfigurationException(s"Dispatcher [${props.dispatcher}] not configured for path $path") /*
* This needs to deal with “mangled” paths, which are created by remote
* deployment, also in this method. The scheme is the following:
*
* Whenever a remote deployment is found, create a path on that remote
* address below “remote”, including the current system’s identification
* as “sys@host:port” (typically; it will use whatever the remote
* transport uses). This means that on a path up an actor tree each node
* change introduces one layer or “remote/scheme/sys@host:port/” within the URI.
*
* Example:
*
* akka.tcp://sys@home:1234/remote/akka/sys@remote:6667/remote/akka/sys@other:3333/user/a/b/c
*
* means that the logical parent originates from “akka.tcp://sys@other:3333” with
* one child (may be “a” or “b”) being deployed on “akka.tcp://sys@remote:6667” and
* finally either “b” or “c” being created on “akka.tcp://sys@home:1234”, where
* this whole thing actually resides. Thus, the logical path is
* “/user/a/b/c” and the physical path contains all remote placement
* information.
*
* Deployments are always looked up using the logical path, which is the
* purpose of the lookupRemotes internal method.
*/ @scala.annotation.tailrec
def lookupRemotes(p: Iterable[String]): Option[Deploy] = {
p.headOption match {
case None ⇒ None
case Some("remote") ⇒ lookupRemotes(p.drop(3))
case Some("user") ⇒ deployer.lookup(p.drop(1))
case Some(_) ⇒ None
}
} val elems = path.elements
val lookup =
if (lookupDeploy)
elems.head match {
case "user" | "system" ⇒ deployer.lookup(elems.drop(1))
case "remote" ⇒ lookupRemotes(elems)
case _ ⇒ None
}
else None val deployment = {
deploy.toList ::: lookup.toList match {
case Nil ⇒ Nil
case l ⇒ List(l reduce ((a, b) ⇒ b withFallback a))
}
} Iterator(props.deploy) ++ deployment.iterator reduce ((a, b) ⇒ b withFallback a) match {
case d @ Deploy(_, _, _, RemoteScope(address), _, _) ⇒
if (hasAddress(address)) {
local.actorOf(system, props, supervisor, path, false, deployment.headOption, false, async)
} else if (props.deploy.scope == LocalScope) {
throw new ConfigurationException(s"configuration requested remote deployment for local-only Props at [$path]")
} else try {
try {
// for consistency we check configuration of dispatcher and mailbox locally
val dispatcher = system.dispatchers.lookup(props.dispatcher)
system.mailboxes.getMailboxType(props, dispatcher.configurator.config)
} catch {
case NonFatal(e) ⇒ throw new ConfigurationException(
s"configuration problem while creating [$path] with dispatcher [${props.dispatcher}] and mailbox [${props.mailbox}]", e)
}
val localAddress = transport.localAddressForRemote(address)
val rpath = (RootActorPath(address) / "remote" / localAddress.protocol / localAddress.hostPort / path.elements).
withUid(path.uid)
new RemoteActorRef(transport, localAddress, rpath, supervisor, Some(props), Some(d))
} catch {
case NonFatal(e) ⇒ throw new IllegalArgumentException(s"remote deployment failed for [$path]", e)
} case _ ⇒
local.actorOf(system, props, supervisor, path, systemService, deployment.headOption, false, async)
}
}

  分析lookupRemotes就会知道,这个函数其实就是在分析路径中/user后面的路径,根据对应的路径去deployer查找对应的配置。那么deployer具体是什么值呢?lookup又是做什么呢?在RemoteActorRefProvider定义中可以看到deployer是一个RemoteDeployer,RemoteDeployer的其他字段和方法不再具体分析,但覆盖的parseConfig很重要,简单来说它就是在读取配置文件的相关配置,然后给deploy传入RemoteScope或RemoteRouterConfig,都是在给scope赋值。

override def parseConfig(path: String, config: Config): Option[Deploy] = {

    super.parseConfig(path, config) match {
case d @ Some(deploy) ⇒
deploy.config.getString("remote") match {
case AddressFromURIString(r) ⇒ Some(deploy.copy(scope = RemoteScope(r)))
case str if !str.isEmpty ⇒ throw new ConfigurationException(s"unparseable remote node name [${str}]")
case _ ⇒
val nodes = immutableSeq(deploy.config.getStringList("target.nodes")).map(AddressFromURIString(_))
if (nodes.isEmpty || deploy.routerConfig == NoRouter) d
else deploy.routerConfig match {
case r: Pool ⇒ Some(deploy.copy(routerConfig = RemoteRouterConfig(r, nodes)))
case _ ⇒ d
}
}
case None ⇒ None
}
}

  从这里可以看出,配置文件的优先级要高于编程传入的参数。配置读取并设置完成后,会执行以下Iterator(props.deploy) ++ deployment.iterator reduce ((a, b) ⇒ b withFallback a),其实就是将配置文件和编程指定的参数进行合并,可以看出配置文件的优先级更高。但无论哪个配置生效对应deploy的scope都是RemoteScope。由于我们指定的address不是在本地,所以hasAddress(address)返回false,这样最终就创建了一个new RemoteActorRef(transport, localAddress, rpath, supervisor, Some(props), Some(d))实例。

  其中rpath是类似这样的字符串akka.tcp://Sys@127.0.0.1:2553/remote/akka.tcp/Sys@127.0.0.1:2552/user/remote#-888292812。第一个地址是远程actor所在的地址,第二个地址是本地地址(也就是远程actor在本地的代理)。至此actorOf执行结束,好像也没有看到在远程部署actor的相关代码啊?惊不惊喜意不意外?哈哈,有点晕啊。

  别着急,还记得makeChild函数么,它最后还调用了actor.start函数啊,那么RemoteActorRef是如何start的呢?

 def start(): Unit =
if (props.isDefined && deploy.isDefined) remote.provider.useActorOnNode(this, props.get, deploy.get, getParent)

  很显然props和deploy都是优质的,会去调用remote.provider.useActorOnNode函数,很显然这个函数是属于RemoteActorRefProvider的。

/**
* Using (checking out) actor on a specific node.
*/
def useActorOnNode(ref: ActorRef, props: Props, deploy: Deploy, supervisor: ActorRef): Unit = {
log.debug("[{}] Instantiating Remote Actor [{}]", rootPath, ref.path) // we don’t wait for the ACK, because the remote end will process this command before any other message to the new actor
// actorSelection can't be used here because then it is not guaranteed that the actor is created
// before someone can send messages to it
resolveActorRef(RootActorPath(ref.path.address) / "remote") !
DaemonMsgCreate(props, deploy, ref.path.toSerializationFormat, supervisor) remoteDeploymentWatcher ! RemoteDeploymentWatcher.WatchRemote(ref, supervisor)
}

  这个函数也比较简单,它给两个actor发送了分别发送了一条消息。我们先来看第一个actor具体是什么。

  def resolveActorRef(path: ActorPath): ActorRef = {
if (hasAddress(path.address)) local.resolveActorRef(rootGuardian, path.elements)
else try {
new RemoteActorRef(transport, transport.localAddressForRemote(path.address),
path, Nobody, props = None, deploy = None)
} catch {
case NonFatal(e) ⇒
log.warning("Error while resolving ActorRef [{}] due to [{}]", path, e.getMessage)
new EmptyLocalActorRef(this, path, eventStream)
}
}

  它通过RootActorPath(ref.path.address) / "remote"这个路径创建了又一个RemoteActorRef,然后给他发送了一个DaemonMsgCreate(props, deploy, ref.path.toSerializationFormat, supervisor)消息。那么这个RootActorPath(ref.path.address) / "remote"路径对应的actor应该是什么呢?其实这应该是一个位于目标actor所在节点且名字是remote的actor,那么这个actor什么时候创建的呢?

  其实远程部署有一个前提,就是目标节点的ActorSystem已经启动,那么就意味着RootActorPath(ref.path.address) / "remote"这个actor在远程节点已经启动,我们只需要找到是如何启动、在哪里启动、启动了哪个Actor,就知道是如何处理DaemonMsgCreate消息了。还记得RemoteActorRefProvider的init函数吗,其中有一段代码如下。

 val internals = Internals(
remoteDaemon = {
val d = new RemoteSystemDaemon(
system,
local.rootPath / "remote",
rootGuardian,
remotingTerminator,
_log,
untrustedMode = remoteSettings.UntrustedMode)
local.registerExtraNames(Map(("remote", d)))
d
},
transport =
if (remoteSettings.Artery.Enabled) remoteSettings.Artery.Transport match {
case ArterySettings.AeronUpd ⇒ new ArteryAeronUdpTransport(system, this)
case ArterySettings.Tcp ⇒ new ArteryTcpTransport(system, this, tlsEnabled = false)
case ArterySettings.TlsTcp ⇒ new ArteryTcpTransport(system, this, tlsEnabled = true)
}
else new Remoting(system, this))

  这段代码中创建了一个RemoteSystemDaemon,这个daemon第二个参数是local.rootPath / "remote",这个值其实是与上文中的RootActorPath(ref.path.address) / "remote"相对应的。

/**
* INTERNAL API
*/
@SerialVersionUID(1L)
private[akka] final case class DaemonMsgCreate(props: Props, deploy: Deploy, path: String, supervisor: ActorRef) extends DaemonMsg /**
* INTERNAL API
*
* Internal system "daemon" actor for remote internal communication.
*
* It acts as the brain of the remote that responds to system remote events (messages) and undertakes action.
*/
private[akka] class RemoteSystemDaemon(
system: ActorSystemImpl,
_path: ActorPath,
_parent: InternalActorRef,
terminator: ActorRef,
_log: MarkerLoggingAdapter,
val untrustedMode: Boolean)
extends VirtualPathContainer(system.provider, _path, _parent, _log)

  RemoteSystemDaemon定义的地方我们同时也找到了DaemonMsgCreate消息的定义,很显然RemoteSystemDaemon是可以处理DaemonMsgCreate消息的。从RemoteSystemDaemon的定义来看这是一个ActorRef。

  其实在RemoteSystemDaemon创建之后还调用了local.registerExtraNames(Map(("remote", d))),这段代码又是用来做什么的呢?

  /**
* Higher-level providers (or extensions) might want to register new synthetic
* top-level paths for doing special stuff. This is the way to do just that.
* Just be careful to complete all this before ActorSystem.start() finishes,
* or before you start your own auto-spawned actors.
*/
def registerExtraNames(_extras: Map[String, InternalActorRef]): Unit = extraNames ++= _extras

  从官方注释来看,这是用来更高级别的provider或者extensions用来注册合成的、顶级路径,以便做特殊的工作。这必须在ActorSystem启动结束或者自动生成的actor自动之前完成成。这个函数很简单,就是把以name和对应的AcotRef注册到extraNames中去。也就是说我们把remote和其对应的RemoteSystemDaemon注册到了extraNames中,那extraNames有什么用呢?或者说什么时候用呢?

  还记得在网络位置透明这篇博客中是如何介绍根据ActorPathString找到对应的actor的吗?序列化类最终调用了RemoteActorRefProvider的internalResolveActorRef把path转化成了对应的ActorRef。

/**
* INTERNAL API: This is used by the `ActorRefResolveCache` via the
* public `resolveActorRef(path: String)`.
*/
private[akka] def internalResolveActorRef(path: String): ActorRef = path match {
case ActorPathExtractor(address, elems) ⇒
if (hasAddress(address)) local.resolveActorRef(rootGuardian, elems)
else {
val rootPath = RootActorPath(address) / elems
try {
new RemoteActorRef(transport, transport.localAddressForRemote(address),
rootPath, Nobody, props = None, deploy = None)
} catch {
case NonFatal(e) ⇒
log.warning("Error while resolving ActorRef [{}] due to [{}]", path, e.getMessage)
new EmptyLocalActorRef(this, rootPath, eventStream)
}
}
case _ ⇒
log.debug("Resolve (deserialization) of unknown (invalid) path [{}], using deadLetters.", path)
deadLetters
}

  很显然,远程ActorSystem收到的path中的address是属于该远程节点的,也就是说最终会调用local.resolveActorRef(rootGuardian, elems)

  private[akka] def resolveActorRef(ref: InternalActorRef, pathElements: Iterable[String]): InternalActorRef =
if (pathElements.isEmpty) {
log.debug("Resolve (deserialization) of empty path doesn't match an active actor, using deadLetters.")
deadLetters
} else ref.getChild(pathElements.iterator) match {
case Nobody ⇒
if (log.isDebugEnabled)
log.debug(
"Resolve (deserialization) of path [{}] doesn't match an active actor. " +
"It has probably been stopped, using deadLetters.",
pathElements.mkString("/"))
new EmptyLocalActorRef(system.provider, ref.path / pathElements, eventStream)
case x ⇒ x
}

  其实也就是调用ref.getChild查找ref的子actor,也就是在rootGuardian现在查找elems对应的actor,根据上下文这里的elems应该就是"remote",我们来看看rootGuardian是如何查找remote的。

override lazy val rootGuardian: LocalActorRef =
new LocalActorRef(
system,
Props(classOf[LocalActorRefProvider.Guardian], rootGuardianStrategy),
defaultDispatcher,
defaultMailbox,
theOneWhoWalksTheBubblesOfSpaceTime,
rootPath) {
override def getParent: InternalActorRef = this
override def getSingleChild(name: String): InternalActorRef = name match {
case "temp" ⇒ tempContainer
case "deadLetters" ⇒ deadLetters
case other ⇒ extraNames.get(other).getOrElse(super.getSingleChild(other))
}
}

  首先需要知道rootGuardian是什么,它是一个LocalActorRef,并且重写了getParent、getSingleChild两个函数。那LocalActorRef.getChild又是如何实现的呢

override def getChild(names: Iterator[String]): InternalActorRef = {
/*
* The idea is to recursively descend as far as possible with LocalActor
* Refs and hand over to that “foreign” child when we encounter it.
*/
@tailrec
def rec(ref: InternalActorRef, name: Iterator[String]): InternalActorRef =
ref match {
case l: LocalActorRef ⇒
val next = name.next() match {
case ".." ⇒ l.getParent
case "" ⇒ l
case any ⇒ l.getSingleChild(any)
}
if (next == Nobody || name.isEmpty) next else rec(next, name)
case _ ⇒
ref.getChild(name)
} if (names.isEmpty) this
else rec(this, names)
}

  names不为空,而且就是“remote”,会去调用rec,rec函数中ref就是this,也就是LocalActorRef,所以会根据name依次查找,由于name就是“remote”,所以会调用getSingleChild,而getSingleChild的实现已经被覆盖。由于name是“remote”,所以会命中覆盖后的getSingleChild函数中case other分支。这个分支的逻辑就是,先去extraNames取值,如果没有查找到则调用super.getSingleChild。很明显在extraNames有"remote"对应的InternalActorRef,那就是RemoteSystemDaemon。

  所以,本地actor给远程RootActorPath(ref.path.address) / "remote"路径下的actor发送的DaemonMsgCreate消息,就会被远程的RemoteSystemDaemon处理。

override def !(msg: Any)(implicit sender: ActorRef = Actor.noSender): Unit = try msg match {
case message: DaemonMsg ⇒
log.debug("Received command [{}] to RemoteSystemDaemon on [{}]", message, path.address)
message match {
case DaemonMsgCreate(_, _, path, _) if untrustedMode ⇒
log.debug("does not accept deployments (untrusted) for [{}]", path) // TODO add security marker? case DaemonMsgCreate(props, deploy, path, supervisor) if whitelistEnabled ⇒
val name = props.clazz.getCanonicalName
if (remoteDeploymentWhitelist.contains(name))
doCreateActor(message, props, deploy, path, supervisor)
else {
val ex = new NotWhitelistedClassRemoteDeploymentAttemptException(props.actorClass, remoteDeploymentWhitelist)
log.error(LogMarker.Security, ex,
"Received command to create remote Actor, but class [{}] is not white-listed! " +
"Target path: [{}]", props.actorClass, path)
}
case DaemonMsgCreate(props, deploy, path, supervisor) ⇒
doCreateActor(message, props, deploy, path, supervisor)
} case sel: ActorSelectionMessage ⇒
val (concatenatedChildNames, m) = {
val iter = sel.elements.iterator
// find child elements, and the message to send, which is a remaining ActorSelectionMessage
// in case of SelectChildPattern, otherwise the actual message of the selection
@tailrec def rec(acc: List[String]): (List[String], Any) =
if (iter.isEmpty)
(acc.reverse, sel.msg)
else {
iter.next() match {
case SelectChildName(name) ⇒ rec(name :: acc)
case SelectParent if acc.isEmpty ⇒ rec(acc)
case SelectParent ⇒ rec(acc.tail)
case pat: SelectChildPattern ⇒ (acc.reverse, sel.copy(elements = pat +: iter.toVector))
}
}
rec(Nil)
}
getChild(concatenatedChildNames.iterator) match {
case Nobody ⇒
val emptyRef = new EmptyLocalActorRef(system.provider, path / sel.elements.map(_.toString),
system.eventStream)
emptyRef.tell(sel, sender)
case child ⇒
child.tell(m, sender)
} case Identify(messageId) ⇒ sender ! ActorIdentity(messageId, Some(this)) case TerminationHook ⇒
terminating.switchOn {
terminationHookDoneWhenNoChildren()
foreachChild { system.stop }
} case AddressTerminated(address) ⇒
foreachChild {
case a: InternalActorRef if a.getParent.path.address == address ⇒ system.stop(a)
case _ ⇒ // skip, this child doesn't belong to the terminated address
} case unknown ⇒ log.warning(LogMarker.Security, "Unknown message [{}] received by [{}]", unknown, this) } catch {
case NonFatal(e) ⇒ log.error(e, "exception while processing remote command [{}] from [{}]", msg, sender)
}

  RemoteSystemDaemon是如何处理DaemonMsgCreate消息的呢?上面是源码,默认配置下whitelistEnabled是false,所以会调用doCreateActor。

private def doCreateActor(message: DaemonMsg, props: Props, deploy: Deploy, path: String, supervisor: ActorRef) = {
path match {
case ActorPathExtractor(address, elems) if elems.nonEmpty && elems.head == "remote" ⇒
// TODO RK currently the extracted “address” is just ignored, is that okay?
// TODO RK canonicalize path so as not to duplicate it always #1446
val subpath = elems.drop(1)
val p = this.path / subpath
val childName = {
val s = subpath.mkString("/")
val i = s.indexOf('#')
if (i < 0) s
else s.substring(0, i)
}
val isTerminating = !terminating.whileOff {
val parent = supervisor.asInstanceOf[InternalActorRef]
val actor = system.provider.actorOf(system, props, parent,
p, systemService = false, Some(deploy), lookupDeploy = true, async = false)
addChild(childName, actor)
actor.sendSystemMessage(Watch(actor, this))
actor.start()
if (addChildParentNeedsWatch(parent, actor)) parent.sendSystemMessage(Watch(parent, this))
}
if (isTerminating) log.error("Skipping [{}] to RemoteSystemDaemon on [{}] while terminating", message, p.address)
case _ ⇒
log.debug("remote path does not match path from message [{}]", message)
}
}

  抛开其他代码,我们会发现,其实最终调用了system.provider.actorOf创建了对应的actor。

DaemonMsgCreate(props, deploy, ref.path.toSerializationFormat, supervisor)

  我们回过头看看DaemonMsgCreate是如何构造的,在结合上下文,就会发现,本地ActorSystem把props,deploy,actorpath,supervisor序列化发送给了远程节点,远程节点收到消息后,反序列化,然后调用provider.actorOf创建了对应的actor。但需要注意其中参数的值,props是远程节点发过来的,systemService是false,lookupDeploy是true,async是false。actor创建的具体过程不在分析,应该就是调用LocalActorRefProvider在本地创建了一个actor,然后把当前actor添加到RemoteSystemDaemon的监控列表中,最后调用actor.start,启动actor。

  至此我们就在远程的ActorSystem创建了对应的actor,怎么样,涉及的逻辑是不是也比较清晰呢?简单来说就是把props、deploy等信息序列化后发送给远程ActorSystem下特定的actor(RemoteSystemDaemon),由该actor负责创建props对应的actor。当然了还需要在本地创建一个远程actor的代理(RemoteActorRef),以便与远程actor收发消息。

Akka源码分析-Remote-Creating Actors Remotely的更多相关文章

  1. Akka源码分析-Cluster-Singleton

    akka Cluster基本实现原理已经分析过,其实它就是在remote基础上添加了gossip协议,同步各个节点信息,使集群内各节点能够识别.在Cluster中可能会有一个特殊的节点,叫做单例节点. ...

  2. Akka源码分析-local-DeathWatch

    生命周期监控,也就是死亡监控,是akka编程中常用的机制.比如我们有了某个actor的ActorRef之后,希望在该actor死亡之后收到响应的消息,此时我们就可以使用watch函数达到这一目的. c ...

  3. Akka源码分析-Cluster-ActorSystem

    前面几篇博客,我们依次介绍了local和remote的一些内容,其实再分析cluster就会简单很多,后面关于cluster的源码分析,能够省略的地方,就不再贴源码而是一句话带过了,如果有不理解的地方 ...

  4. Akka源码分析-Persistence

    在学习akka过程中,我们了解了它的监督机制,会发现actor非常可靠,可以自动的恢复.但akka框架只会简单的创建新的actor,然后调用对应的生命周期函数,如果actor有状态需要回复,我们需要h ...

  5. Akka源码分析-Akka-Streams-概念入门

    今天我们来讲解akka-streams,这应该算akka框架下实现的一个很高级的工具.之前在学习akka streams的时候,我是觉得云里雾里的,感觉非常复杂,而且又难学,不过随着对akka源码的深 ...

  6. Akka源码分析-Cluster-Distributed Publish Subscribe in Cluster

    在ClusterClient源码分析中,我们知道,他是依托于“Distributed Publish Subscribe in Cluster”来实现消息的转发的,那本文就来分析一下Pub/Sub是如 ...

  7. Akka源码分析-Akka Typed

    对不起,akka typed 我是不准备进行源码分析的,首先这个库的API还没有release,所以会may change,也就意味着其概念和设计包括API都会修改,基本就没有再深入分析源码的意义了. ...

  8. Akka源码分析-Cluster-Metrics

    一个应用软件维护的后期一定是要做监控,akka也不例外,它提供了集群模式下的度量扩展插件. 其实如果读者读过前面的系列文章的话,应该是能够自己写一个这样的监控工具的.简单来说就是创建一个actor,它 ...

  9. Akka源码分析-Cluster-ClusterClient

    ClusterClient可以与某个集群通信,而本身节点不必是集群的一部分.它只需要知道一个或多个节点的位置作为联系节点.它会跟ClusterReceptionist 建立连接,来跟集群中的特定节点发 ...

随机推荐

  1. ZOJ - 3992 - One-Dimensional Maze (思维)

    题意: 一条长度为n的直线,你一开始在位置m上 其中每个整点都有一个字符'L'或'R',如果是'L'那么你必须往左走一步,否则往右走一步 如果你到达位置1或位置n你任务就完成了 不过有可能你永远到不了 ...

  2. CF 429B B.Working out (四角dp)

    题意: 两个人一个从左上角一个从左下角分别开始走分别走向右下角和右上角,(矩阵每个格子有数)问到达终点后可以得到的最大数是多少,并且条件是他们两个相遇的时候那个点的数不能算 思路: 首先这道题如果暴力 ...

  3. LCS(HDU_5495 循环节)

    传送门:LCS 题意:给出两个序列an和bn,想在给出一个序列pn,问经过a[p1],,,,a[pn]和b[p1],,,b[pn]变换后序列a和序列b的最长公共子序列的长度是多少. 思路:对a[i]- ...

  4. React组件设计技巧

    React组件设计 组件分类 展示组件和容器组件 展示组件 容器组件 关注事物的展示 关注事物如何工作 可能包含展示和容器组件,并且一般会有DOM标签和css样式 可能包含展示和容器组件,并且不会有D ...

  5. software collection

    software software Table of Contents 1. Privacy 2. GFW 2.1. google search 2.2. 修改 DNS 服务器 2.2.1. 修改ip ...

  6. phpcms 搭建宣传网站首页

    1 .修改后台提交的表单信息展示: 文件路径: phpcms\modules\formguide\template\formguide_info_list.tpl.php function getQu ...

  7. Java并发编程:线程池 - 实例

    代码块: public class test { public static void main(String[] args) { test t = new test(); ThreadPoolExe ...

  8. js 发布订阅模式

    //发布订阅模式 class EventEmiter{ constructor(){ //维护一个对象 this._events={ } } on(eventName,callback){ if( t ...

  9. HDU——2824 The Euler function

    The Euler function Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Other ...

  10. spring boot日期转换

    spring boot 作为微服务简易架构.拥有其自身的特点.快速搭建架构 简单 快捷.这里我只是简单的介绍下我遇到的其中的  两个问题.第一前台页面传递的时间类型 无法自动映射到Java的 Date ...