Введение в Akka Actors in Java

Введение в Akka Актеры в Java

1. Вступление

Akka is an open-source library that helps to easily develop concurrent and distributed applications с использованием Java или Scala с использованием модели акторов.

В этом руководствеwe’ll present the basic features like defining actors, how they communicate and how we can kill them. В заключительных примечаниях мы также отметим некоторые передовые методы работы с Akka.

2. Модель Актера

Модель актера не нова для компьютерного сообщества. Впервые он был представлен Карлом Эдди Хьюиттом в 1973 году в качестве теоретической модели для обработки параллельных вычислений.

Он начал показывать свою практическую применимость, когда индустрия программного обеспечения начала осознавать подводные камни внедрения параллельных и распределенных приложений.

An actor represents an independent computation unit. Некоторые важные характеристики:

  • актер инкапсулирует свое состояние и часть логики приложения

  • актеры взаимодействуют только через асинхронные сообщения и никогда через прямые вызовы методов

  • каждый участник имеет уникальный адрес и почтовый ящик, в который другие участники могут доставлять сообщения

  • субъект будет обрабатывать все сообщения в почтовом ящике в последовательном порядке (реализация почтового ящика по умолчанию представляет собой очередь FIFO)

  • система акторов организована в виде древовидной иерархии

  • актер может создавать других актеров, может отправлять сообщения любому другому актеру и останавливать себя, или любой актер создан

2.1. преимущества

Разработка параллельного приложения сложна, потому что нам нужно иметь дело с синхронизацией, блокировками и общей памятью. Используя акторов Akka, мы можем легко написать асинхронный код без необходимости блокировок и синхронизации.

Одним из преимуществ использования сообщений вместо вызовов методов является то, чтоthe sender thread won’t block to wait for a return value when it sends a message to another actor. Принимающий субъект ответит результатом, отправив ответное сообщение отправителю.

Еще одно большое преимущество использования сообщений - это то, что нам не нужно беспокоиться о синхронизации в многопоточной среде. Это связано с тем, чтоall the messages are processed sequentially.

Другое преимущество актерской модели Akka - обработка ошибок. Организуя актеров в иерархии, каждый актер может уведомить своего родителя о сбое, чтобы он мог действовать соответственно. Родительский актер может решить остановить или перезапустить дочерних актеров.

3. Настроить

Чтобы воспользоваться преимуществами акторов Akka, нам нужно добавить следующую зависимость отMaven Central:


    com.typesafe.akka
    akka-actor_2.12
    2.5.11

4. Создание актера

Как уже упоминалось, действующие лица определены в иерархической системе. Все акторы, которые имеют общую конфигурацию, будут определеныActorSystem.

А пока мы просто определимActorSystem со стандартной конфигурацией и собственным именем:

ActorSystem system = ActorSystem.create("test-system");

Несмотря на то, что мы еще не создали актеров, система уже будет содержать трех основных субъектов:

  • корневой субъект-хранитель, имеющий адрес «/», который в качестве имени состояния представляет корень иерархии системы субъекта

  • актер-хранитель пользователя, имеющий адрес «/ пользователь». Это будет родитель всех актеров, которых мы определили

  • субъект-хранитель системы, имеющий адрес «/ system». Это будет родитель для всех действующих лиц, определенных внутри системы Akka.

Любой актор Akka расширит абстрактный классAbstractActor и реализует методcreateReceive() для обработки входящих сообщений от других акторов:

public class MyActor extends AbstractActor {
    public Receive createReceive() {
        return receiveBuilder().build();
    }
}

This is the most basic actor we can create. Он может получать сообщения от других субъектов и отбрасывать их, потому что вReceiveBuilder. не определены соответствующие шаблоны сообщений. Мы поговорим о сопоставлении шаблонов сообщений позже в этой статье.

Теперь, когда мы создали нашего первого актера, мы должны включить его вActorSystem:

ActorRef readingActorRef
  = system.actorOf(Props.create(MyActor.class), "my-actor");

4.1. Конфигурация Актера

The Props class contains the actor configuration. Мы можем настроить такие вещи, как диспетчер, почтовый ящик или конфигурацию развертывания. Этот класс является неизменяемым, поэтому потокобезопасным, поэтому его можно использовать совместно при создании новых актеров.

Настоятельно рекомендуется и считается оптимальным определять фабричные методы внутри объекта-актера, которые будут обрабатывать создание объектаProps.

В качестве примера давайте определим актера, который будет выполнять некоторую обработку текста. Актер получит объектString, над которым он выполнит обработку:

public class ReadingActor extends AbstractActor {
    private String text;

    public static Props props(String text) {
        return Props.create(ReadingActor.class, text);
    }
    // ...
}

