1.Akka 与 Actor 模型
Akka 是一个开发并发、容错和可伸缩应用的框架。它是 Actor Model 的一个实现,和
Erlang 的并发模型很像。在 Actor 模型中,所有的实体被认为是独立的 actors。actors 和其他 actors 通过发送异步消息通信。Actor 模型的强大来自于异步。它也可以显式等待响应,这使得可以执行同步操作。但是,强烈不建议同步消息,因为它们限制了系统的伸缩性。每个 actor 有一个邮箱(mailbox),它收到的消息存储在里面。另外,每一个 actor 维护自身单独的状态。
一个 Actors 网络如下所示:
每个 actor 是一个单一的线程,它不断地从其邮箱中 poll(拉取)消息,并且连续不断地处理。对于已经处理过的消息的结果,actor 可以改变它自身的内部状态或者发送一个新消息或者孵化一个新的 actor。尽管单个的 actor 是自然有序的,但一个包含若干个 actor 的系统却是高度并发的并且极具扩展性的。因为那些处理线程是所有 actor 之间共享的。这也是我们为什么不该在 actor 线程里调用可能导致阻塞的“调用”。因为这样的调用可能会阻塞该线程使得他们无法替其他 actor 处理消息。
(1) Actor 系统
一个 Actor 系统包含了所有存活的 actors。它提供的共享服务包括调度、配置和日志等。
Actor 系统同时包含一个线程池,所有 actor 从这里获取线程。
多个 Actor 系统可以在一台机器上共存。如果一个 Actor 系统通过RemoteActorRefProvider 启动,它就可以被其他机器上的 Actor 系统发现。Actor 系统能够自动识别消息是发送给本地机器还是远程机器的 Actor 系统。在本地通信的情况下,消息通过共享存储器高效的传输。在远程通信的情况下,消息通过网络栈发送。
所有 Actors 都是继承来组织的。每个新创建的 actor 将其创建的 actor 视作父 actor。继承被用来监督。每个父 actor 对自己的子 actor 负责监督。如果在一个子 actor 发生错误,父 actor 将会收到通知。如果这个父 actor 可以解决这个问题,它就重新启动这个子 actor。如果这个错误父 actor 无法处理,它可以把这个错误传递给自己的父 actor。
第一个 actor 通过系统创建,由/user 这个 actor 负责监督。详细的 Actor 的继承制度可以参考 doc.akka.io//docs/akka/…
2.使用 Akka
Akka 系统的核心 ActorSystem 和 Actor,若需构建一个 Akka 系统,首先需要创建 ActorSystem,创建完 ActorSystem 后,可通过其创建 Actor
(注意:Akka 不允许直接 new 一个 Actor,只能通过 Akka 提供的某些 API 才能创建或查找 Actor,一般会通过ActorSystem.actorOf 和 ActorContext.actorOf 来创建 Actor),
另外,我们只能通过 ActorRef
(Actor 的引用, 其对原生的 Actor 实例做了良好的封装,外界不能随意修改其内部状态)来与 Actor 进行通信。如下代码展示了如何配置一个 Akka 系统。
// 1. 构建 ActorSystem
// 使用缺省配置
ActorSystem system = ActorSystem.create("sys");
// 也可显示指定 appsys 配置
ActorSystem system1 = ActorSystem.create("helloakka",ConfigFactory.load("appsys"));
// 2. 构建 Actor,获取该 Actor 的引用,即 ActorRef
ActorRef helloActor = system.actorOf(Props.create(HelloActor.class),"helloActor");
// 3. 给 helloActor 发送消息
helloActor.tell("hello helloActor", ActorRef.noSender());
// 4. 关闭ActorSystem
system.terminate();
(1) Actor 路径
在 Akka 中,创建的每个 Actor 都有自己的路径,该路径遵循 ActorSystem 的层级结构,
大致如下:
1)本地路径
在上面代码中,本地 Actor 路径为 akka://sys/user/helloActor
含义如下:
⚫ sys,创建的 ActorSystem 的名字;
⚫ user,通过 ActorSystem.actorOf 和 ActorContext.actorOf 方法创建的 Actor 都属于/user 下,与/user 对应的是/system, 其是系统层面创建的,与系统整体行为有关,
在开发阶段并不需要对其过多关注
⚫ helloActor,我们创建的 HelloActor
2)远程路径
在上面代码中,远程 Actor 路径为 akka.tcp://sys@l27.0.0.1:2020/user/remoteActor 含义如下:
⚫ akka.tcp,远程通信方式为 tcp;
⚫ sys@127.0.0.1:2020,ActorSystem 名字及远程主机 ip 和端口号。
⚫ user,与本地的含义一样
⚫ remoteActor,创建的远程 Actor
(2) 获取 Actor
若提供了 Actor 的路径,可以通过路径获取到 ActorRef,然后与之通信,代码如下所示:
ActorSystem system = ActorSystem.create("sys");
ActorSelection as = system.actorSelection("/path/to/actor");
Timeout timeout = new Timeout(Duration.create(2, "seconds"));
Future<ActorRef> fu = as.resolveOne(timeout);
fu.onSuccess(new OnSuccess<ActorRef>() {
@Override
public void onSuccess(ActorRef actor) {
System.out.println("actor:" + actor);
actor.tell("hello actor", ActorRef.noSender());
}
}, system.dispatcher());
fu.onFailure(new OnFailure() {
@Override
public void onFailure(Throwable failure) {
System.out.println("failure:" + failure);
}
}, system.dispatcher());
若需要与远端 Actor 通信,路径中必须提供 ip:port。
3.与 Actor 通信
Akka 有两种核心的异步通信方式:tell 和 ask。
(1) tell 方式
当使用 tell 方式时,表示仅仅使用异步方式给某个 Actor 发送消息,无需等待 Actor 的响应结果,并且也不会阻塞后续代码的运行,如:
helloActor.tell("hello helloActor", ActorRef.noSender());
其中:
第一个参数为发送的消息,它可以是任何可序列化的数据或对象,
第二个参数表示发送者,通常来讲是另外一个 Actor 的引用,这里用的是ActorRef.noSender()表示无发送者(实际上是一个叫做 deadLetters 的 Actor)。
(2) ask 方式
当我们需要从 Actor 获取响应结果时,可使用 ask 方法,ask 方法会将返回结果包装在 scala.concurrent.Future 中,然后通过异步回调获取返回结果。如调用方:
HelloActor 处理消息方法的代码大致如下:
上面主要介绍了 Akka 中的 ActorSystem、Actor,及与 Actor 的通信;Flink 借此构建了其底层通信系统。
// 异步发送消息给 Actor,并获取响应结果
Future<Object> fu = Patterns.ask(printerActor, "hello helloActor", timeout);
fu.onComplete(new OnComplete<Object>() {
@Override
public void onComplete(Throwable failure, String success) throws Throwable {
if (failure != null) {
System.out.println("failure is " + failure);
} else {
System.out.println("success is " + success);
}
}
}, system.dispatcher());
// HelloActor 处理消息方法的代码大致如下:
private void handleMessage (Object object){
if (object instanceof String) {
String str = (String) object;
log.info("[HelloActor] message is {}, sender is {}", str, getSender().path().toString());
// 给发送者发送消息
getSender().tell(str, getSelf());
}
}