一、初识 Actor
1.1 初识 Actor
Actor模型
是一种并发计算的理论模型,而 Akka
的核心其实是 Actor
模型的一种实现。Akka 借鉴了 Erlang 的 Actor 模型实现,同时又引入了许多新特性,能够构建处理如今大规模问题的应用程序。据说 Akka
一词来源于瑞典的一座冰山。
在 Actor模型
中,Actor 是一个并发原语。更简单的来说,可以把每个 Actor 都看做一个独立的人,每个人都各司其职。就像能够工作或是处理任务的进程或线程一样。这样就没有那么多需要共享的数据。就像你不希望你的银行卡密码被第二个人知道。
1.1.1 Actor 和消息传递
在面向对象编程中,对象的特点之一就是能够被调用,一个对象可以访问或修改另一个对象的属性,也可以直接调用另一个对象的方法。这种情况在单线程模式下运行是没有问题的;但是在多线程的模式下就要对共享的数据进行加锁。
Actor
与 对象
的不同之处在于其不能直接被读取、修改或调用。反之,Actor
只能通过 消息传递
的方式与外界进行通信。
Actor
每次只能同步处理一个消息。邮箱
本质上是等待 Actor 处理的 工作队列
。处理一个消息时,为了能够做出响应,Actor 可以修改内部状态,创建更多的 Actor 或是将消息发送给其他 Actor。
通常用 Actor系统
表示多个 Actor 的集合以及所有跟 Actor 相关的东西,包括地址、邮箱及配置。
- Actor:一个表示工作节点的并发原语,同步处理接收到的消息。Actor 可以保存并修改内部状态。
- 消息:用于跨进程(比如多个 Actor 之间)通信的数据。
- 消息传递:一种软件开发范式,通过传递消息来触发各种行为,而不是直接触发行为。
- 邮箱地址:消息传递的目标地址,当 Actor 空闲时会从该地址获取消息进行处理。
- 邮箱:在 Actor 处理消息前,存储消息的地方。实质是一个消息队列。
- Actor 系统:多个 Actor 的集合,以及这些 Actor 的地址、邮箱及配置。
Actor
模型的好处之一就是可以 消除共享状态
。因为一个 Actor
一次只处理一条消息,所以可以在 Actor内部
安全的保存状态。
1.1.2 Actor 容错机制
20 世纪 80 年代,爱立信在 Erlang 语言中实现了 Actor 模型,用于嵌入式电信应用程序;并且在该实现中引用了通过监督机制(Supervision)提供的容错性概念,使得 AXD301 这个应用能够提供 99.9999999% 的可用性。
Actor
模型也通过 监督机制
来提供 容错性
。监督机制基本上是指把处理 响应错误
的责任 交给
出错对象 以外
的实体。这意味着一个 Actor 可以负责监督他的子 Actor,它会监控子 Actor 的运行错误,并且根据子 Actor 生命周期中的运行表现执行相应的操作。当一个正在运行的 Actor 发生错误时,监督机制
提供 默认
的处理方式是 重新启动
发生错误的 Actor(实际上是重新创建)。
这种重新创建出错 Actor 的处理方式基于一种假设:意外发生的错误是由错误状态导致的,因此移除并重新创建应用程序中出错的部分可以将其修复,并回复正常工作。我们也可以编写自定义的错误处理方式作为监督策略,这样一来基本上就可以采取任何操作将应用程序回复至工作状态。
1.2 创建一个 Akka 应用
创建一个 Actor
,该 Actor
接收一个消息,将消息的值存入 map 以修改 Actor 的内部状态。
1.2.1 构造消息结构
Actor
需要从其 邮箱
中获取消息,并 查看
消息中的操作提示。通过消息的 类/类型
来决定具体的操作。
消息必须永远是不可变的,这样可以避免通过 多个执行上下文/线程 来做一些不安全的操作,从而避免一些奇怪而又出人意料的行为。同样要记住这些消息除了会发给本地的 Actor 之外,也可能会发给另一台机器上的 Actor。一般来说,应该始终在所有代码中优先使用不可变对象。
package com.shiw.ak;
/**
* Created by Sweeney on 2017/9/26.
*/
public class SetRequest {
private final String key;
private final Object value;
public SetRequest(String key, Object value) {
this.key = key;
this.value = value;
}
public String getKey() {
return key;
}
public Object getValue() {
return value;
}
@Override
public String toString() {
return "SetRequest{" +
"key='" + key + '\'' +
", value=" + value +
'}';
}
}
1.2.2 定义 Actor 收到消息后的行为
package com.shiw.ak;
import akka.actor.AbstractActor;
import akka.event.Logging;
import akka.event.LoggingAdapter;
import akka.japi.pf.ReceiveBuilder;
import java.util.HashMap;
import java.util.Map;
/**
* Created by Sweeney on 2017/9/26.
*/
public class AkkademyDb extends AbstractActor {
protected final LoggingAdapter log = Logging.getLogger(context().system(), this);
protected final Map<String, Object> map = new HashMap<>();
@Override
public Receive createReceive() {
return new ReceiveBuilder().match(SetRequest.class, message -> {
log.info("Received Set request : {}", message);
map.put(message.getKey(), message.getValue());
}).matchAny(o -> log.info("Received unknown message : {}", o)).build();
}
}
继承了 AbstractActor
的一般类就是一个 Actor。ReceiveBuilder
中的 match
方法有点像 case
语句,只不过 match
方法可以匹配 类的类型
,正式一点说,这就是 模式匹配
。
1.2.3 使用单元测试验证代码
Akka 提供了一套测试工具,其中几乎包含了测试 Actor 代码需要的所有工具。
package com.shiw.ak;
import akka.actor.ActorRef;
import akka.actor.ActorSystem;
import akka.actor.Props;
import akka.testkit.TestActorRef;
import org.junit.Assert;
import org.junit.Test;
/**
* Created by Sweeney on 2017/9/26.
*/
public class AkkademyDbTest {
ActorSystem system = ActorSystem.create();
@Test
public void test() {
TestActorRef<AkkademyDb> aRef = TestActorRef.create(system, Props.create(AkkademyDb.class));
aRef.tell(new SetRequest("key", "value"), ActorRef.noSender());
AkkademyDb akkademyDB = aRef.underlyingActor();
Assert.assertEquals(akkademyDB.map.get("key"), "value");
}
}
我们再 Actor 系统中创建 Actor 返回的是一个 ActorRef
,我们可以将消息发送至该 ActorRef。有了 Actor 系统和 ActorRef 后 Akka 就足以在 Actor 系统中创建这个简单的 Actor。
Actor 之间的交互是通过消息传递来进行的。使用 tell
将消息放到 Actor 的邮箱中。 通过 tell 的第二个参数(ActorRef.noSender()
)定义该消息并不需要任何响应对象。
因为这里用的是 TestActorRef
,所以只有在 tell
调用请求处理完毕后,才会继续执行后面的代码。但要注意的是,这个例子并没有展示出 Actor API 的异步特性。而这并不是常见的用法,通常情况下 tell
是一个 异步操作
,调用后会立即返回。
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于