16
1 Akka: Programação concorrente Fique por dentro Este artigo irá apresentar um modelo de concorrência baseado em troca de mensagens e uma implementação deste modelo, o framework Akka. O desenvolvimento de aplicações que demandam alto volume de requisições e alta disponibilidade é um grande desafio para quem adota o modelo convencional com threads e locks. Neste cenário, o Akka é uma opção interessante que usa um paradigma diferente de concorrência que possibilita uma escalabilidade muito grande. Criar aplicações que suportam alta concorrência sempre foi um desafio, independente da tecnologia adotada. O crescimento da capacidade de processamento dos computadores, resultado do aumento do número de núcleos, memória, disco, etc. permitiu a criação de aplicações com mais poder computacional e com suporte a um maior número de requisições. E quando o número de requisições extrapola a capacidade de um servidor, podemos ainda estruturar a aplicação para funcionar em um cluster de máquinas, aumentando mais a capacidade de concorrência. Nos primórdios do Java, quando precisávamos lidar com concorrência, criávamos uma ou várias threads e administrávamos manualmente esse volume necessário para a execução eficiente de determinada tarefa. Na versão 5, o Java trouxe uma API de concorrência de alto nível, a java.util.concurrent [1], nos livrando da gestão manual das threads através da utilização de Executors. Algum tempo depois, no Java 7, foi introduzido o Fork/Join framework [2], otimizando a utilização de threads e elevando ainda mais a capacidade de concorrência. Contudo, mesmo com estas evoluções na API de concorrência do Java, esta ainda se trata de algo de baixo nível quando desejamos criar aplicações com alta concorrência sem nos preocuparmos com detalhes de threads, pool de threads, etc. Neste contexto, o Akka [3] é um framework que nos oferece esta desejada abstração. Escrito em Scala e com API para Scala e Java, o Akka foi desenvolvido sobre a API de concorrência do Java e que possibilita ao desenvolvedor utilizar um modelo de concorrência baseado em atores. Com o Akka, todas as buzzwords de concorrência, como threads, pools, locks, etc., deixam de fazer sentido. Assim, nos concentramos apenas na estruturação da nossa aplicação em atores e na lógica de negócio de cada ator. Porém, antes de começarmos a falar de atores, vamos entender em que modelo de concorrência os atores se encaixam, para compreendê-los melhor. Modelos de concorrência De maneira generalista, podemos dividir os modelos de concorrência em dois paradigmas: concorrência usando estado compartilhado e concorrência usando troca de mensagens. O modelo de estado compartilhado tem sido largamente adotado no campo, mas em aplicações maiores ou com maior concorrência, o modelo de troca de mensagens é preferido, pois sua característica assíncrona facilita a distribuição do processamento, reduzindo gargalos. Estado compartilhado x troca de mensagens O modelo de estado compartilhado é o modelo que adotamos normalmente em nossas aplicações. Neste modelo o código a ser paralelizado é executado simultaneamente através da criação de processos ou threads.

Akka programação concorrente

Embed Size (px)

Citation preview

Page 1: Akka   programação concorrente

1

Akka: Programação concorrente

Fique por dentro

Este artigo irá apresentar um modelo de concorrência baseado em troca de mensagens e uma implementação

deste modelo, o framework Akka. O desenvolvimento de aplicações que demandam alto volume de

requisições e alta disponibilidade é um grande desafio para quem adota o modelo convencional com threads

e locks.

Neste cenário, o Akka é uma opção interessante que usa um paradigma diferente de concorrência que

possibilita uma escalabilidade muito grande.

Criar aplicações que suportam alta concorrência sempre foi um desafio, independente da tecnologia adotada.

O crescimento da capacidade de processamento dos computadores, resultado do aumento do número de

núcleos, memória, disco, etc. permitiu a criação de aplicações com mais poder computacional e com suporte

a um maior número de requisições.

E quando o número de requisições extrapola a capacidade de um servidor, podemos ainda estruturar a

aplicação para funcionar em um cluster de máquinas, aumentando mais a capacidade de concorrência.

Nos primórdios do Java, quando precisávamos lidar com concorrência, criávamos uma ou várias threads e

administrávamos manualmente esse volume necessário para a execução eficiente de determinada tarefa.

Na versão 5, o Java trouxe uma API de concorrência de alto nível, a java.util.concurrent [1], nos livrando

da gestão manual das threads através da utilização de Executors. Algum tempo depois, no Java 7, foi

introduzido o Fork/Join framework [2], otimizando a utilização de threads e elevando ainda mais a

capacidade de concorrência.

Contudo, mesmo com estas evoluções na API de concorrência do Java, esta ainda se trata de algo de baixo

nível quando desejamos criar aplicações com alta concorrência sem nos preocuparmos com detalhes de

threads, pool de threads, etc. Neste contexto, o Akka [3] é um framework que nos oferece esta desejada

abstração.

Escrito em Scala e com API para Scala e Java, o Akka foi desenvolvido sobre a API de concorrência do Java

