Introdução aos atores Akka em Java
1. Introdução
Akka is an open-source library that helps to easily develop concurrent and distributed applications usando Java ou Scala, aproveitando o modelo de ator.
Neste tutorial,we’ll present the basic features like defining actors, how they communicate and how we can kill them. Nas notas finais, também observaremos algumas práticas recomendadas ao trabalhar com Akka.
2. O modelo de ator
O modelo de ator não é novo para a comunidade da ciência da computação. Foi introduzido pela primeira vez por Carl Eddie Hewitt em 1973, como um modelo teórico para lidar com computação simultânea.
Começou a mostrar sua aplicabilidade prática quando a indústria de software começou a perceber as armadilhas da implementação de aplicativos concorrentes e distribuídos.
An actor represents an independent computation unit. Algumas características importantes são:
-
um ator encapsula seu estado e parte da lógica do aplicativo
-
atores interagem apenas através de mensagens assíncronas e nunca através de chamadas diretas de método
-
cada ator tem um endereço exclusivo e uma caixa de correio na qual outros atores podem entregar mensagens
-
o ator processará todas as mensagens na caixa de correio em ordem sequencial (a implementação padrão da caixa de correio é uma fila FIFO)
-
o sistema do ator é organizado em uma hierarquia semelhante a uma árvore
-
um ator pode criar outros atores, pode enviar mensagens para qualquer outro ator e parar a si mesmo ou qualquer ator criado
2.1. Vantagens
O desenvolvimento de aplicativos simultâneos é difícil porque precisamos lidar com sincronização, bloqueios e memória compartilhada. Usando atores Akka, podemos escrever facilmente código assíncrono sem a necessidade de bloqueios e sincronização.
Uma das vantagens de usar mensagem em vez de chamadas de método é quethe sender thread won’t block to wait for a return value when it sends a message to another actor. O ator receptor responderá com o resultado enviando uma mensagem de resposta ao remetente.
Outro grande benefício de usar mensagens é que não precisamos nos preocupar com a sincronização em um ambiente multithread. Isso se deve ao fato deall the messages are processed sequentially.
Outra vantagem do modelo de ator Akka é o tratamento de erros. Ao organizar os atores em uma hierarquia, cada ator pode notificar seu pai da falha, para que possa agir de acordo. O ator pai pode decidir parar ou reiniciar os atores filhos.
3. Configuração
Para aproveitar as vantagens dos atores Akka, precisamos adicionar a seguinte dependência deMaven Central:
com.typesafe.akka
akka-actor_2.12
2.5.11
4. Criando um ator
Como mencionado, os atores são definidos em um sistema hierárquico. Todos os atores que compartilham uma configuração comum serão definidos por umActorSystem.
Por enquanto, vamos simplesmente definir umActorSystem com a configuração padrão e um nome personalizado:
ActorSystem system = ActorSystem.create("test-system");
Embora ainda não tenhamos criado nenhum ator, o sistema já conterá 3 atores principais:
-
o ator responsável pela raiz com o endereço "/" que, como o nome indica, representa a raiz da hierarquia do sistema do ator
-
o ator guardião do usuário com o endereço "/ user". Este será o pai de todo o ator que definimos
-
o ator responsável pelo sistema com o endereço "/ system". Este será o pai de todos os atores definidos internamente pelo sistema Akka
Qualquer ator Akka estenderá a classe abstrataAbstractActor e implementará o métodocreateReceive() para lidar com as mensagens recebidas de outros atores:
public class MyActor extends AbstractActor {
public Receive createReceive() {
return receiveBuilder().build();
}
}
This is the most basic actor we can create. Ele pode receber mensagens de outros atores e irá descartá-las porque nenhum padrão de mensagem correspondente foi definido noReceiveBuilder.. Falaremos sobre a correspondência de padrão de mensagem posteriormente neste artigo.
Agora que criamos nosso primeiro ator, devemos incluí-lo noActorSystem:
ActorRef readingActorRef
= system.actorOf(Props.create(MyActor.class), "my-actor");
4.1. Configuração de Ator
The Props class contains the actor configuration. Podemos configurar coisas como o despachante, a caixa de correio ou a configuração de implantação. Essa classe é imutável e, portanto, segura para threads, para que possa ser compartilhada ao criar novos atores.
É altamente recomendado e considerado uma prática recomendada definir os métodos de fábrica dentro do objeto de ator que irão lidar com a criação do objetoProps.
Para exemplificar, vamos definir um ator que fará algum processamento de texto. O ator receberá um objetoString no qual fará o processamento:
public class ReadingActor extends AbstractActor {
private String text;
public static Props props(String text) {
return Props.create(ReadingActor.class, text);
}
// ...
}
Agora, para criar uma instância desse tipo de ator, apenas usamos o método de fábricaprops() para passar o argumentoString para o construtor:
ActorRef readingActorRef = system.actorOf(
ReadingActor.props(TEXT), "readingActor");
Agora que sabemos como definir um ator, vamos ver como eles se comunicam dentro do sistema de atores.
5. Mensagem do ator
Para interagir entre si, os atores podem enviar e receber mensagens de qualquer outro ator no sistema. Essesmessages 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. Isso ajuda a escrever um código fácil de entender e saber quais mensagens um ator pode manipular.
5.1. Enviando Mensagens
Dentro do sistema do agente Akka, as mensagens são enviadas usando os métodos:
-
contar()
-
pergunte ()
-
frente()
When we want to send a message and don’t expect a response, we can use the tell() method. Este é o método mais eficiente de uma perspectiva de desempenho:
readingActorRef.tell(new ReadingActor.ReadLines(), ActorRef.noSender());
O primeiro parâmetro representa a mensagem que enviamos para o endereço do atorreadingActorRef.
O segundo parâmetro especifica quem é o remetente. Isso é útil quando o ator que recebe a mensagem precisa enviar uma resposta a um ator que não seja o remetente (por exemplo, o pai do ator que envia).
Normalmente, podemos definir o segundo parâmetro paranull ouActorRef.noSender(), porque não esperamos uma resposta. When we need a response back from an actor, we can use the ask() method:
CompletableFuture
Ao solicitar uma resposta de um ator, um objetoCompletionStage é retornado, de forma que o processamento permanece sem bloqueio.
Um fato muito importante ao qual devemos prestar atenção é a manipulação de erros por dentro do ator que responderá. To return a Future object that will contain the exception we must send a Status.Failure message to the sender actor.
Isso não é feito automaticamente quando um ator lança uma exceção durante o processamento de uma mensagem e a chamadaask() atingirá o tempo limite e nenhuma referência à exceção será vista nos logs:
@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();
}
Também temos o métodoforward(), que é semelhante atell(). A diferença é que o remetente original da mensagem é mantido ao enviar a mensagem; portanto, o ator que encaminha a mensagem atua apenas como ator intermediário:
printerActorRef.forward(
new PrinterActor.PrintFinalResult(totalNumberOfWords), getContext());
5.2. Recebendo Mensagens
Each actor will implement the createReceive() method, que lida com todas as mensagens recebidas. OreceiveBuilder() atua como uma instrução switch, tentando combinar a mensagem recebida com o tipo de mensagem definido:
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. Matando um ator
Quando terminamos de usar um atorwe can stop it by calling the stop() method da interfaceActorRefFactory:
system.stop(myActorRef);
Podemos usar esse método para finalizar qualquer ator filho ou o próprio ator. É importante observar que a parada é feita de forma assíncrona e quecurrent message processing will finish antes do ator ser encerrado. No more incoming messages will be accepted in the actor mailbox.
Porstopping a parent actor,we’ll also send a kill signal to all of the child actors que foram gerados por ele.
Quando não precisarmos mais do sistema de ator, podemos encerrá-lo para liberar todos os recursos e evitar qualquer vazamento de memória:
Future terminateResponse = system.terminate();
Isso interromperá os atores guardiões do sistema, portanto, todos os atores definidos neste sistema Akka.
We could also send a PoisonPill message para qualquer ator que desejamos matar:
myActorRef.tell(PoisonPill.getInstance(), ActorRef.noSender());
A mensagemPoisonPill será recebida pelo ator como qualquer outra mensagem e colocada na fila. The actor will process all the messages until it gets to the PoisonPill one. Somente então o ator começará o processo de rescisão.
Outra mensagem especial usada para matar um ator é a mensagemKill. Ao contrário doPoisonPill,, o ator lançará umActorKilledException ao processar esta mensagem:
myActorRef.tell(Kill.getInstance(), ActorRef.noSender());
7. Conclusão
Neste artigo, apresentamos o básico da estrutura Akka. Mostramos como definir atores, como eles se comunicam e como finalizá-los.
Concluiremos com algumas práticas recomendadas ao trabalhar com Akka:
-
usetell() em vez deask() quando o desempenho for uma preocupação
-
ao usarask(), devemos sempre lidar com as exceções enviando uma mensagemFailure
-
atores não devem compartilhar nenhum estado mutável
-
um ator não deve ser declarado dentro de outro ator
-
actors aren’t stopped automatically quando eles não são mais referenciados. Devemos destruir explicitamente um ator quando não precisarmos mais dele para evitar vazamentos de memória
-
mensagens usadas por atoresshould always be immutable
Como sempre, o código-fonte do artigo está disponívelover on GitHub.