6.2.2 远程 REPL 动作
Akka 提供了两种方式来获取远程节点上 actor 的引用。 一种是通过路径来查询 actor, 另一种是创建 actor,获取它的引用,然后远程部署它。 我们从前一个选项开始。REPL 终端是快速探索新 scala 类的非常棒的交互式工具。 我们使用 stb 控制台来分别在两个 REPL 会话中启动 actor 系统。 使用 sbt 控制台在 chapter6 目录中启动一个终端。 我们得开启远程功能,因此要做的第一件事就是提供一个配置。 一般情况下, 你的 src/main/resource 目录下的 application.conf 配置文件会包含这个信息,但是在 REPL 会话的情况下,我们可以从一个字符串加载它。 列表 6.2 包含了用 :paste 命令来执行的 REPL 命令。
Listing 6.2 加载远程功能的 REPL 命令
scala> :paste
// Entering paste mode (ctrl-D to finish)
val conf = """
akka {
  actor {
    provider = "akka.remote.RemoteActorRefProvider"
  }
  remote {
    enabled-transports = ["akka.remote.netty.tcp"]
    netty.tcp {
      hostname = "0.0.0.0"
      port = 2551
    }
  }
}
"""
- 选择用 Remote ActorRef Provider 来启动远程功能
- 远程的配置区
- 允许 TCP 传输
- TCP 传输的设置,要监听的主机和端口号
我们来把这个配置字符串加载进 ActorSystem。 最值得注意的是,它定义了一个启动 akka-remote 模块的 Remoting ActorRefProvider。 如名字所示,它也为你的代码提供对远程 Actors 的引用 ActorRef。 列表 6.3 首先引入了要求的配置和 actor 包,然后将配置加载近 actor 系统:
Listing 6.3 远程功能的配置
scala> import com.typesafe.config._
import com.typesafe.config._
scala> import akka.actor._
import akka.actor._
scala> val config = ConfigFactory.parseString(conf)
config: com.typesafe.config.Config = ....
scala> val backend = ActorSystem("backend", config)
[Remoting] Starting remoting
.....
[Remoting] Remoting now listens on addresses:
[akka.tcp://[email protected]:2551]
backend: akka.actor.ActorSystem = akka://backend
- 将字符串解析为一个 Config 对象
- 使用解析后的 Config 对象创建 ActorSystem。
如果你这么敲了,你就从 REPL 启动了你的第一个开启了远程功能的 ActorSystem,就这么简单!只需要五行代码就能启动一个服务器。
后台的 ActorSystem 是使用开启了远程功能的配置对象来创建的。 如果你忘记了传递配置给 ActorSystem,你会得到一个可以运行的 ActorSystem,但是没有开启远程功能,因为与 Akka 一起打包的默认 application.conf 没有启动远程功能。 Remoting 模块现在在所有(网卡)接口的 2551 端口上为后台 actor 系统监听。 我们来加一个非常简单的 Actor,只是在控制台上打印出它收到的任何信息,这样我们可以看出一切正常。 列表 6.4 展示了我们需要的代码。
Listing 6.4 配置前台 actor
scala> :paste
// Entering paste mode (ctrl-D to finish)
class Simple extends Actor {
  def receive = {
    case m => println(s"received $m!")
  }
}
// Exiting paste mode, now interpreting.
scala> backend.actorOf(Props[Simple], "simple")
- 以 simple 的名字在后台的 actor 系统创建一个简单的 actor。
Simple actor 现在正运行在后台的 actor 系统中。 你需要注意到 Simple actor 是以 simple 的名字创建的。 这使得可以通过名字来在另一端来查找它。 该启动另一个终端了,开启 sbt 控制台,创建另一个开启了远程功能的 actor 系统,即前台。 我们使用与之前相同的命令,除了端口之外,我们要确保前台 actor 系统运行在一个不同的端口上。
scala> :paste
// Entering paste mode (ctrl-D to finish)
val conf = """
akka {
  actor {
    provider = "akka.remote.RemoteActorRefProvider"
  }
  remote {
    enabled-transports = ["akka.remote.netty.tcp"]
    netty.tcp {
      hostname = "0.0.0.0"
      port = 2552
    }
  }
}
"""
import com.typesafe.config._
import akka.actor._
val config = ConfigFactory.parseString(conf)
val frontend= ActorSystem("frontend", config)
[Remoting] Starting remoting
.....
[Remoting] Remoting now listens on addresses:
[akka.tcp://[email protected]:2552]
frontend: akka.actor.ActorSystem = akka://frontend
- 在另一个与后台不同的端口上运行前台,这样它们就可以运行在同一台机器上。
配置加载进前台的 actor system 中。 现在前台 actor 系统也正在运行,远程功能已开启。 我们来从前台获取一个到后台的 actor 系统的 Simple actor 的引用。 我们首先来构造一个 actor 路径。 下图展示了路径是如何组成的:
Figure 6.3 远程 actor 路径
我们可以构建一个字符串形式的路径,然后使用前端 actorsystem 的 ActorSelection 方法来找到它:
scala> :paste
// Entering paste mode (ctrl-D to finish)
val path = "akka.tcp://[email protected]:2551/user/simple"
val simple = frontend.actorSelection(path)
// Exiting paste mode, now interpreting.
path: String = akka.tcp://[email protected]:2551/user/simple
simple: akka.actor.ActorSelection = ActorSelection[Actor[akka.tcp://[email protected]:2551/]/user/simple]
- 到远程 Simple Actor 的路径
- 使用 actorSelection 来选择路径
可以把 actorSelection 方法想象成在 actor 等级中的查询。 在这种情况下,查询是一个到远程 actor 的精确路径。 ActorSelection 是一个代表所有使用 actorSelection 方法在 actor 系统中找到的所有 actor 的对象 Actor Selection 可以用来发给消息给所有匹配查询的 actors。 我们现在不需要 Simple Actor 的精确的 ActorRef,我们只想试试发送消息给它,查看 ActorSelection 是否工作。 因为后台的 actor 系统已经在其他控制台运行,你可以找下边这么做:
scala> simple ! "Hello Remote World!"
scala>
当你切换到你启动后台 actor 系统的终端是,你应该会看到打印出来了如下信息:
scala> received Hello Remote World!!
REPL 显示了消息是从前台发往后台的。 依我们的看法,用 REPL 控制台交互式地探索远程系统非常有价值,你会在下一章中见到更多的。 在表面之下发生的是,“Hello Remote World”消息被序列化,发送到 TCP socket,被远程模块接收,反序列化,发送给运行在后台的 Simple Actor。
你可能注意到,我们不需要写任何关于序列化的代码,那它为什么可以工作呢?因为我们发送了个简单的字符串("Hello Remote World!")。 Akka 对任何需要在网线上传输的消息默认使用 Java Serialization。 也可以用其他的序列化工具,你也可以写自己的序列化工具,这是我们要在第三部分处理的主题。 Akka 远程消息协议有一个字段,其中包含序列化工具的名字,其被用来对消息进行序列化,这样接收的远程模块就可以反序列化装载的字节。 用来表示消息的类需要时可序列化的(Serializable),且在两端的 classpath 路径中都可以找到。 幸运的是,标准的 case 类和 case 对象默认都是可序列化的,可以用作 goticks.com 的应用。 既然你已经了解了如何在 REPL 中查询远程 actor 并发送消息给它,我们在下一节中看看如何在 goticks.com 应用中使用它。
Footnote 3 Serializable 是标记接口,并没有保证什么。如果你使用‘非标准’的构造,你需要保证它能正常工作。