e que possibilita ao desenvolvedor utilizar um modelo de concorrência baseado em atores. Com o Akka,

todas as buzzwords de concorrência, como threads, pools, locks, etc., deixam de fazer sentido.

Assim, nos concentramos apenas na estruturação da nossa aplicação em atores e na lógica de negócio de

cada ator. Porém, antes de começarmos a falar de atores, vamos entender em que modelo de concorrência os

atores se encaixam, para compreendê-los melhor.

Modelos de concorrência

De maneira generalista, podemos dividir os modelos de concorrência em dois paradigmas: concorrência

usando estado compartilhado e concorrência usando troca de mensagens. O modelo de estado compartilhado

tem sido largamente adotado no campo, mas em aplicações maiores ou com maior concorrência, o modelo

de troca de mensagens é preferido, pois sua característica assíncrona facilita a distribuição do

processamento, reduzindo gargalos.

Estado compartilhado x troca de mensagens

O modelo de estado compartilhado é o modelo que adotamos normalmente em nossas aplicações. Neste

modelo o código a ser paralelizado é executado simultaneamente através da criação de processos ou threads.

Page 2: Akka   programação concorrente

2

A complexidade aumenta quando estas threads precisam acessar a mesma informação (estado

compartilhado).

Para resolver este problema usamos blocos synchronized, que acabam gerando um gargalo na aplicação,

pois as instruções protegidas por synchronized são executadas apenas por uma thread por vez. Com o

crescimento da aplicação, consequentemente a quantidade de blocos synchronized cresce, eventualmente

causando lentidão e casualmente um dead-lock.

Derek Wyatt, engenheiro de software atuante na comunidade Akka/Scala, no seu livro “Akka Concurrency”

[4], nos brinda com uma definição perfeita sobre este modelo: “Em concorrência com estado compartilhado

tendemos a criar os problemas primeiro, então resolvê-los usando primitivas de sincronização.”

. A Figura 1 ilustra o modelo de estado compartilhado e seus gargalos.

Figura 1. Representação gráfica do modelo de concorrência baseado em estado compartilhado.

No modelo de troca de mensagens (ver Figura 2) não temos o problema de locks encontrado no modelo de

estado compartilhado, pois os componentes não compartilham estado e se comunicam através de mensagens

predominantemente assíncronas. O envio das mensagens normalmente é feito por algum outro software que

atua como intermediário entre os componentes.

Este “desconhecimento” entre os componentes garante um bom nível de desacoplamento e favorece a

distribuição, pois permite a troca de mensagens entre componentes que estejam em servidores diferentes ou

até em outras redes. O modelo de atores é uma derivação do modelo de troca de mensagens, e falaremos

sobre ele agora.

Page 3: Akka   programação concorrente

3

Figura 2. Representação gráfica do modelo de concorrência baseado em troca de mensagens.

Modelo de Atores

Em 1973, Carl Hewitt, Peter Bishop e Richard Steiger, introduziram pela primeira vez o modelo de atores,

no paper “A Universal Modular Actor Formalism for Artificial Intelligence”. Nos anos seguintes, diversas

intervenções foram feitas neste modelo e diversos papers foram publicados, o que culminou na definição da

teoria do modelo de atores.

Fundamentalmente, o modelo de atores prega que “tudo são atores”. Os atores são definidos como entidades

capazes de realizar um processamento computacional e que se comunicam entre si através do envio e

recebimento de mensagens.

Além disso, podem criar outros atores, estabelecendo assim uma hierarquia entre eles. Como a comunicação

entre atores deve ser feita estritamente pela troca de mensagens, o estado interno de um ator não é acessível

por outros, mas somente por ele mesmo.

Aliando isso ao fato do processamento das mensagens ser sequencial, isto é, um ator poder ter várias

mensagens pendentes, mas apenas uma ser processada por vez, os problemas com locks simplesmente

deixam de existir. A Figura 3 mostra uma visão simplificada do modelo de atores.

Page 4: Akka   programação concorrente

4

Figura 3. Representação gráfica do modelo de concorrência baseado em atores

Com estas características, este tipo de solução vem sendo adotado para aplicações que processam grandes

volumes de dados, pois para atender esse tipo de demanda usando o modelo convencional de threads e o

processamento síncrono, ficamos limitados pela capacidade individual dos servidores.

Por sua vez, o modelo de atores permite uma distribuição mais fácil dos componentes da aplicação devido a

sua natureza assíncrona e baseada em mensagens. O Erlang [5], linguagem originada na Ericsson com o

propósito de suportar aplicações distribuídas altamente concorrentes, possui em seu core uma

implementação deste modelo.

Contudo, apesar do Erlang ser uma linguagem de uso geral, não conquistou grande adoção. Em razão disso,

faltava ao mercado uma solução para alta concorrência baseada em atores. Assim surgiu o Akka.

Akka

O Akka foi desenvolvido em 2009 por Jonas Bonér, inspirado pelo modelo de atores do Erlang. Seu

primeiro release público, o Akka 0.5, foi anunciado em Janeiro de 2010. Atualmente esta solução é mantida

