在之前的博客中,我们分析过local模式下Actor的创建过程,最终还是调用了provider的actorOf的函数创建了Actor,在remote模式下provider就是RemoteActorRefProvider,所以这样就知道在哪里最终创建了Actor。
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这个函数也非常重要。最后一段代码的逻辑是根据配置信息判断最终是创建本地actor还是远程actor(也就是RemoteActorRef)。其实这一点还是有点不好理解的,怎么绕来绕去还是用LocalActorRef创建了actor呢?其实关于这一点,我们还需要从官网的文档找到一些细节。官网原文如下:
If you want to use the creation functionality in Akka remoting you have to further amend the application.conf
file in the following way (only showing deployment section):
akka {
actor {
deployment {
/sampleActor {
remote = "akka.tcp://[email protected]:2553"
}
}
}
}
The configuration above instructs Akka to react when an actor with path /sampleActor
is created, i.e. using system.actorOf(Props(...), "sampleActor")
. This specific actor will not be directly instantiated, but instead the remote daemon of the remote system will be asked to create the actor, which in this sample corresponds to [email protected]:2553
.
Once you have configured the properties above you would do the following in code:
-
val actor = system.actorOf(Props[SampleActor], "sampleActor") actor ! "Pretty slick"
The actor class SampleActor
has to be available to the runtimes using it, i.e. the classloader of the actor systems has to have a JAR containing the class.
这句话的意思大概是,你可以指定某个actor在远程节点创建!感觉真是神一样的操作!仅仅通过配置就能实现!至少我看到这段说明的时候,感到有点不可思议。当然前提是对应的Actor类能够在远程加载到,也就是包含该类的jar在远程被加载过。这就是RemoteActorRef存在的意义。
分析到这里我们发现,remote模式下,actor的创建基本都转发给了LocalActorRef,那么这样创建的actor能够跨网络传输吗。其实我们仔细思考一下大概就明白了,不管是什么模式创建的actor,一定会有一个本地actor的。毕竟我们发消息的代码都在本地,这就必须有一个本地ActorRef实例。如果要跨网络发消息,就必须拿到远程Actor的ActorRef,而ActorRef我们知道是可以序列化后跨网络传输的,获取到了远程的ActorRef,就可以发远程消息了。至于远程部署Actor,其实也就是在本地创建了一个特殊的Actor(RemoteActorRef、ReliableDeliverySupervisor)作为代理,把消息通过对应的网络对象转发到远程而已。关于这一点我们后面再分析。
remote模式下,Actor的创建也就基本结束了,我们发现,这个跟local模式下的大概流程差别也不是很大,只是维护了与网路相关的对象。不过正是这些网络对象,使得我们可以给远程actor发消息。