注重版权,若要转载烦请附上作者和链接
作者:Joshua_yi
链接:https://blog.csdn.net/weixin_44984664/article/details/124732703
文章目录
一、Akka介绍
Akka 一词据说来源于瑞典的一座山,我们说到 Akka时,通常是指一个分布式工具集,用于协调远程计算资源来进行一些工作。Akka 是 Actor并发模型的一种现代化实现。现在的 Akka 可以认为是从许多其他技术发展演化而来的,它借鉴了 Erlang 的 Actor 模型实现,同时又引入了许多新特性,帮助构建能够处理如今大规模问题的应用程序。——《Akka入门与实践》,Jason GoodWin
Akka 是一个用于在 JVM 上构建高并发、分布式和可容错的事件驱动应用程序的运行时工具包。Akka 既可以用于 Java,也可以用于 Scala。
Akka 提供:
- 不使用原子或锁之类的低级并发构造的多线程行为,甚至可以避免你考虑内存可见性问题。
- 系统及其组件之间的透明远程通信,使你不再编写和维护困难的网络代码。
- 一个集群的、高可用的体系结构,具有弹性、可按需扩展性,使你能够提供真正的反应式系统。
应用
目前 Akka 已经在多家互联网&软件公司广泛使用,比如 eBay、Amazon、VMWare、PayPal、阿里、惠普、豌豆荚等,涉及行业包括游戏、金融投资、医疗保健、数据分析等。
使用场景包括:
- 服务后端:比如 rest web,websocket 服务,分布式消息处理等。
- 并发&并行:比如日志异步处理,密集数据计算等。
- 对高并发和密集计算的系统,Akka 都是适用的
相关资料
- Gitter Chat,Akka 在线交流平台;
- Akka Forums,Akka 论坛;
- Akka in GitHub,Akka 开源项目仓库;
- Akka Official Website,Akka 官网;
- Akka Java API,Akka 应用程序编程接口。
- https://github.com/guobinhit/akka-guide 学习手册(ZH)
- https://github.com/jasongoodwin/learning-akka 学习手册(EN)
二、Actor模型
(一)基础概念
Actor模型是并发计算的理论模型,Akka的核心是Actor模型的一种实现,Actors 是 Akka 的执行单元。这些设计让编写正确的并发、并行和分布式系统更加容易。
Actor是一个并发原语,可以理解为一个工人,能够处理任务的进程和线程。每个实例的开销只有大约 300 字节。(1G 内存可容纳接近 300 万个 Actor)
Actor 每次只同步处理一个消息。邮箱本质上是等待 Actor 处理的一个工作队列。
概念
- Actor:一个表示工作节点的并发原语,同步处理接收到的消息。Actor 可以保存并修改内部状态。
- 消息:用于跨进程(比如多个 Actor 之间)通信的数据。
- 消息传递:一种软件开发范式,通过传递消息来触发各种行为,而不是直接触发行为。
- 邮箱地址:消息传递的目标地址,当 Actor 空闲时会从该地址获取消息进行处理。
- 邮箱:在 Actor 处理消息前具体存储消息的地方。可以将其看作是一个消息队列。
- Actor 系统:多个 Actor 的集合以及这些 Actor 的邮箱地址、邮箱和配置等。
(二)Actor 模型如何满足现代分布式系统的需求?(非阻塞保证)
常见的编程实践不能合适地满足现代系统的需求。Actor 模型以一种原则性的方式解决了这些缺点,允许系统以更好地匹配我们的构思模型的方式运行。Actor 模型抽象允许你从通信的角度来考虑你的代码。
1、等待自由
如果保证每个调用都以有限的步骤完成,则方法是无等待的,即等待自由(wait-freedom)。如果一个方法是有界“无等待”的,那么步骤的数量有一个有限的上限。
根据这个定义,无等待方法永远不会被阻塞,因此不会发生死锁。此外,由于每个 Actor 都可以在有限的步骤之后(调用完成时)继续进行,因此无等待方法没有饥饿。
2、锁自由
锁自由(lock-freedom)比等待自由更弱。在无锁调用的情况下,某些方法以有限的步数完成可能导致无限的等待。这个定义意味着没有死锁的调用是不可能的。另一方面,某些调用以有限的步骤完成的保证不足以保证所有调用最终都完成。换句话说,锁自由不足以保证不发生饥饿。
3、障碍自由
障碍自由(obstruction-freedom)是本文讨论的最薄弱的非阻塞保证。如果一个方法在某个时间点之后独立执行(其他线程不执行任何步骤,例如:挂起),则该方法称为无障碍的,它以有限的步骤完成。所有锁自由对象都是无障碍的,但相反的情况通常是不正确的。
乐观并发控制(OCC)方法通常是无障碍的。OCC 方法是,每个 Actor 都试图在共享对象上执行其操作,但如果 Actor 检测到来自其他对象的冲突,则会回滚修改,并根据某些计划重试。如果有一个时间点,其中一个 Actor 是唯一的尝试者,那么操作将成功。
三、Actor
Actor是一个并发原语,可以理解为一个工人,能够处理任务的进程和线程。每个实例的开销只有大约 300 字节。Actor 和对象的不同之处在于其不能被直接读取、修改或是调用。反之,Actor 只能
通过消息传递的方式与外界进行通信。消息传递是异步的。无论是处理消息还是回复消息,Actor 对外界都没有依赖。
Actor 是封装状态和行为的对象( 是状态、行为、邮箱、子 Actor 和监督者策略的容器)。它们通过交换放在收件人邮箱中的消息进行专门的通信。从某种意义上说,Actor 是面向对象编程最严格的形式,但最好将其视为“人”:当与 Actor 一起建模解决方案时,设想一组人员并为其分配子任务,将其功能安排到组织结构中,并考虑如何升级失败。
Actor 系统的典型特征是,任务被拆分和委托,直到它们变得足够小,可以一块处理。这样做,不仅任务本身结构清晰,而且结果 Actor 可以根据他们应该处理哪些消息、应该如何正常反应以及应该如何处理失败来进行推理。如果一个 Actor 没有处理特定情况的方法,它会向其监督 Actor 发送相应的失败消息,请求帮助。然后,递归结构允许在正确的级别处理故障。
- Actor 每次只同步处理一个消息。
- 每个Actor的创建者就是他的监督者
- Actor 有一个明确的生命周期,当不再被引用时它们不会被自动销毁
- 控制当 Actor 终止时如何释放资源
使用 Actor 允许我们: - 在不使用锁的情况下强制封装。
- 利用协同实体对信号作出反应、改变状态、相互发送信号的模型来驱动整个应用程序向前发展。
- 不要担心执行机制与我们的世界观不匹配。
(一)组件介绍
- Actor引用:为了从 Actor 模型中获益,需要将 Actor 对象从外部屏蔽。因此,使用 Actor 引用将 Actor 表示为外部对象
- 状态:Actor 对象通常包含一些反映 Actor 可能处于的状态的变量。这可以是一个显式状态机(例如,使用「FSM」模块),也可以是一个计数器、一组监听器、挂起的请求等。
- 行为:每次处理消息时,它都与 Actor 的当前行为相匹配。行为(Behavior)指的是一个函数,它定义了在该时间点对消息做出反应时要采取的操作。在构造 Actor 对象期间定义的初始行为是特殊的,因为重新启动 Actor 会将其行为重置为初始行为。
- 邮箱:Actor 的目的是处理消息,这些消息是从其他 Actor(或从 Actor 系统外部)发送给 Actor 的。连接发送方和接收方的部分是 Actor 的邮箱:每个 Actor 只有一个邮箱,所有发送方都将其消息排队。有不同的邮箱实现可供选择,默认为FIFO
- 子Actor:每个 Actor 都可能是一个监督者:如果它为分配子任务创建子 Actor,它将自动对它们进行监督。
- Actor终止:一旦一个 Actor 终止,即以一种不被重启处理的方式失败、自行停止或被其监督者停止,它将释放其资源,将其邮箱中的所有剩余邮件排入系统的“死信邮箱”,该邮箱将它们作为死信(DeadLetters)转发到事件流(EventStream)。然后在 Actor 引用中用系统邮箱替换原 Actor 的邮箱,将所有新消息作为死信重定向到事件流。在监管时,我们需要根据不同的情况选择不同的处理方案(比如停止、重启、恢复或者失败上溯)和策略(比如 1 vs 1、1 vs N 策略)。
(二)Akka的Actor层级
Akka 的 Actor 总是属于父 Actor。通常,你可以通过调用getContext().actorOf()来创建 Actor。与创建一个“独立的” Actor 不同,这会将新 Actor 作为一个子节点注入到已经存在的树中:创建 Actor 的 Actor 成为新创建的子 Actor 的父级。
所有的 Actor 都有一个共同的父节点,即用户守护者。可以使用system.actorOf()在当前 Actor 下创建新的 Actor 实例。
事实上,在代码中创建 Actor 之前,Akka 已经在系统中创建了三个 Actor 。这些内置的 Actor 的名字包含guardian,因为他们守护他们所在路径下的每一个子 Actor。守护者 Actor 包括:
- /,根守护者(root guardian)。这是系统中所有 Actor 的父 Actor,也是系统本身终止时要停止的最后一个 Actor。
- /user,守护者(guardian)。这是用户创建的所有 Actor 的父 Actor。不要让用户名混淆,它与最终用户和用户处理无关。使用 Akka 库创建的每个 Actor 都将有一个事先准备的固定路径/user/。
- /system,系统守护者(system guardian)。这是除上述三个 Actor 外,系统创建的所有 Actor 的父 Actor,
(三)Actors 和 Java 内存模型
通过 Akka 中的 Actor 实现,多个线程可以通过两种方式在共享内存上执行操作:
- 如果消息发送给某个 Actor(例如由另一个 Actor)。在大多数情况下,消息是不可变的,但是如果该消息不是正确构造的不可变对象,没有“先于发生”规则,则接收者可能会看到部分初始化的数据结构。
- 如果 Actor 在处理消息时更改其内部状态,并在稍后处理另一条消息时访问该状态。重要的是要认识到,对于 Actor 模型,你不能保证同一线程将对不同的消息执行相同的 Actor。
为了防止 Actor 出现可见性和重新排序问题,Akka 保证以下两条“先于发生”规则: - Actor 发送规则:向 Actor 发送消息的过程发生在同一个 Actor 接收消息之前。
- Actor 后续处理规则:一条消息的处理发生在同一个 Actor 处理下一条消息之前。
注释:在外行术语中,这意味着当 Actor 处理下一条消息时,Actor 内部字段的更改是可见的。因此,Actor 中的字段不必是volatile或equivalent的。
这两个规则仅适用于同一个 Actor 实例,如果使用不同的 Actor,则这两个规则无效。 - 为了避免共享可变状态陷阱,消息应该是不可变的。
(三)Actor生命周期
Actor 在运行时中会经历不同的阶段,比如创建、运行、重启和销毁等,这一系列的过程或状态,我们称之为生命周期。在理想情况下,Actor 应该像一头老黄牛一样任劳任怨地不停工作,但实际情况往往是,当某个不算常态的错误发生时(比如网络偶尔超时),我们想让它重启一下,然后重复之前的动作;又或者,当一个程序异常被抛出时,我们不得不停止该 Actor,让它不能继续走下去,这些行为有可能是手动触发的,也可能是 Actor 的监督-容错机制导致的。无论怎样,当这些行为发生时,我们都希望能及时感知并做有效处理。
Actor 生命周期主要包括启动(Start)、恢复(Resume)、重启(Restart)、停止(Stop)这四个阶段,每个阶段都会伴随着自身状态信息的变化。
1、创建并启动
当通过 actorOf 创建并启动 Actor 时,该 Actor 除了默认拥有已指定的 path 外,也会被分配一个 UID(可以通过 getSelf().path().uid()得到该值),作为它的唯一标识。在 Actor 启动后,会默认调用preStart 方法,在该方法里,我们可以做些资源初始化的操作。
2、恢复运行
当 Actor 出现某种异常后,通过容错机制,可以让该 Actor 恢复并继续运行,此时 Actor 会延用之前的实例,状态也会被保留下来。
3、重启
当 Actor 出现某种异常后,通过容错机制,可以让该 Actor 执行重启。重启的具体过程如下:
调用旧实例的preRestart 方法,该方法默认情况下会停掉所有子级 Actor,并调用postStop 方法;
创建新实例,并在新实例上调用postRestart 方法,该方法默认情况下会调用preStart 方法。
Actor 重启后,path 和 UID 不变,这意味着假如要继续使用该 Actor,不需要重新获取ActorRef 对象,使用之前那个就可以了。同时也得注意到:Actor 重启后不会保留自身状态。
4、停止
当 Actor 停止时,会调用postStop 方法,同时会发送一条Terminated信息给自己的监控者,告知自己已处于停止状态。
(四)Actor 最佳实践
- Actor 应该像好的同事一样:高效地工作,而不是不必要地打扰其他人,并且避免占用资源。翻译成编程语言,这意味着以事件驱动的方式处理事件并生成响应(或更多请求)。Actor 不应在可能是锁、网络套接字等外部实体上阻塞,即占用线程时被动等待,除非这是不可避免的。
- 不要在 Actor 之间传递可变对象。为了确保这一点,最好选择不可变的消息。如果通过将它们的可变状态暴露到外部来破坏 Actor 的封装,则会返回正常的 Java 并发域,并存在所有的缺点。
- Actor 被设计成行为和状态的容器,接受这一点意味着不经常在消息中发送行为。其中一个风险是不小心在 Actor 之间共享可变状态,而这种对 Actor 模型的违反不幸地破坏了所有属性。
- 顶级 Actor 是错误内核的最核心部分,因此要谨慎地创建它们,并且更倾向于真正的分层系统。这对于故障处理(同时考虑配置的粒度和性能)有好处,而且它还减少了对守护者 Actor 的压力,如果使用过度,这是一个单一的竞争点。
(五)Actor 使用
actor常见API操作
https://github.com/guobinhit/akka-guide/blob/master/articles/actors/actors.md
1、创建
Actor 类是通过继承AbstractActor类并在createReceive方法中设置“初始行为”来实现的。
createReceive方法没有参数,并返回AbstractActor.Receive。它定义了 Actor 可以处理哪些消息,以及如何处理消息的实现。可以使用名为ReceiveBuilder的生成器来构建此类行为。在AbstractActor中,有一个名为receiveBuilder的方便的工厂方法。
Props是一个配置类,用于指定创建 Actor 的选项,将其视为不可变的,因此可以自由共享用于创建 Actor 的方法,包括关联的部署信息(例如,要使用哪个调度程序,请参阅下面的更多内容)。
static class DemoActor extends AbstractActor {
/**
* Create Props for an actor of this type.
*
* @param magicNumber The magic number to be passed to this actor’s constructor.
* @return a Props for creating this actor, which can then be further configured (e.g. calling
* `.withDispatcher()` on it)
*/
static Props props(Integer magicNumber) {
// You need to specify the actual type of the returned actor
// since Java 8 lambdas have some runtime type information erased
return Props.create(DemoActor.class, () -> new DemoActor(magicNumber));
}
private final Integer magicNumber;
public DemoActor(Integer magicNumber) {
this.magicNumber = magicNumber;
}
@Override
public Receive createReceive() {
return receiveBuilder()
.match(
Integer.class,
i -> {
getSender().tell(i + magicNumber, getSelf());
})
.build();
}
}
static class SomeOtherActor extends AbstractActor {
// Props(new DemoActor(42)) would not be safe
ActorRef demoActor = getContext().actorOf(DemoActor.props(42), "demo");
// ...
}
AbstractActor
它还提供:
getSelf()
,对 Actor 的ActorRef的引用getSender()
,前一次接收到的消息的发送方 Actor 的引用supervisorStrategy()
,用户可重写定义用于监视子 Actor 的策略
该策略通常在 Actor 内部声明,以便访问决策函数中 Actor 的内部状态:由于故障作为消息发送给其监督者并像其他消息一样进行处理(尽管不属于正常行为),因此 Actor 内的所有值和变量都可用,就像sender引用一样(报告失败的是直接子级;如果原始失败发生在一个遥远的后代中,则每次仍向上一级报告)。getContext()
公开 Actor 和当前消息的上下文信息,例如:- 创建子 Actor 的工厂方法(actorOf)
- Actor 所属的系统
- 父级监督者
- 受监督的子级
- 生命周期监控
- 如Become/Unbecome中所述的热交换行为栈
2、发送消息
消息通过以下方法之一发送给 Actor。
tell
的意思是“发送并忘记(fire-and-forget)”,例如异步发送消息并立即返回。ask
异步发送消息,并返回一个表示可能的答复。
每一个发送者都有消息顺序的保证。
使用ask会带来性能方面的影响,因为有些东西需要跟踪它何时超时,需要有一些东西将一个Promise连接到ActorRef中,并且还需要通过远程处理实现它。所以,我们更倾向于使用tell,只有当你有足够的理由时才应该使用ask。
在所有这些方法中,你可以选择传递自己的ActorRef。将其作为一种实践,因为这样做将允许接收者 Actor 能够响应你的消息,因为发送者引用与消息一起发送。
Tell: Fire-forget
这是发送消息的首选方式,它不用等待消息返回,因此不是阻塞的。这提供了最佳的并发性和可伸缩性的特性。
// don’t forget to think about who is the sender (2nd argument)
target.tell(message, getSelf());
发送方引用与消息一起传递,并在处理此消息时通过getSender()方法在接收 Actor 中使用。在一个 Actor 内部,通常是getSelf()作为发送者,但在某些情况下,回复(replies)应该路由到另一个 Actor,例如,父对象,其中tell的第二个参数将是另一个 Actor。在 Actor 外部,如果不需要回复,则第二个参数可以为null;如果在 Actor 外部需要回复,则可以使用下面描述的ask模式。
Ask: Send-And-Receive-Future
ask模式涉及 Actor 和Future,因此它是作为一种使用模式而不是ActorRef上的一种方法提供的:
import static akka.pattern.Patterns.ask;
import static akka.pattern.Patterns.pipe;
import java.util.concurrent.CompletableFuture;
final Duration t = Duration.ofSeconds(5);
// using 1000ms timeout
CompletableFuture<Object> future1 =
ask(actorA, "request", Duration.ofMillis(1000)).toCompletableFuture();
// using timeout from above
CompletableFuture<Object> future2 = ask(actorB, "another request", t).toCompletableFuture();
CompletableFuture<Result> transformed =
CompletableFuture.allOf(future1, future2)
.thenApply(
v -> {
String x = (String) future1.join();
String s = (String) future2.join();
return new Result(x, s);
});
pipe(transformed, system.dispatcher()).to(actorC);
这个例子演示了ask和pipeTo模式在Future上的结合,因为这可能是一个常见的组合。请注意,以上所有内容都是完全非阻塞和异步的:ask生成一个,其中两个使用CompletableFuture.allOf和thenApply方法组合成新的Future,然后pipe在CompletionStage上安装一个处理程序,以将聚合的Result提交给另一个 Actor。
使用ask会像使用tell一样向接收 Actor 发送消息,并且接收 Actor 必须使用getSender().tell(reply, getSelf())才能完成返回的值。ask操作涉及创建一个用于处理此回复的内部 Actor,该 Actor 需要有一个超时,在该超时之后才能将其销毁,以便不泄漏资源;
- 要完成带异常的,你需要向发件人发送akka.actor.Status.Failure消息。当 Actor 在处理消息时抛出异常,不会自动执行此操作。
try {
String result = operation();
getSender().tell(result, getSelf());
} catch (Exception e) {
getSender().tell(new akka.actor.Status.Failure(e), getSelf());
throw e;
}
如果 Actor 未完成,则它将在超时期限(指定为ask方法的参数)之后过期;这将使用AskTimeoutException完成CompletionStage。
这可以用于注册回调以便在完成时获取通知,从而提供避免阻塞的方法。
- 当使用Future的回调时,内部 Actor 需要小心避免关闭包含 Actor 的引用,即不要从回调中调用方法或访问封闭 Actor 的可变状态。这将破坏 Actor 的封装,并可能引入同步错误和竞态条件,因为回调将被同时调度到封闭 Actor。目前还没有一种方法可以在编译时检测到这些非法访问。
3、接收消息
Actor 必须通过在AbstractActor中实现createReceive方法来定义其初始接收行为:
@Overridepublic Receive createReceive() {
return receiveBuilder().match(String.class,
s -> System.out.println(s.toLowerCase()))
.build();
}
返回类型是AbstractActor.Receive,它定义了 Actor 可以处理哪些消息,以及如何处理这些消息的实现。可以使用名为ReceiveBuilder的生成器来构建此类行为。下面是一个例子:
import akka.actor.AbstractActor;
import akka.event.Logging;
import akka.event.LoggingAdapter;
public class MyActor extends AbstractActor {
private final LoggingAdapter log = Logging.getLogger(getContext().getSystem(), this);
@Override
public Receive createReceive() {
return receiveBuilder()
.match(
String.class,
s -> {
log.info("Received String message: {}", s);
})
.matchAny(o -> log.info("received unknown message"))
.build();
}
}
四、Akka相关库
akka-actor
:Akka 的核心库是akka-actor,但是 Actor 在整个 Akka 库中使用,它提供了一个一致的、集成的模型,使你能够独立地解决并发或分布式系统设计中出现的挑战。从鸟瞰图(birds-eye)来看,Actor 是一种将 OOP 的支柱之一封装用到极致的编程范式。与对象不同,Actor 不仅封装了他们的状态,而且还封装了他们的执行。与 Actor 的通信不是通过方法调用,而是通过传递消息。虽然这种差异看起来很小,但实际上它允许我们在并发性和远程通信方面打破 OOP 的限制。akka-remote
:远程处理(Remoting)使存活(live)在不同计算机上的 Actor 能够无缝地交换消息。akka-cluster
:如果你有一组协作解决某些业务问题的 Actor 系统,那么你可能希望以规范的方式管理这些系统集(set of systems)。当远程处理解决了与远程系统组件寻址和通信的问题时,集群(Clustering)使你能够将它们组织成一个由成员协议绑定在一起的“元系统(meta-system)”。在大多数情况下,你希望使用集群模块(Cluster module)而不是直接使用远程处理。集群在远程处理之上提供了一组额外的服务,这是大多数实际应用程序所需要的。akka-cluster-sharding
:分片(Sharding)有助于解决在 Akka 集群成员之间分配一组 Actor 的问题。分片是一种模式,它主要与持久性(Persistence)一起使用,以将一大组需要持久化的实体(由 Actors 支持)平衡到集群的各个成员,并在成员崩溃或离开时将它们迁移到其他节点。akka-cluster-singleton
:分布式系统中的一个常见(事实上,有点太常见)用例是让一个实体负责一个给定的任务,该任务在集群的其他成员之间共享,并且在主机系统发生故障时进行迁移。尽管这无疑会给整个集群带来一个限制扩展的常见瓶颈,但在某些情况下,使用这种模式是不可避免的。集群单例(Cluster singleton)允许集群选择一个 Actor 系统,该系统将承载一个特定的 Actor,而其他系统始终可以独立地访问该 Actor 承担的服务。
单例模块可用于解决这些挑战:- 如何确保整个集群中只有一个服务实例在运行。
- 如何确保服务处于启动状态,即使承载它的系统在缩小规模的过程中崩溃或关闭。
- 如何从集群的任何成员访问这个实例,假设它可以随着时间迁移到其他系统。
akka-persistence
:就像 OOP 中的对象一样,Actor 将其状态保存在易失性内存中。一旦系统正常关闭或突然崩溃,内存中的所有数据都将丢失。持久性(Persistence)提供了使 Actor 能够持久化导致其当前状态的事件的模式。启动时,可以重播事件以恢复由 Actor 承载的实体的状态。可以查询事件流并将其输入到其他处理管道(例如外部大数据集群)或备用视图(如报表)中。akka-distributed-data
:在最终一致性可以接受的情况下,可以在 Akka 集群中的节点之间共享数据,甚至在集群分区面前也可以接受读和写。这可以通过使用「无冲突的复制数据类型(CRDTs)」来实现,其中不同节点上的写入可以并发进行,并随后以可预测的方式进行合并。分布式数据(Distributed Data)模块提供了共享数据和许多有用数据类型的基础结构。
五、Demo
两个Actor类型,一个是打招呼的人,一个是打印器
(一)Greeter
package com.lightbend.akka.sample;
import akka.actor.AbstractActor;
import akka.actor.ActorRef;
import akka.actor.Props;
import com.lightbend.akka.sample.Printer.Msg;
public class Greeter extends AbstractActor {
// 实例化
static public Props props(String message, ActorRef printerActor) {
return Props.create(Greeter.class, () -> new Greeter(message, printerActor));
}
public Greeter(String name, ActorRef printerActor) {
this.name = name;
this.printerActor = printerActor;
}
// 接收的消息类型
static public class WhoSayHello {
public final String who;
public WhoSayHello(String who) {
this.who = who;
}
}
private final String name;
// 使用actor的引用来操作actor
private final ActorRef printerActor;
@Override
public Receive createReceive() {
return receiveBuilder()
.match(WhoSayHello.class, wtg -> {
printerActor.tell(new Msg(wtg.who + " say hello to " + name), getSelf());
})
.build();
}
}
(二)Printer
package com.lightbend.akka.sample;
import akka.actor.AbstractActor;
import akka.actor.Props;
import akka.event.Logging;
import akka.event.LoggingAdapter;
public class Printer extends AbstractActor {
// 实例化
static public Props props() {
return Props.create(Printer.class, () -> new Printer());
}
// 接收的消息
static public class Msg {
public final String message;
public Msg(String message) {
this.message = message;
}
}
private LoggingAdapter log = Logging.getLogger(getContext().getSystem(), this);
public Printer() {
}
@Override
public Receive createReceive() {
return receiveBuilder()
.match(Msg.class, msg -> {
log.info(msg.message);
})
.build();
}
}
(三)Main
package com.lightbend.akka.sample;
import java.io.IOException;
import com.lightbend.akka.sample.Greeter.WhoSayHello;
import akka.actor.ActorRef;
import akka.actor.ActorSystem;
public class AkkaQuickstart {
public static void main(String[] args) {
final ActorSystem system = ActorSystem.create("helloakka");
try {
//#create-actors
final ActorRef printerActor =
system.actorOf(Printer.props(), "printerActor");
final ActorRef aGreeter =
system.actorOf(Greeter.props("A", printerActor), "AGreeter");
final ActorRef bGreeter =
system.actorOf(Greeter.props("B", printerActor), "BGreeter");
//#create-actors
//#main-send-messages
aGreeter.tell(new WhoSayHello("B"), ActorRef.noSender());
bGreeter.tell(new WhoSayHello("A"), ActorRef.noSender());
//#main-send-messages
System.out.println(">>> Press ENTER to exit <<<");
System.in.read();
} catch (IOException ioe) {
} finally {
system.terminate();
}
}
}
输出
B say hello to A
A say hello to B