pela Typesafe Inc., a mesma empresa que mantém o Scala e o Play Framework. Recentemente, o Akka

passou a fazer parte da API padrão do Scala, substituindo o modelo de atores original desta linguagem, e

apesar de ser escrito em Scala, possui uma API oficial também para Java.

De acordo com a definição da equipe, o Akka é um “toolkit e um runtime para construir aplicações

altamente concorrentes, distribuídas e tolerantes a falhas na JVM”. Assim, aplicações criadas usando o Akka

já nascem prontas para operar de maneira distribuída, pois esta característica está na essência do framework.

Page 5: Akka   programação concorrente

5

Criado com o foco em aplicações distribuídas e otimizado para operar de maneira standalone, o Akka provê:

· Atores: conforme explicado anteriormente, a base do Akka é sua implementação do modelo de atores;

· Futures: apesar de podermos utilizar atores para resolver praticamente todos os problemas, às vezes pode

ser mais conveniente utilizar Futures. Os futures do Akka são muito parecidos com a implementação padrão

do java.util.concurrent, porém possui integração com atores;

· Scheduler: integrado ao contexto de atores, o Akka permite o agendamento de tarefas usando vários tipos

de schedulers.

Devido a estas características e recursos, o Akka vem sendo adotado por várias empresas de porte. Dentre

elas, podemos citar a Amazon, Autodesk, Blizzard, VMware, dentre outros.

Hello World – Primeiros passos

Uma forma de desenvolver aplicações com o Akka é fazer com que o framework seja uma dependência do

projeto e usar a própria aplicação para iniciar o sistema de atores. Além dessa, existe outra opção para

desenvolver tais aplicações: usando o Typesafe Activator, que é um software que depois de instalado pode

ser utilizado para criar e executar aplicações diversas baseadas em templates.

O Activator na verdade oferece muito mais, como por exemplo, uma IDE totalmente web para manutenção

dos projetos gerados por ele. Tanto o Activator quanto as dependências do Akka podem ser obtidos na

página de downloads do site do próprio Akka [6]. Neste artigo, adotaremos a primeira opção, pois esta

permite uma compreensão melhor do funcionamento do framework.

Para criar uma aplicação usando o Akka podemos usar uma ferramenta de build como o Maven ou o SBT.

Aqui optaremos pelo Maven, por ele ser mais empregado pela comunidade. Antes de criar a aplicação, você

deve ter o Eclipse com o plugin do Maven (m2e) instalado, ou apenas o Maven, caso você prefira realizar o

build pela linha de comando.

Dito isso, crie um diretório para hospedar o código da aplicação, e depois crie o pom.xml com a dependência

do Akka, conforme indica a Listagem 1.

Listagem 1. pom.xml base de uma aplicação com Akka.

<project>

<modelVersion>4.0.0</modelVersion>

<groupId>com.devmedia.akka</groupId>

<artifactId>hello</artifactId>

<version>0.0.1-SNAPSHOT</version>

<build>

<pluginManagement>

<plugins>

<plugin>

<groupId>org.apache.maven.plugins</groupId>

<artifactId>maven-compiler-plugin</artifactId>

<configuration>

<source>1.7</source>

<target>1.7</target>

</configuration>

</plugin>

</plugins>

</pluginManagement>

</build>

Page 6: Akka   programação concorrente

6

<dependencies>

<dependency>

<groupId>com.typesafe.akka</groupId>

<artifactId>akka-actor_2.10</artifactId>

<version>2.2.3</version>

</dependency>

</dependencies>

</project>

Em seguida, crie uma classe no diretório src/main/java chamada Start. Esta classe será responsável por

iniciar a aplicação e o sistema de atores do Akka. Inicialmente ela deve estar vazia, conforme a Listagem 2.

Listagem 2. Classe que irá iniciar o sistema de atores do Akka.

public class Start {

public static void main(String[] args) {

// aqui inicializaremos o Akka

}

}

Agora, antes de pormos as mãos na massa, vamos analisar o conceito de sistema de atores do Akka.

Actor System – O container de atores

Como principal característica, o Akka implementa um modelo de atores. De modo simples, este modelo diz

que tudo são atores. Sendo assim, a lógica da aplicação que normalmente é programada em classes que

costumamos chamar de “o modelo da aplicação” ou os “componentes de negócio”, deve ser estruturada em

um ou vários atores que se comunicam de maneira assíncrona através da troca de mensagens, todos sob a

gestão do actor system.

Veremos adiante que para criar um ator basta estender uma determinada classe do Akka e usar a sua API

para solicitar a instanciação. Contudo, para dar vida aos atores, precisamos antes inicializar o container

Akka, ou seja, iniciar o actor system.

O actor system é o ponto de partida de toda aplicação, por isso, deve ser criado assim que ela iniciar. Em

relação ao número de actor systems, uma aplicação pode iniciar vários deles, porém este tipo de design não é

indicado por questões de organização.

Para criar um actor system devemos usar a classe akka.actor.ActorSystem, como demonstra a Listagem 3.

Listagem 3. Criação do actor system.