Теперь, чтобы создать экземпляр этого типа актера, мы просто используем фабричный методprops() для передачи аргументаString конструктору:

ActorRef readingActorRef = system.actorOf(
  ReadingActor.props(TEXT), "readingActor");

Теперь, когда мы знаем, как определить актера, давайте посмотрим, как они взаимодействуют внутри системы акторов.

5. Актер Обмен сообщениями

Чтобы взаимодействовать друг с другом, субъекты могут отправлять и получать сообщения от любого другого субъекта в системе. Эти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. Это помогает писать код, который легко понять и знает, какие сообщения может обрабатывать актор.

5.1. Отправка сообщений

Внутри системы Akka Actor сообщения отправляются с использованием методов:

  • сказать()

  • просить()

  • вперед()

When we want to send a message and don’t expect a response, we can use the tell() method. Это наиболее эффективный метод с точки зрения производительности:

readingActorRef.tell(new ReadingActor.ReadLines(), ActorRef.noSender());

Первый параметр представляет сообщение, которое мы отправляем на адрес актераreadingActorRef.

Второй параметр указывает, кто является отправителем. Это полезно, когда субъект, получающий сообщение, должен отправить ответ субъекту, отличному от отправителя (например, родитель отправителя).

Обычно мы можем установить второй параметр наnull илиActorRef.noSender(), потому что мы не ожидаем ответа. When we need a response back from an actor, we can use the ask() method:

CompletableFuture future = ask(wordCounterActorRef,
  new WordCounterActor.CountWords(line), 1000).toCompletableFuture();


При запросе ответа от актера возвращается объектCompletionStage, поэтому обработка остается неблокирующей.

Очень важный факт, на который мы должны обратить внимание, - это обработка ошибок у инсайдера, который ответит. To return a Future object that will contain the exception we must send a Status.Failure message to the sender actor.с

Это не выполняется автоматически, когда субъект генерирует исключение во время обработки сообщения, и время ожидания вызова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();
}

У нас также есть методforward(), аналогичныйtell(). Разница заключается в том, что первоначальный отправитель сообщения сохраняется при отправке сообщения, поэтому субъект, пересылающий сообщение, действует только как посредник:

printerActorRef.forward(
  new PrinterActor.PrintFinalResult(totalNumberOfWords), getContext());

5.2. Получение сообщений

Each actor will implement the createReceive() method, который обрабатывает все входящие сообщения. receiveBuilder() действует как оператор switch, пытаясь сопоставить полученное сообщение с определенным типом сообщений:

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.

6. Убийство актера

Когда мы закончили использовать актераwe can stop it by calling the stop() method из интерфейсаActorRefFactory:

system.stop(myActorRef);

Мы можем использовать этот метод для завершения любого дочернего актера или самого актера. Важно отметить, что остановка выполняется асинхронно и чтоcurrent message processing will finish перед завершением действия актера. No more incoming messages will be accepted in the actor mailbox.

Поstopping a parent actor,we’ll also send a kill signal to all of the child actors, которые были порождены им.

Когда нам больше не нужна система акторов, мы можем завершить ее, чтобы освободить все ресурсы и предотвратить утечки памяти:

Future terminateResponse = system.terminate();

Это остановит действующих лиц системы-опекуна, следовательно, всех действующих лиц, определенных в этой системе Akka.

We could also send a PoisonPill message любому актеру, которого мы хотим убить:

myActorRef.tell(PoisonPill.getInstance(), ActorRef.noSender());

СообщениеPoisonPill будет получено актером, как и любое другое сообщение, и помещено в очередь. The actor will process all the messages until it gets to the PoisonPill one. Только тогда актер начнет процесс прекращения.

Еще одно специальное сообщение, используемое для убийства актера, - это сообщениеKill. В отличие отPoisonPill,, актер выдастActorKilledException при обработке этого сообщения:

myActorRef.tell(Kill.getInstance(), ActorRef.noSender());

7. Заключение

В этой статье мы представили основы фреймворка Akka. Мы показали, как определить актеров, как они общаются друг с другом и как их прекратить.

В заключение приведем несколько рекомендаций по работе с Akka:

  • используйтеtell() вместоask(), когда производительность является проблемой

  • при использованииask() мы всегда должны обрабатывать исключения, отправляя сообщениеFailure

  • актеры не должны делиться каким-либо изменчивым состоянием

  • актер не должен быть объявлен в другом актере

  • actors aren’t stopped automatically, когда на них больше нет ссылок. Мы должны явно уничтожить актера, когда он нам больше не нужен, чтобы предотвратить утечку памяти.

  • сообщения, используемые актерамиshould always be immutable

Как всегда доступен исходный код статьиover on GitHub.