1. Introduction
1.介绍
Akka is an open-source library that helps to easily develop concurrent and distributed applications using Java or Scala by leveraging the Actor Model.
Akka是一个开源的库,通过利用角色模型,帮助使用Java或Scala轻松开发并发和分布式应用。
In this tutorial, we’ll present the basic features like defining actors, how they communicate and how we can kill them. In the final notes, we’ll also note some best practices when working with Akka.
在本教程中,我们将介绍一些基本功能,如定义角色、它们如何通信以及我们如何杀死它们。在最后的说明中,我们还将指出使用Akka时的一些最佳实践。
2. The Actor Model
2.演员模式
The Actor Model isn’t new to the computer science community. It was first introduced by Carl Eddie Hewitt in 1973, as a theoretical model for handling concurrent computation.
代理人模型对计算机科学界来说并不陌生。它是由Carl Eddie Hewitt在1973年首次提出的,作为处理并发计算的理论模型。
It started to show its practical applicability when the software industry started to realize the pitfalls of implementing concurrent and distributed applications.
当软件行业开始意识到实施并发和分布式应用的陷阱时,它开始显示出其实际的适用性。
An actor represents an independent computation unit. Some important characteristics are:
一个角色代表一个独立的计算单元。一些重要的特征是。
- an actor encapsulates its state and part of the application logic
- actors interact only through asynchronous messages and never through direct method calls
- each actor has a unique address and a mailbox in which other actors can deliver messages
- the actor will process all the messages in the mailbox in sequential order (the default implementation of the mailbox being a FIFO queue)
- the actor system is organized in a tree-like hierarchy
- an actor can create other actors, can send messages to any other actor and stop itself or any actor is has created
2.1. Advantages
2.1.优点
Developing concurrent application is difficult because we need to deal with synchronization, locks and shared memory. By using Akka actors we can easily write asynchronous code without the need for locks and synchronization.
开发并发应用程序是困难的,因为我们需要处理同步、锁和共享内存。通过使用Akka actors,我们可以轻松地编写异步代码,而不需要锁和同步。
One of the advantages of using message instead of method calls is that the sender thread won’t block to wait for a return value when it sends a message to another actor. The receiving actor will respond with the result by sending a reply message to the sender.
使用消息而不是方法调用的优点之一是,发送者线程在向另一个角色发送消息时不会阻塞等待返回值。接收的角色将通过向发送者发送一条回复消息来回应结果。
Another great benefit of using messages is that we don’t have to worry about synchronization in a multi-threaded environment. This is because of the fact that all the messages are processed sequentially.
使用消息的另一大好处是,我们不必担心多线程环境下的同步问题。这是因为,所有的消息都是按顺序处理的。
Another advantage of the Akka actor model is error handling. By organizing the actors in a hierarchy, each actor can notify its parent of the failure, so it can act accordingly. The parent actor can decide to stop or restart the child actors.
Akka行为体模型的另一个优势是错误处理。通过将行为体组织在一个层次结构中,每个行为体都可以将故障通知其父代,这样它就可以采取相应的行动。父角色可以决定停止或重新启动子角色。
3. Setup
3.设置
To take advantage of the Akka actors we need to add the following dependency from Maven Central:
为了利用Akka行动者的优势,我们需要从Maven中心添加以下依赖。
<dependency>
<groupId>com.typesafe.akka</groupId>
<artifactId>akka-actor_2.12</artifactId>
<version>2.5.11</version>
</dependency>
4. Creating an Actor
4.创建一个演员
As mentioned, the actors are defined in a hierarchy system. All the actors that share a common configuration will be defined by an ActorSystem.
如前所述,演员被定义在一个层次系统中。所有共享一个共同配置的角色将由一个ActorSystem.来定义。
For now, we’ll simply define an ActorSystem with the default configuration and a custom name:
现在,我们只需定义一个具有默认配置和自定义名称的ActorSystem。
ActorSystem system = ActorSystem.create("test-system");
Even though we haven’t created any actors yet, the system will already contain 3 main actors:
尽管我们还没有创建任何角色,但该系统已经包含了3个主要角色:。
- the root guardian actor having the address “/” which as the name states represent the root of the actor system hierarchy
- the user guardian actor having the address “/user”. This will be the parent of all the actor we define
- the system guardian actor having the address “/system”. This will be the parent for all the actors defined internally by the Akka system
Any Akka actor will extend the AbstractActor abstract class and implement the createReceive() method for handling the incoming messages from other actors:
任何Akka角色都将扩展AbstractActor抽象类并实现createReceive()方法,以处理来自其他角色的传入消息。
public class MyActor extends AbstractActor {
public Receive createReceive() {
return receiveBuilder().build();
}
}
This is the most basic actor we can create. It can receive messages from other actors and will discard them because no matching message patterns are defined in the ReceiveBuilder. We’ll talk about message pattern matching later on in this article.
这是我们可以创建的最基本的角色。它可以接收来自其他角色的消息,并将其丢弃,因为在ReceiveBuilder中没有定义匹配的消息模式。我们将在本文后面讨论消息模式匹配。
Now that we’ve created our first actor we should include it in the ActorSystem:
现在我们已经创建了第一个演员,我们应该把它纳入ActorSystem。
ActorRef readingActorRef
= system.actorOf(Props.create(MyActor.class), "my-actor");
4.1. Actor Configuration
4.1.演员配置
The Props class contains the actor configuration. We can configure things like the dispatcher, the mailbox or deployment configuration. This class is immutable, thus thread-safe, so it can be shared when creating new actors.
Props类包含了角色配置。我们可以配置诸如调度器、邮箱或部署配置。这个类是不可改变的,因此是线程安全的,所以在创建新的角色时可以共享它。
It’s highly recommended and considered a best-practice to define the factory methods inside the actor object that will handle the creation of the Props object.
强烈建议并被认为是最佳做法的是,在处理创建Props对象的actor对象中定义工厂方法。
To exemplify, let’s define an actor the will do some text processing. The actor will receive a String object on which it’ll do the processing:
为了举例说明,让我们定义一个角色,它将做一些文本处理。这个角色将接收一个String对象,它将对该对象进行处理。
public class ReadingActor extends AbstractActor {
private String text;
public static Props props(String text) {
return Props.create(ReadingActor.class, text);
}
// ...
}
Now, to create an instance of this type of actor we just use the props() factory method to pass the String argument to the constructor:
现在,为了创建这种类型的角色的实例,我们只需使用props()工厂方法,将String参数传递给构造函数。
ActorRef readingActorRef = system.actorOf(
ReadingActor.props(TEXT), "readingActor");
Now that we know how to define an actor, let’s see how they communicate inside the actor system.
现在我们知道了如何定义一个角色,让我们看看他们如何在角色系统内进行交流。
5. Actor Messaging
5.演员信息传递
To interact with each other, the actors can send and receive messages from any other actor in the system. These messages can be any type of object with the condition that it’s immutable.
为了彼此互动,行为体可以发送和接收来自系统中任何其他行为体的消息。这些消息可以是任何类型的对象,条件是它是不可改变的。
It’s a best practice to define the messages inside the actor class. This helps to write code that is easy to understand and know what messages an actor can handle.
在角色类中定义消息是一种最佳做法。这有助于编写易于理解的代码,并知道一个角色可以处理哪些消息。
5.1. Sending Messages
5.1.发送消息
Inside the Akka actor system messages are sent using methods:
在Akka行为体系统中,消息是使用方法发送的。
- tell()
- ask()
- forward()
When we want to send a message and don’t expect a response, we can use the tell() method. This is the most efficient method from a performance perspective:
当我们想要发送一个消息,并且不期望得到响应时,我们可以使用tell()方法。从性能角度来看,这是最有效的方法。
readingActorRef.tell(new ReadingActor.ReadLines(), ActorRef.noSender());
The first parameter represents the message we send to the actor address readingActorRef.
第一个参数代表我们向演员地址readingActorRef发送的信息。
The second parameter specifies who the sender is. This is useful when the actor receiving the message needs to send a response to an actor other than the sender (for example the parent of the sending actor).
第二个参数指定谁是发送者。当接收消息的行为体需要向发送者以外的行为体(例如,发送行为体的父母)发送响应时,这很有用。
Usually, we can set the second parameter to null or ActorRef.noSender(), because we don’t expect a reply. When we need a response back from an actor, we can use the ask() method:
通常,我们可以将第二个参数设置为null或ActorRef.noSender(),因为我们并不期望得到回复。当我们需要一个角色的回复时,我们可以使用ask()方法:。
CompletableFuture<Object> future = ask(wordCounterActorRef,
new WordCounterActor.CountWords(line), 1000).toCompletableFuture();
When asking for a response from an actor a CompletionStage object is returned, so the processing remains non-blocking.
当要求一个角色的响应时,会返回一个CompletionStage对象,所以处理过程保持非阻塞。
A very important fact that we must pay attention to is error handling insider the actor which will respond. To return a Future object that will contain the exception we must send a Status.Failure message to the sender actor.
我们必须注意的一个非常重要的事实是错误处理,在行为体内部,它将作出回应。为了返回一个包含异常的Future对象,我们必须向发送者角色发送一个Status.Failure消息。
This is not done automatically when an actor throws an exception while processing a message and the ask() call will timeout and no reference to the exception will be seen in the logs:
当一个角色在处理消息时抛出一个异常时,这不会自动完成,ask()调用将超时,在日志中不会看到对异常的引用。
@Override
public Receive createReceive() {
return receiveBuilder()
.match(CountWords.class, r -> {
try {
int numberOfWords = countWordsFromLine(r.line);
getSender().tell(numberOfWords, getSelf());
} catch (Exception ex) {
getSender().tell(
new akka.actor.Status.Failure(ex), getSelf());
throw ex;
}
}).build();
}
We also have the forward() method which is similar to tell(). The difference is that the original sender of the message is kept when sending the message, so the actor forwarding the message only acts as an intermediary actor:
我们还有forward()方法,它与tell()类似。不同的是,在发送消息时保留了消息的原始发送者,所以转发消息的行为者只是作为一个中间行为者。
printerActorRef.forward(
new PrinterActor.PrintFinalResult(totalNumberOfWords), getContext());
5.2. Receiving Messages
5.2.接收信息
Each actor will implement the createReceive() method, which handles all incoming messages. The receiveBuilder() acts like a switch statement, trying to match the received message to the type of messages defined:
每个角色将实现createReceive()方法,它处理所有传入的消息。receiveBuilder()的作用就像一个开关语句,试图将收到的消息与定义的消息类型相匹配。
public Receive createReceive() {
return receiveBuilder().matchEquals("printit", p -> {
System.out.println("The address of this actor is: " + getSelf());
}).build();
}
When received, a message is put into a FIFO queue, so the messages are handled sequentially.
当收到消息时,消息被放入FIFO队列,所以消息是按顺序处理的。
6. Killing an Actor
6.杀死一个演员
When we finished using an actor we can stop it by calling the stop() method from the ActorRefFactory interface:
当我们使用完一个角色时,我们可以通过调用stop()方法来停止它,来自ActorRefFactory接口。
system.stop(myActorRef);
We can use this method to terminate any child actor or the actor itself. It’s important to note stopping is done asynchronously and that the current message processing will finish before the actor is terminated. No more incoming messages will be accepted in the actor mailbox.
我们可以用这个方法来终止任何子行为体或行为体本身。需要注意的是,停止是异步进行的,在行为体被终止之前,当前的消息处理将结束。行为体的邮箱将不再接受任何传入的消息。
By stopping a parent actor, we’ll also send a kill signal to all of the child actors that were spawned by it.
通过停止一个父角色,我们也会向所有由它产生的子角色发送一个杀死信号。
When we don’t need the actor system anymore, we can terminate it to free up all the resources and prevent any memory leaks:
当我们不再需要角色系统时,我们可以终止它以释放所有的资源并防止任何内存泄漏。
Future<Terminated> terminateResponse = system.terminate();
This will stop the system guardian actors, hence all the actors defined in this Akka system.
这将停止系统守护者的行动,因此在这个Akka系统中定义的所有行动者。
We could also send a PoisonPill message to any actor that we want to kill:
我们也可以向任何我们想要杀死的行为者发送一个PoisonPill消息。
myActorRef.tell(PoisonPill.getInstance(), ActorRef.noSender());
The PoisonPill message will be received by the actor like any other message and put into the queue. The actor will process all the messages until it gets to the PoisonPill one. Only then the actor will begin the termination process.
PoisonPill消息将像其他消息一样被行为体接收并放入队列中。行为体将处理所有的消息,直到它到达PoisonPill一条。只有到那时,行为体才会开始终止过程。
Another special message used for killing an actor is the Kill message. Unlike the PoisonPill, the actor will throw an ActorKilledException when processing this message:
另一个用于杀死一个角色的特殊消息是Kill消息。与PoisonPill不同,演员在处理这个消息时将抛出一个ActorKilledException。
myActorRef.tell(Kill.getInstance(), ActorRef.noSender());
7. Conclusion
7.结论
In this article, we presented the basics of the Akka framework. We showed how to define actors, how they communicate with each other and how to terminate them.
在这篇文章中,我们介绍了Akka框架的基础知识。我们展示了如何定义角色,它们如何相互通信以及如何终止它们。
We’ll conclude with some best practices when working with Akka:
最后,我们将介绍一些使用Akka时的最佳实践。
- use tell() instead of ask() when performance is a concern
- when using ask() we should always handle exceptions by sending a Failure message
- actors should not share any mutable state
- an actor shouldn’t be declared within another actor
- actors aren’t stopped automatically when they are no longer referenced. We must explicitly destroy an actor when we don’t need it anymore to prevent memory leaks
- messages used by actors should always be immutable
As always, the source code for the article is available over on GitHub.
一如既往,该文章的源代码可在GitHub上获得。