import akka.actor.ActorSystem;

public class Start {

public static void main(String[] args) {

// Criação de um Actor System, container Akka.

ActorSystem system = ActorSystem.create("HelloSystem");

}

}

Podemos observar que o construtor do actor system recebe um texto como parâmetro para definir o seu

nome. Este nome deve ser único por aplicação e também deve ser único por container, caso a aplicação seja

instalada em um microcontainer Akka. Como veremos adiante, o nome do actor system também irá compor

o path de localização do ator.

Page 7: Akka   programação concorrente

7

Atores em todos os lugares

De acordo com a explicação anterior, no modelo de atores um ator é um objeto capaz de receber e enviar

mensagens, além de criar outros atores. No processamento das mensagens recebidas está a lógica de negócio

do ator. No exemplo que estamos desenvolvendo, iremos criar um ator que recebe uma mensagem e exibe

esta mensagem no console.

No Akka, para criarmos a classe de um ator, devemos estender a classe abstrata akka.actor.UntypedActor

e implementar o método void onReceive(Object msg). Este método é responsável pelo processamento das

mensagens. Deste modo, crie a classe EcoActor em src/main/java contendo o código da Listagem 4.

Listagem 4. Um ator que exibe no console a mensagem recebida.

import akka.actor.UntypedActor;

import akka.event.LoggingAdapter;

import akka.event.Logging;

public class EcoActor extends UntypedActor {

LoggingAdapter log = Logging.getLogger(getContext().system(), this);

@Override

public void onReceive(Object msg) throws Exception {

log.info("Mensagem recebida: " + msg);

}

}

Deixando os detalhes de criação do logger para depois, podemos reparar que a classe de um ator é simples.

É importante notar que a mensagem pode ser qualquer objeto Java, porém é razoável que este implemente

Serializable (apesar do Akka não obrigar) para não termos problemas em um ambiente distribuído, onde

mensagens são serializadas para trafegar pela rede.

Voltando para a classe Start, agora que temos um ator implementado, podemos usar o actor system para

efetivamente dar vida a este ator. Assim, usamos o método actorOf() de actor system para criar o ator. Este

método recebe como parâmetro uma instância de akka.actor.Props e uma String com o nome que o ator

terá no actor system. Props é uma classe que define um objeto de configuração, usado apenas para criar

atores.

Para a construção do objeto Props, usamos o método estático Props.create(), passando como parâmetro a

classe do ator. Existem muitas maneiras de construir Props, mas para o nosso exemplo, usaremos a forma

mais simples. A Listagem 5 mostra a classe Start alterada, com a criação do ator.

Listagem 5. Criando o ator EcoActor.

import akka.actor.ActorSystem;

import akka.actor.ActorRef;

import akka.actor.Props;

public class Start {

public static void main(String[] args) {

// Criação de um Actor System, que é o container Akka.

ActorSystem system = ActorSystem.create("HelloSystem");

// Criando o ator EcoActor

ActorRef actor = system.actorOf(Props.create(EcoActor.class), "eco");

}

}

Page 8: Akka   programação concorrente

8

É importante perceber que o que obtemos ao criar um ator é um ActorRef e não um objeto com a classe do

ator propriamente dita (EcoActor). Nesta informação reside um grande conceito do Akka e do modelo de

atores: os detalhes do ator não são expostos para quem envia a mensagem, pois o estado interno deste deve

ser visível apenas para ele mesmo. Isso fica bem claro na implementação do ator.

Basta percebermos que o código processado por ele (sua lógica de negócio) se restringe ao método

onReceive(), que é chamado pelo Akka para processar as mensagens. Como as mensagens são processadas

uma por vez (ou seja, apenas uma thread por vez), não existe nenhuma necessidade de blocos synchronized.

Com isso, locks e deadlocks não existem neste modelo.

ActorRef é a interface para comunicação com o ator criado e o método ActorRef.tell() é utilizado para

enviar uma mensagem para este ator. Este método recebe como parâmetro a mensagem e o ator que a está

enviando. Como estamos enviando a mensagem do método main() da casse Start (isto é, não é um ator que

está enviando a mensagem), usaremos ActorRef.noSender(), que é um método auxiliar que retorna uma

constante para identificar ao actor system que não existe nenhum ator origem. A Listagem 6 mostra o envio

dessa mensagem ao ator.

Listagem 6. Enviando uma mensagem para EcoActor.

import akka.actor.ActorSystem;

import akka.actor.ActorRef;

import akka.actor.Props;

public class Start {

public static void main(String[] args) {

// Criação de um Actor System, que é o container Akka.

ActorSystem system = ActorSystem.create("HelloSystem");

// Criando o ator EcoActor

ActorRef ecoActor = system.actorOf(Props.create(EcoActor.class), "eco");

// Enviando a mensagem ao ator

ecoActor.tell("Alô Mundo com Atores", ActorRef.noSender());

}

}

Com essas duas classes já é possível ver o Akka em ação, mas antes, precisamos compilar a aplicação. Para

isso, usaremos o Maven através do comando mvn clean install. Em seguida, executaremos a aplicação

também pelo Maven, com o plugin exec:java, como indica a Listagem 7.

Listagem 7. Executando a aplicação.

$ mvn clean install exec:java -Dexec.mainClass=Start

.

. (detalhes do build omitidos)

.

[INFO] [01/01/2014 19:00:00.384] [HelloSystem-akka.actor.default-dispatcher-3]

[akka://HelloSystem/user/eco] Mensagem recebida: Alô Mundo com Atores

Note que a mensagem enviada (“Alô Mundo com Atores”) foi processada pelo ator e impressa no console

conforme esperado. O log do actor system ainda informa o timestamp, o nome da thread e o nome do ator

que está logando.

Falaremos destes detalhes adiante. Para interromper a execução, digite Ctrl+c, caso contrário o actor system

continuará executando.

Page 9: Akka   programação concorrente

9

Antes de partir para a modelagem de uma aplicação mais próxima do real, vamos apresentar algumas

funcionalidades do Akka usando o tradicional “Hello, World!”.

Atores criando atores

Conforme descrito anteriormente, um ator pode iniciar (ou criar) outros atores, estabelecendo uma

hierarquia entre eles. Esta hierarquia é definida de acordo com a modelagem da aplicação. Contudo, nada

impede de modelarmos uma aplicação usando o Akka e criar todos os atores na raiz do actor system, como

fizemos com EcoActor.

Apesar dessa possibilidade, por questões de organização e de gestão de exceções, é importante definir uma

estrutura coesa de atores e normalmente isso implica em vários níveis.

Para criar um ator a partir de outro, é indicado fazê-lo dentro do método preStart() de UntypedActor,

apesar de nada impedir de fazermos isso sob demanda, ou seja, na primeira vez que o ator for utilizar o ator

filho.

O actor system executa o prestar() (dentre outros que ainda analisaremos do ciclo de vida dos atores) antes

de completar a inicialização de um ator. Sendo assim, este é um excelente lugar para inicializar eventuais

estados internos do ator, como por exemplo, dependência de outros atores ou atributos privados utilizados

por ele.

Tendo em vista estas informações, vamos alterar o código da aplicação exemplo para que o EcoActor crie

um ator na sua inicialização e repasse toda mensagem recebida por ele para este ator “filho”.

Para isso, declare uma classe chamada ChildActor com o mesmo código de EcoActor (ver Listagem 4).

Agora, devemos alterar EcoActor, sobrescrever o método preStart() e instanciar o ator “filho”, do tipo

ChildActor. Assim como a criação de um ator via actor system, para instanciar um ator dentro de outro

usamos o método actorOf() do contexto do ator.

Este contexto é obtido através do método getContext(). A Listagem 8 mostra a implementação do método

preStart() e a criação do ator filho, bem como o repasse das mensagens recebidas em onReceive().

Listagem 8. Criando um ator filho via getContext().

public class EcoActor extends UntypedActor {

LoggingAdapter log = Logging.getLogger(getContext().system(), this);

//Declaramos o ator filho como atributo de EcoActor

private ActorRef childActor;

@Override

public void preStart() throws Exception {

super.preStart();

//Na inicialização do ator, instanciamos o ator filho

childActor = getContext().actorOf(Props.create(ChildActor.class), "childOfEco");

}

@Override

public void onReceive(Object msg) throws Exception {

log.info("Mensagem recebida: " + msg);

//Repassamos a mensagem recebida para o ator filho

childActor.tell(msg, getSelf());

}

Page 10: Akka   programação concorrente

10

}

Para ver os resultados destas alterações, devemos repetir os comandos de compilação e execução via Maven,

como mostra a Listagem 9.

Listagem 9. Executando a aplicação.

$ mvn clean install exec:java -Dexec.mainClass=Start

.

. (detalhes do build omitidos)

.

[INFO] [01/01/2014 10:29:26.722] [HelloSystem-akka.actor.default-dispatcher-2]

[akka://HelloSystem/user/eco] Mensagem recebida: Alô Mundo com Atores

[INFO] [01/01/2014 10:29:26.722] [HelloSystem-akka.actor.default-dispatcher-3]

[akka://HelloSystem/user/eco/childOfEco] Mensagem recebida: Alô Mundo com Atores

O log exibido no resultado mostra claramente a hierarquia dos atores, exposta pelo path do ator. Repare que

a mensagem foi recebida por EcoActor, que tem o path akka://HelloSystem/user/eco, e repassada ao ator

filho, que tem o path akka://HelloSystem/user/eco/childOfEco.

O path de um ator é uma URI única que o identifica. Ela é composta pelo protocolo (akka://), o identificador

do actor system (HelloSystem), pela raiz dos atores criados pela aplicação (user), o identificador do ator pai

(caso exista) e o identificador do próprio ator. Sendo assim, na estrutura de atores criada pela aplicação

exemplo, temos dois atores identificáveis por dois paths:

akka://HelloSystem/user/eco

akka://HelloSystem/user/eco/childOfEco

O identificador do actor system (HelloSystem) é definido no start da aplicação, e o identificador de cada ator

é definido no momento de sua criação. Apenas o prefixo user é algo imposto pelo Akka, pois ele define a

raiz da hierarquia dos atores. Na prática, todos os atores criados pelas aplicações desenvolvidas com Akka

ficarão sob o guardião chamado user. Existem guardiões disponíveis para diversas finalidades, a saber:

/user: É o guardião de todos os atores criados pelo código da aplicação desenvolvida usando Akka;

· /system: Este é o guardião de atores criados pelo próprio Akka, para manter o funcionamento do

framework;

· /deadLetters: É um ator que recebe as mensagens enviadas para atores inexistentes, ou atores que não

estão em funcionamento;

· /temp: É o guardião de atores criados para necessidades temporárias, isto é, são atores de vida curta;

· /remote: Este é um guardião para paths virtuais, que representam atores remotos, ou seja, que estejam

instanciados em outras JVMs.

Mensagens tipadas

É muito comum no desenvolvimento de aplicações Akka um determinado ator ser programado para receber

vários tipos de mensagens e realizar processamentos distintos de acordo com a mensagem. Neste caso o

padrão adotado é utilizar classes diferentes para representar os diferentes tipos de mensagens.

A vantagem de usar este padrão é que ele deixa claro e visível os tipos de mensagens que um ator processa e

principalmente os que ele não processa. Para exemplificar, vamos refatorar nosso código para enviar uma

Page 11: Akka   programação concorrente

11

mensagem tipada para nossos atores e refutar qualquer outra mensagem. Para isto, criaremos uma classe

chamada HelloMessage, conforme a Listagem 10.

Listagem 10. Classe que representará a mensagem.

public class HelloMessage {

}

Como pode ser observado, esta classe não contém nenhum estado interno, pois o seu propósito é apenas

identificar um tipo de mensagem. Dito isso, depois de criá-la, devemos alterar os nossos atores, para deixar

claro que eles recebem e processam apenas esse tipo de mensagem. Sendo assim, o método receive() de

EcoActor deve ficar como exposto na Listagem 11.

Listagem 11. Alteração do método onReceive() de EcoActor.

@Override

public void onReceive(Object msg) throws Exception {

if (msg instanceof HelloMessage) {

log.info("Mensagem recebida: " + msg);

// repassamos a mensagem recebida para o ator filho

childActor.tell(msg, getSelf());

}

else {

// informa ao actor system que este ator não processa esta mensagem

unhandled(msg);

}

}

Repare que usamos instanceof para filtrar as mensagens processadas de acordo com o tipo destas. Neste

código é importante ressaltar a chamada ao método unhandled(), que deixa claro ao actor system que esta

mensagem não foi processada pelo ator. Deste modo, ela será considerada pelo actor system para eventuais

tratamentos futuros.

Se o método unhandled() não for chamado, o actor system não tem como saber que a mensagem não foi

processada. Para dar continuidade ao exemplo, devemos alterar também ChildActor, para receber apenas

mensagens do tipo HelloMessage. Veja a Listagem 12.

Listagem 12. Alteração do método onReceive() de ChildActor.

@Override

public void onReceive(Object msg) throws Exception {

if (msg instanceof HelloMessage) {

log.info("Mensagem recebida: " + msg);

}

else {

unhandled(msg);

}

}

Ao executarmos a aplicação (Listagem 9) após essas alterações, será possível notar que nenhum log será

exibido. Isso é facilmente explicado pelo fato de nossos atores agora apenas processarem mensagens do tipo

HelloMessage e a mensagem enviada pela classe Start ser do tipo String. Para corrigir isso, devemos

alterar a classe Start de acordo com a Listagem 13.

Listagem 13. Alterando o envio da mensagem em Start.

Page 12: Akka   programação concorrente

12

// Enviando a mensagem ao ator

ecoActor.tell(new HelloMessage(), ActorRef.noSender());

Com essa simples mudança, ao reexecutar a aplicação teremos o resultado apresentado na Listagem 14.

Listagem 14. Resultado da reexecução após as alterações.

$ mvn clean install exec:java -Dexec.mainClass=Start

.

. (detalhes do build omitidos)

.

[INFO] [02/02/2014 11:22:00.668] [HelloSystem-akka.actor.default-dispatcher-3]

[akka://HelloSystem/user/eco] Mensagem recebida: HelloMessage@7ee14288

[INFO] [02/02/2014 11:22:00.668] [HelloSystem-akka.actor.default-dispatcher-4]

[akka://HelloSystem/user/eco/childOfEco] Mensagem recebida: HelloMessage@7ee14288

Supervisores e estratégia de recuperação

Todos sabem o que acontece quando uma exceção é lançada em um sistema convencional. A exceção é

lançada a camadas superiores de serviços até que uma dessas camadas tenha a capacidade de lidar com ela.

No modelo de atores o que acontece é um pouco diferente, evidentemente por conta do desacoplamento

entre os componentes e pela característica assíncrona das chamadas.

Quando uma exceção é lançada no processamento de uma mensagem, o supervisor do ator que lançou a

exceção deve decidir o que vai acontecer com o ator. O supervisor de um ator é o ator que o criou, ou seja, o

seu pai.

Caso o ator tenha sido criado diretamente pelo actor system (como os atores criados na classe Start nos

exemplos), o ator não tem um pai conhecido ou explícito. Neste caso, o supervisor passa a ser o ator

guardião, ou seja, /user.

Antes de vermos o que pode acontecer com um ator que lança uma exceção, vamos entender um pouco mais

a respeito do ciclo de vida dos atores.

Ciclo de vida de um ator

É importante ressaltar que no Akka a(s) vida(s) dos atores é(são) representada(s) por encarnações (ver

Figura 4). Isso significa que um ator pode “nascer”, “morrer” e “reencarnar” (nascer novamente) e assim

sucessivamente. O termo encarnação é de fato utilizado na documentação do Akka e traz à luz alguns

conceitos interessantes da vida do ator, a saber:

1. Inicialmente um ator simplesmente não existe até o ponto em que alguma chamada por actorOf() é

executada, seja no actor system (raiz) ou pelo contexto de outro ator;

2. Após a chamada de actorOf() o ator ainda não está encarnado, ou seja, ainda não responde por

mensagens, porém tem: o path reservado; um identificador único (UID); a instância do objeto criada; o

método preStart() executado.

3. Após a execução do preStart() o ator está encarnado e recebendo/processando as mensagens destinadas a

ele;

4. Neste ponto algo errado pode acontecer e o ator pode lançar uma exceção. Assim, o ator passa para um

estado temporário que chamaremos de doente. Neste momento o supervisor do ator deve decidir o que fazer

com o ator doente, tendo três opções:

Page 13: Akka   programação concorrente

13

a. Continuar. Simplesmente ignorar que a exceção aconteceu e continuar com a vida do ator, processando

as mensagens normalmente;

b. Reiniciar o ator. O método preRestart() é executado na instância em execução, uma nova instância é

criada e o método postRestart() é executado na nova instância. O path do ator se mantém, assim como o

UID, caracterizando uma mesma encarnação;

c. Encerrar o ator (morte). O ator pode ser terminado pelo seu supervisor ou pelo envio de uma mensagem

que o finalize (existe uma mensagem no Akka do tipo akka.actor.PoisonPill, que ao ser consumida por um

ator causa sua interrupção). Neste ponto, o método postStop() é executado. Caso o ator seja iniciado

novamente, uma nova encarnação será criada, tendo o mesmo path, porém com um novo UID.

Figura 4. Ciclo de vida dos atores no container do Akka.

A definição da estratégia de recuperação é feita no supervisor, ou seja, no ator pai, e vale para todos os seus

filhos. Caso o supervisor de um ator não defina a estratégia de recuperação dos seus filhos, se o ator em

questão lançar uma exceção, o Akka tenta usar a estratégia definida pelo pai do pai e assim sucessivamente

até a raiz, isto é, o ator guardião /user.

Para definir uma estratégia de recuperação em um ator, devemos sobrescrever o método

supervisorStrategy() de UntypedActor. Este método deve retornar uma instância de

akka.actor.SupervisorStrategy, que pode ser de dois tipos:

· OneForOneStrategy: Se o retorno do método supervisorStrategy() for uma instância de

OneForOneStrategy, significa que a ação tomada irá afetar apenas o ator que lançou a exceção, ou seja, o

ator doente;

Page 14: Akka   programação concorrente

14

· AllForOneStrategy: Se o retorno do método supervisorStrategy() for uma instância de

AllForOneStrategy, a ação tomada será aplicada a todos os atores filhos do supervisor que está definindo a

estratégia.

O trecho de código da Listagem 15 mostra a criação de uma estratégia de recuperação implementada em um

ator.

Listagem 15. Implementação de uma estratégia de recuperação.

@Override

public SupervisorStrategy supervisorStrategy() {

return new OneForOneStrategy(-1, Duration.Inf(), new Function<Throwable,

Directive>() {

public Directive apply(Throwable t) throws Exception {

return OneForOneStrategy.resume();

}

});

}

Para implementar uma estratégia de recuperação em um supervisor (um ator com filhos), devemos

sobrescrever o método supervisorStrategy(), que por sua vez, deve retornar uma instância de

SupervisorStrategy. No caso do código exemplo, foi escolhida a opção akka.actor.OneForOneStrategy.

Isso significa que a estratégia de recuperação será aplicada apenas no ator que lançou a exceção.

Para construir um objeto do tipo OneForOneStrategy devemos informar três parâmetros no construtor:

maxNrOfRetries (número máximo de tentativas de reinício do ator), withinTimeRange (intervalo entre a

primeira e a última tentativa de reinício) e decider.

Deixando de lado os dois primeiros parâmetros, o decider, ou decisor, deve ser uma implementação da

interface akka.japi.Function e possuir apenas o método chamado apply(), que recebe como parâmetro um

Throwable e retorna um akka.actor.SupervisorStrategy.Directive (explicado adiante).

Quando a aplicação estiver em execução e um determinado ator lançar uma exceção no processamento de

uma mensagem, o Akka irá executar o método apply() do objeto decider definido na estratégia do ator pai

passando como parâmetro a exceção lançada e obtendo como retorno uma diretiva a ser utilizada para tomar

a devida ação.

As diretivas podem ser do tipo:

· resume: Ignora a exceção atirada pelo ator supervisionado (filho). A mensagem simplesmente deixa de ser

processada e o ator continua processando normalmente outras mensagens;

restart: Reinicia o ator, conforme explicado anteriormente, na descrição do ciclo de vida do ator;

escalate: Repassa a decisão do que deve ser feito para o supervisor diretamente acima;

stop: Finaliza (mata) o ator.

Voltando a falar dos dois primeiros parâmetros da construção de OneForOneStrategy, são aplicáveis

somente à ação de restart. O primeiro parâmetro diz quantas vezes o ator pode ser reiniciado e o segundo

parâmetro diz em qual intervalo esta quantidade de reinícios pode acontecer.

Este tipo de estratégia faz sentido em situações de erros intermitentes, por exemplo, uma dependência de

uma conexão com o banco de dados. Caso este número seja ultrapassado no intervalo definido, o ator é

finalizado (morto) e as mensagens enviadas para ele são perdidas.

Modelando atores

Page 15: Akka   programação concorrente

15

Assim como qualquer paradigma de desenvolvimento, o modelo de atores tem as suas peculiaridades. Ao

desenvolver uma aplicação usando este modelo, devemos estruturar a aplicação em atores, considerando que

a comunicação entre eles é assíncrona e realizada através troca de mensagens. Depois de uma vida inteira

desenvolvendo aplicações com o modelo tradicional de concorrência, inicialmente este paradigma poderá

parecer um pouco confuso.

Para facilitar o aprendizado a respeito, existe uma grande quantidade de material na internet relacionada à

modelagem de atores, mas para ficar apenas no universo do Akka, uma boa referência é o site Let It Crash

[7], mantido pela comunidade. Este site possui trechos de código, artigos, discussões e outros conteúdos

relacionados ao desenvolvimento com Akka.

Próximos passos

Este artigo procurou introduzir os princípios básicos do paradigma de concorrência baseado em atores e o

framework Akka. Por esse motivo, ainda existe muito a ser explorado.

Pensando em facilitar os próximos passos, e direcionando nosso foco para o Akka, a seguir listamos os

tópicos mais importantes que o leitor pode se aprofundar:

· Mailbox: Apesar do Akka abstrair a existência deles, cada instância de ator possui uma caixa de

mensagens, ou mailbox. Existem vários tipos de customizações que podem ser feitas com este recurso, como

por exemplo, podemos criar uma caixa de mensagens para vários atores, balanceando o consumo, realizando

uma espécie de load balancing de processamento de mensagens;

· Dispatchers: São implementações de ExecutionContext que regem a criação das threads de

processamento de mensagens, schedulers e todo o funcionamento do framework. Também possui um alto

grau de customização;

· Persistência: É possível configurar o Akka para persistir o estado interno de um ator, inclusive o seu

mailbox. Sendo assim, caso a aplicação caia ou seja desligada indevidamente, no restart do container este

estado é recuperado. Este módulo ainda é experimental no Akka e foi introduzido na versão 2.3.0 do

framework;

· Extensões: O Akka oferece um mecanismo padrão para criar extensões. Com ele podemos atuar sobre

elementos centrais do framework, como o Actor system, para modificar o seu comportamento.

O modelo de concorrência baseado em threads e estado compartilhado tem atendido razoavelmente as

necessidades do mercado, porém, quando nos deparamos com o desafio de construir uma aplicação que

suporte alta concorrência e alta disponibilidade, é importante considerarmos um modelo baseado em

mensagens assíncronas.

No ecossistema do Java, o Akka é a ferramenta ideal para esta finalidade e tem se tornado mais completo a

cada release. Além de suas qualidades técnicas, é seguro apostar no Akka não apenas por tratar-se de uma

tecnologia suportada e mantida por uma empresa do porte da Typesafe, mas também por ser um excelente

framework, leve, bem estruturado, modular e muito fácil de escalar.

Links

[1] Página da API de concorrência do Java.

http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/package-

summary.html

Page 16: Akka   programação concorrente

16

[2] Documentação do Fork/Join framework.

http://docs.oracle.com/javase/tutorial/essential/concurrency/forkjoin.html

[3] Página principal do Akka.

http://akka.io

[4] Página da Amazon do livro Akka Concurrency, de Derek Wyatt.

http://www.amazon.com/Akka-Concurrency-Derek-Wyatt/dp/0981531660

[5] Página da linguagem de programação Erlang.

http://www.erlang.se

[6] Página de downloads do Akka.

http://akka.io/downloads/

[7] Página de um grupo de desenvolvedores da comunidade Akka.

http://letitcrash.com