Conteúdo


Simultaneidade JVM

Agir de forma assíncrona com Akka

Construa sistemas de agentes para aplicativos simultâneos

Comments

Conteúdos da série:

Esse conteúdo é a parte # de # na série: Simultaneidade JVM

Fique ligado em conteúdos adicionais dessa série.

Esse conteúdo é parte da série:Simultaneidade JVM

Fique ligado em conteúdos adicionais dessa série.

Artigos anteriores nesta série abordaram a implementação da simultaneidade ao:

  • Fazer a mesma operação em vários conjuntos de dados em paralelo (como em fluxos Java 8)
  • Explicitamente estruturar os cálculos para fazerem certas operações de forma assíncrona e depois combinar os resultados (como futuros)

Ambas são excelentes formas de obter simultaneidade, mas você precisa fazer o design delas explicitamente em seu aplicativo.

Agora e nas próximas partes do artigo, focarei em uma abordagem diferente de simultaneidade, uma baseada em uma estrutura específica de programa ao invés de codificação explícita. A estrutura desse programa é o modelo de agente. Você verá como funciona com a implementação Akka do modelo de agente específico. (Akka é um kit de ferramentas e tempo de execução para construir aplicativos JVM simultâneos e distribuídos.) Veja a execução de Recursos para um link ao código amostra integral para este artigo.

Conceitos básicos do modelo de agente

O modelo de agente para cálculos simultâneos constrói sistemas baseados em primitivas chamadas de agentes. Os agentes agem em resposta a entradas chamadas de mensagens. As ações podem incluir a alteração o próprio estado interno de um agente, além de enviar outras mensagens e até mesmo criar outros agentes. Todas as mensagens são entregues de forma assíncrona, desacoplando os emissores de mensagens de seus destinatários. Devido a esse desacoplamento, os sistemas dos agentes são inerentemente simultâneos: qualquer agente que tiver mensagens de entrada disponíveis pode ser executado em paralelo, sem restrição.

Em termos Akka, os agentes parecem ter comportamento exagerado e interagem através de mensagens. Como agentes reais, agentes Akka querem um grau de privacidade. Não é possível enviar mensagens diretamente para um agente Akka. Ao invés disso, mensagens são enviadas a um agente de referência que é o equivalente a uma caixa de e-mail. As mensagens recebidas são roteadas através da referência para uma caixa de correio para o agente, e as mensagens são entregues para o agente mais tarde. Agentes Akka exigem até que todas as mensagens recebidas sejam estéreis (ou em termos JVM, imutáveis) para evitar contaminação de outros agentes.

Diferente de algumas demandas de agentes reais, essas restrições aparentemente obsessivas na Akka existem por um motivo. Usar referências para agentes evita qualquer interação fora da troca de mensagem que poderia quebrar o desacoplamento no centro do modelo de agente. Os agentes são de encadeamento único em execução (não mais de um encadeamento executa uma instância específica do agente), assim, a caixa de correio age como um buffer para reter as mensagens até que possam ser processadas. E a imutabilidade das mensagens (não atualmente impingidas pela Akka devido às limitações JVM, mas um requisito declarado) significa que você nunca precisará se preocupar com problemas de sincronização afetando os dados compartilhados entre agentes; se o único dado compartilhado for imutável, a sincronização nunca será necessária.

Prazer em conhecê-lo

Agora que você já teve uma visão geral do modelo de agente e das especificidades da Akka, é hora de visualizar alguns códigos. Usar Hello como um exemplo de codificação é clichê, mas dá uma visão geral rápida e fácil de uma linguagem ou sistema. A listagem 1 mostra uma versão Akka no Scala.

Lista 1. Scala Simples hello
import akka.actor._ import akka.util._
/** Simple hello from an actor in Scala. */ object Hello1 extends App
{

  val system = ActorSystem("actor-demo-scala")
  val hello = system.actorOf(Props[Hello])
  hello ! "Bob"
  Thread sleep 1000
  system shutdown

  class Hello extends Actor {
    def receive = {
      case name: String => println(s"Hello $name")
    }
  }
}

O código da Listagem 1 está em duas partes separadas, todas contidas no objeto de aplicativo Hello1 . O primeiro chunk de código é a infraestrutura do aplicativo Akka, que:

  1. Cria um sistema de agentes (a linha ActorSystem(...) ).
  2. Cria um agente no sistema (a linha system.actorOf(...) , que retorna uma referência de agente para o agente criado).
  3. Usa a referência do agente para enviar uma mensagem ao agente (a linha hello ! "Bob" ).
  4. Espera um segundo e depois encerrar o sistema do agente (a linha de encerramento do sistema ).

A chamada system.actorOf(Props[Hello]) é a maneira recomendada de criar uma instância de agente, usando as propriedades de configuração especializadas para o Hello tipo de agente. Para este agente simples (executando uma pequena parte, com uma única linha de diálogo) não há informações de configuração, então o objeto Props não possui parâmetros. Se desejar definir uma configuração para seu agente, é possível definir a classe Props especificamente para aquele agente que inclui todas as informações necessárias. (Um outro exemplo irá mostrar como fazer isso.)

A instruçãohello ! "Bob" envia uma mensagem (neste caso, somente a cadeia de caracteres Bob) ao agente criado. O operador! é uma maneira conveniente de representar o envio de uma mensagem a um agente na Akka, com um padrão do tipo envia e segue. Caso não goste do estilo do operador especializado, você pode usar o método tell() para fazer a mesma coisa.

O segundo chunk de código é a definição do agenteHello , iniciando com o Agente de extensão de classe Hello . Esta definição específica do agente é a mais simples possível. Define a função parcial necessária (para todos os agentes) receive, que implementa o tratamento de mensagens recebidas. (receive é uma função parcial função parcial, pois é definida somente — neste caso, somente as entradas de mensagem String .) O tratamento implementado para este agente é imprimir uma saudação usando o valor de mensagem sempre que uma mensagem String for recebida.

Hello no Java

A listagem 2 mostra o Hello Akka da Listagem 1 em Java ordinário.

Lista 2. Hello em Java
import akka.actor.*;
public class Hello1 {
    public static void main(String[] args) {
        ActorSystem system = ActorSystem.create("actor-demo-java");
        ActorRef hello = system.actorOf(Props.create(Hello.class));
        hello.tell("Bob", ActorRef.noSender());
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) { /* ignore */ }
        system.shutdown();
    }

    private static class Hello extends UntypedActor {
        
        public void onReceive(Object message) throws Exception {
            if (message instanceof String) {
                System.out.println("Hello " + message);
            }
        }
    }
}

A listagem 3 mostra a definição de agente da versão Java 8 com lambdas, juntamente com a importação requerida para a classe ReceiveBuilder com suporte para lambdas. Talvez a Listagem 3 seja um pouco mais compacta, mas fora isso, é basicamente a mesma que a Listagem 2.

Lista 3. A versão Java 8 da Akka Hello
import akka.japi.pf.ReceiveBuilder;
...
    private static class Hello extends AbstractActor {
        
        public Hello() {
            receive(ReceiveBuilder.
                match(String.class, s -> { System.out.println("Hello " + s); }).
                build());
        }
    }

Comparada à Listagem 2, o código Java 8 da Listagem 3 usa uma classe base diferente —AbstractActor em vez de UntypedActor— e também usa uma maneira diferente de definir as alternativas de processamento de mensagens. A classe ReceiveBuilder permite usar expressões lambda para definir o processamento da mensagem, com uma certa sintaxe de correspondência semelhante a Scala. Se você desenvolve principalmente em Scala, essa técnica pode ajudar a fazer o código Java Akka ficar um pouco mais limpo, mas os benefícios de usar a versão específica para Java 8 parecem não ser muito grandes.

Por que esperar?

O principal código do aplicativo inclui uma espera na forma de Thread sleep 1000 após enviar uma mensagem ao agente antes de encerrar o sistema. Você pode querer saber a necessidade disso. Apesar de tudo, a mensagem é trivial ao processo; ela não passará imediatamente pelo agente e será processada até o momento em que a instrução hello ! "Bob" estiver concluída?

A resposta curta à esta pergunta é não. Agentes Akka são executados de forma assíncrona, portanto, mesmo se o agente destino estiver localizado na mesma JVM que o remetente, a execução do agente de destino nunca é imediata. Ao invés disso, o encadeamento executando a mensagem adiciona a mensagem na caixa de correio do agente de destino. Adicionar a mensagem na caixa de correio desencadeia a execução de um encadeamento para retirar a mensagem da caixa de correio e processá-la ao invocar o receive do agente. Mas o encadeamento que tira a mensagem da caixa de correio geralmente não é o mesmo que adicionou a mensagem à caixa de correio.

Tempo de entrega da mensagem e garantias

Além da breve resposta para a pergunta "Por que esperar?" é um princípio mais profundo. A Akka suporta o agente remotamente com transparência de localização, o que significa que o código não possui nenhuma maneira direta de saber se um agente específico está localizado na mesma JVM ou executando em um sistema em algum lugar na nuvem. Mas esses dois casos obviamente terão características muito diferentes na operação real.

A Akka não garante que as mensagens serão entregues. A lógica filosófica subjacente a essa não garantia de entrega está relacionada a um dos princípios centrais da Akka.

Uma diferença está relacionada à perda da mensagem. A Akka não garante que as mensagens sejam entregues, o que pode ser uma surpresa aos desenvolvedores acostumados a sistemas de mensagens que são usadas para conectar aplicativos. A lógica filosófica subjacente a esta não garantia de entrega está relacionada a um dos princípios centrais da Akka: projetado para falha. Como uma simplificação deliberada, considere que as garantias de entrega adicionam complexidade substancial aos sistemas de transferência de mensagens, mas mesmo assim, esses sistemas mais complexos não funcionam como esperado, e o código do aplicativo deve ser envolvido na recuperação. Assim, faz todo sentido que o código do aplicativo manipule o caso de falha de entrega ele mesmo todo o tempo, permitindo que o sistema de transferência de mensagens seja mantido simples.

A Akka garante que as mensagens serão entregues no máximo uma vez, e que as mensagens enviadas de uma instância de agente a outra instância de agente nunca serão recebidas fora de ordem. A segunda parte da garantia se aplica somente a pares específicos de agentes, e não é associativo. Se o agente A envia mensagens ao agente B, essas mensagens nunca estarão fora de ordem. O mesmo ocorre se o agente A envia mensagens ao agente C. Mas se o agente B também envia mensagens ao agente C (por exemplo, ao encaminhar as mensagens de A para C), as mensagens de B podem estar fora de ordem com relação às mensagens de A.

No código da Listagem 1 , a possibilidade de perda de mensagem é bastante remota, pois o código é executado em uma única JVM e não gera uma carga de mensagem pesada. (Uma carga de mensagem pesada pode levar à perda da mensagem. Se a Akka ficar sem espaço para armazenar as mensagens, por exemplo, não tem outra alternativa a não ser descartá-las.) Mas o código da Listagem 1 ainda está estruturado para não fazer nenhuma suposição sobre o tempo de entrega de mensagem e permitir a operação assíncrona do sistema do agente.

Agentes e estado

O modelo de agente da Akka é flexível e permite todos os tipos de agentes. Você pode ter agentes sem informações de estado (como no exemplo Hello1 ), mas esses agentes tendem a ser o equivalente de chamadas de método. Adicionar informações de estado permite funções de agentes muito mais flexíveis.

A Listagem 1 fornece um exemplo completo (embora trivial) de um sistema de agente — mas com um agente restrito a fazer a mesma coisa, sempre. Até mesmo agentes ficam entediados ao somente ficar repetindo a mesma linha, então a Listagem 4 torna as coisas mais interessantes ao adicionar algumas informações de estado ao agente.

Lista 4. Polyglot Scala hello
object Hello2 extends App {
  
  case class Greeting(greet: String)
  case class Greet(name: String)
  
  val system = ActorSystem("actor-demo-scala")
  val hello = system.actorOf(Props[Hello], "hello")
  hello ! Greeting("Hello")
  hello ! Greet("Bob")
  hello ! Greet("Alice")
  hello ! Greeting("Hola")
  hello ! Greet("Alice")
  hello ! Greet("Bob")
  Thread sleep 1000
  system shutdown
  
  class Hello extends Actor {
    var greeting = ""
    def receive = {
      case Greeting(greet) => greeting = greet
      case Greet(name) => println(s"$greeting $name")
    }
  }
}

O agente da Listagem 4 sabe como manipular dois tipos diferentes de mensagens, definidas próximo ao início da listagem: a mensagem de Greeting e a mensagem de Greet , cada uma delas agrupando um valor de sequência. Quando o agente modificado Hello recebe uma mensagem de Greeting , salva a sequência agrupada como a saudação . Quando recebe uma mensagem Greet , combina a saudação salva com a sequência Greet para formar a sentença completa. Eis o que você verá (embora não necessariamente nesta ordem, pois a ordem de execução do agente é indeterminada) impresso no console quando executar este aplicativo:

Hello Bob
Hello Alice
Hola Alice
Hola Bob

Não há muita novidade no código da Listagem 4, então não incluirei as versões Java aqui. Você as encontrará no download de código (consulte Recursos) como com.sosnoski.concur.article5java.Hello2 e com.sosnoski.concur.article5java8.Hello2.

Propriedades e interações

Sistemas reais de agentes fazem coisas ao usar vários agentes que interagem ao enviar mensagens uns aos outros. Esses agentes também muitas vezes precisam receber informações de configuração para se prepararem para suas funções específicas. A Listagem 5 constrói as técnicas usadas nos exemplos Hello para mostra uma versão simples de configuração e interações de agentes.

Lista 5. Propriedades e interações de agentes
object Hello3 extends App {

  import Greeter._
  val system = ActorSystem("actor-demo-scala")
  val bob = system.actorOf(props("Bob", "Howya doing"))
  val alice = system.actorOf(props("Alice", "Happy to meet you"))
  bob ! Greet(alice)
  alice ! Greet(bob)
  Thread sleep 1000
  system shutdown

  object Greeter {
    case class Greet(peer: ActorRef)
    case object AskName
    case class TellName(name: String)
    def props(name: String, greeting: String) = Props(new Greeter(name, greeting))
  }

  class Greeter(myName: String, greeting: String) extends Actor {
    import Greeter._
    def receive = {
      case Greet(peer) => peer ! AskName
      case AskName => sender ! TellName(myName)
      case TellName(name) => println(s"$greeting, $name")
    }
  }
}

A Listagem 5 apresenta um novo agente em uma função importante, o agente Greeter . O Greeter vai uma etapa além do exemplo Hello2 , com:

  • Propriedades passadas para configurar as instâncias do Greeter
  • Um objeto Scala adicional que define as propriedades de configuração e mensagens (se estiver vindo de um histórico de Java, pense no objeto adicional como uma classe estática auxiliar com o mesmo nome que a classe do agente)
  • Mensagens enviadas entre instâncias do agente Greeter

Este código produz um resultado simples:

Howya doing, Alice
Happy to meet you, Bob

Se tentar executar o código algumas vezes, provavelmente verá as linhas na ordem reversa. Essa classificação é outro exemplo da natureza dinâmica do sistema de agentes Akka, onde a ordem na qual as mensagens são processadas não é determinante (com algumas poucas exceções discutidas em "Tempo e garantias de entrega de mensagem").

Greeter em Java

A Listagem 6 mostra o código Akka Greeter da Listagem 5 em Java ordinário.

Lista 6. Greeter em Java
public class Hello3 {

    public static void main(String[] args) {
        ActorSystem system = ActorSystem.create("actor-demo-java");
        ActorRef bob = system.actorOf(Greeter.props("Bob", "Howya doing"));
        ActorRef alice = system.actorOf(Greeter.props("Alice", "Happy to meet you"));
        bob.tell(new Greet(alice), ActorRef.noSender());
        alice.tell(new Greet(bob), ActorRef.noSender());
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) { /* ignore */ }
        system.shutdown();
    }
    
    // messages
    private static class Greet {
        public final ActorRef target;
        
        public Greet(ActorRef actor) {
            target = actor;
        }
    }
    
    private static Object AskName = new Object();
    
    private static class TellName {
        public final String name;
        
        public TellName(String name) {
            this.name = name;
  }
    }

    // actor implementation
    private static class Greeter extends UntypedActor {
        private final String myName;
        private final String greeting;
        
        Greeter(String name, String greeting) {
            myName = name;
            this.greeting = greeting;
        }
        
        public static Props props(String name, String greeting) {
            return Props.create(Greeter.class, name, greeting);
          }
        
        public void onReceive(Object message) throws Exception {
            if (message instanceof Greet) {
                ((Greet)message).target.tell(AskName, self());
            } else if (message == AskName) {
                sender().tell(new TellName(myName), self());
            } else if (message instanceof TellName) {
                System.out.println(greeting + ", " + ((TellName)message).name);
            }
        }
    }
}

A Listagem 7 mostra a versão Java 8 com lambdas. Novamente, esta versão é um pouco mais compacta na implementação da manipulação da mensagem, mas de outra forma, é a mesma.

Lista 7. Versão Java 8
import akka.japi.pf.ReceiveBuilder;
...
    private static class Greeter extends AbstractActor {
        private final String myName;
        private final String greeting;
        
        Greeter(String name, String greeting) {
            myName = name;
            this.greeting = greeting;
            receive(ReceiveBuilder.
                match(Greet.class, g -> { g.target.tell(AskName, self()); }).
                matchEquals(AskName, a -> { sender().tell(new TellName(myName), self()); }).
                match(TellName.class, t -> { System.out.println(greeting + ", " + t.name); }).
                build());
        }
        
        public static Props props(String name, String greeting) {
            return Props.create(Greeter.class, name, greeting);
          }
    }

Propriedades de passe

A Akka usa objetos Props para passar propriedades de configuração a um agente. Cada instância Props agrupa uma cópia dos argumentos do construtor necessários pela classe do ator, juntamente com uma referência à classe. Essas informações podem ser passadas ao construtor Props de duas maneiras. O exemplo da Listagem 5 passa o construtor para o agente como um parâmetro passe por nome ao construtor Props . Note que esta forma não chama o construtor imediatamente e passa o resultado; ela passa a chamada do construtor (isso pode parecer estranho se você está acostumado com o Java).

A outra forma de passar a configuração de agente ao construtor Props é fornecer a classe do agente como o primeiro parâmetro e os argumentos do construtor para o agente como os parâmetros remanescentes. Para o exemplo da Listagem 5 , essa forma de chamada seria Props(classOf[Greeter], name, greeting).

Qualquer forma de construtor Props que usar, os valores sendo passados ao agente recém-nascido precisa ser serializável, para que o Props possa ser enviado pela rede, se necessário, para qualquer lugar que a instância do agente for executada. No caso da chamada de construtor passe por nome, como usada na Listagem 5, o encerramento da chamada é serializado quando precisa ser enviada para fora da JVM.

A prática recomendada pela Akka para criar objetos Props em código Scala é a de definir um factory method em um objeto adicional, como realizado na Listagem 5. Essa técnica evita qualquer problema possível com o fechamento acidental sobre a referência this para um objeto de agente quando utilizar a abordagem de chamada de construtor passe por nome para Props. O Objeto adicional também é um ótimo local para definir as mensagens que o agente receberá, para que todas as informações associadas estejam em um único local. Para agentes Java, um método de construtor dentro da classe do ator funciona bem, como utilizado na Listagem 6.

gentes enviando mensagens

Cada um dos agentes Greeter da Listagem 5 está configurado com um nome e uma saudação, mas quando é solicitado a saudar outro agente, primeiro precisa descobrir o nome desse outro agente. Os agentes Greeter realizam essa tarefa ao enviar uma mensagem separada ao outro agente: uma mensagem AskName . A mensagem AskName não carrega nenhuma informação per se, mas a instância Greeter que a recebe sabe responder com uma mensagem TellName que carrega o nome do remetente TellName . Quando o primeiro Greeter recebe de volta uma mensagem TellName , imprime sua saudação.

Cada mensagem enviada a um agente vem com algumas informações adicionais fornecidas pela Akka, mais notadamente um ActorRef para o remetente da mensagem. Essas informações de envio podem ser acessadas a qualquer momento durante o processamento de uma mensagem ao chamar o método sender() definido na classe base do agente. Os agentes Greeter usam a referência de remetente em seu tratamento de uma mensagem AskName , assim, enviam a respostaTellName ao agente correto.

A Akka permite enviar uma mensagem em nome de outro agente (uma maneira benigna de roubo de identidade), para que o agente recebendo a mensagem veja o outro agente como o remetente. Isso é um recurso muito útil em sistemas de agentes, especialmente para trocas de mensagens do tipo de solicitação de resposta nas quais você quer que a resposta vá a algum lugar que não seja ao agente fazendo a solicitação. As mensagens enviadas pelo código do aplicativo fora de um agente, por padrão, usarão um agente Akka especial chamado de agente deadletter como remetente. O agente deadletter também é usado toda vez que uma mensagem não pode ser entregue a um agente, fornecendo uma maneira conveniente de monitorar mensagens não entregues em seu sistema de agentes ao habilitar a criação de logs apropriada (algo que abordarei na próxima parte do artigo).

Digitando agentes

Você deve ter percebido que em nenhum lugar na sequência de mensagens dos exemplos há nenhuma informação de tipo que explicitamente mostre que o destino da mensagem seja um Greeter . Esse é geralmente o caso com agentes Akka e as mensagens que eles trocam. Mesmo o ActorRef usado para identificar o agente de destino para a mensagem não é digitado.

A programação de um sistema de agentes não digitado possui vantagens práticas. É possível definir os tipos de agentes, — digamos, pelo conjunto de mensagens que podem processar, — mas fazer isso pode ser enganoso. Na Akka, agentes podem mudar seu comportamento (mais sobre isso na próxima parte do artigo), portanto, diferentes conjuntos de mensagens podem ser adequadas para diferentes estados de agentes. Os tipos também tendem a atrapalhar a elegante simplicidade do modelo de agente, que trata todos os agentes como tendo pelo menos o potencial de manipular qualquer mensagem.

Ainda assim, a Akka oferece suporte de agente digitado para quando você realmente quiser utilizá-lo. Este suporte é muito útil quando você tem uma interface entre o código do agente e o código não agente. Você pode definir uma interface que pode ser utilizada pelo código não agente com o agente, fazendo o agente parecer mais como um componente normal do programa. Para a maioria dos propósitos, isso provavelmente gerará mais problemas do que soluções, considerando a facilidade de enviar mensagens diretamente a um agente mesmo se não estiver no sistema do agente (como pode ver a partir de qualquer aplicativos de amostra até aqui, onde o código não agente envia mensagens), mas é ótimo ter a opção disponível.

Mensagens e mutabilidade

A Akka quer que você tenha certeza de não compartilhar acidentalmente dados mutáveis entre agentes. Se você fez isso, os resultados podem ser inválidos — não tão desastroso quanto trocar os fluxos de seus pacotes de prótons ao espantar fantasmas (referência aos Ghostbusters , caso você não saiba), mas mesmo assim muito ruim. O problema com o compartilhamento de dados mutáveis é que os agentes são executados em encadeamentos separados. Se compartilhar dados mutáveis entre agentes, não há coordenação entre os encadeamentos executando os agentes, então não verão o que os outros encadeamentos estão fazendo e podem se confundir em várias maneiras. Os problemas ficam piores se estiver executando um sistema distribuído, onde cada agente terá sua própria cópia de dados mutáveis.

Portanto, as mensagens precisam ser imutáveis e não somente no nível superficial. Se qualquer objeto for parte dos dados da mensagem, esses objetos também devem ser imutáveis, e assim por diante, até o fechamento de tudo referenciado pela mensagem. A Akka não pode impingir esse requisito no momento, mas os desenvolvedores Akka querem colocar restrições em algum momento no futuro. Se desejar que seu código continue utilizável nas futuras versões da Akka, você precisa prestar atenção a este requisito agora.

Ask versus tell

O código da Listagem 5 usa a operação padrão tell para enviar mensagens. Com a Akka, você também pode usar um padrão de mensagem ask , como uma operação auxiliar. A operação ask (exibida pelo ? operador ou por usar a função ask ) envia uma mensagem com um Future para a resposta. A Listagem 8 mostra o código da Listagem 5 reestruturado para usar ask em vez de tell.

Lista 8. Usando ask
import scala.concurrent.duration._
import akka.actor._
import akka.util._
import akka.pattern.ask

object Hello4 extends App {

  import Greeter._
  val system = ActorSystem("actor-demo-scala")
  val bob = system.actorOf(props("Bob", "Howya doing"))
  val alice = system.actorOf(props("Alice", "Happy to meet you"))
  bob ! Greet(alice)
  alice ! Greet(bob)
  Thread sleep 1000
  system shutdown

  object Greeter {
    case class Greet(peer: ActorRef)
    case object AskName
    def props(name: String, greeting: String) = Props(new Greeter(name, greeting))
  }

  class Greeter(myName: String, greeting: String) extends Actor {
    import Greeter._
    import system.dispatcher
    implicit val timeout = Timeout(5 seconds)
    def receive = {
      case Greet(peer) => {
        val futureName = peer ? AskName
        futureName.foreach { name => println(s"$greeting, $name") }
      }
      case AskName => sender ! myName
    }
  }
}

No código da Listagem 8, a mensagem TellName foi substituída por um ask. O futuro retornado pela operação ask é do tipo Future[Any], pois o compilador não sabe nada sobre o resultado a ser retornado. Quando o futuro for concluído, o foreach usa o despachante implícito definido pela instrução import system.dispatcher para executar o println. Se o futuro não for concluído com uma mensagem de resposta dentro do tempo limite permitido (outro valor implícito, definido como cinco segundos nesse caso), ele será concluído com uma exceção de tempo limite.

Nos bastidores, o padrão ask cria um agente temporário especializado que age como um intermediário na troca de mensagens. O intermediário recebe uma Promise e a mensagem a ser enviada, juntamente com a referência do agente de destino. Envia a mensagem, depois aguarda pela mensagem de resposta esperada. Quando a resposta é recebida, preenche a promessa e completa o futuro usado pelo agente original.

A utilização da abordagem ask apresenta algumas limitações. Em especial, para evitar expor o estado do agente (e potencialmente causar problemas de passagem), é preciso se certificar de que o agente não esteja usando nenhum estado mutável no código executado quando o futuro é concluído. Na prática, é geralmente mais fácil usar o padrão tell para mensagens enviadas entre agentes. O único caso em que o padrão ask é mais útil ocorre quando você tem o código do aplicativo (como um programa principal que ativa o sistema de agente e cria os agentes iniciais) que precisa obter uma resposta de um agente (seja digitada ou não digitada).

Agentes de partes de bits

Não hesite em apresentar um novo agente em seu design sempre que ele ajudar a manipular as operações assíncronas de maneira limpa.

O agente de uso único criado pelo padrão ask é um exemplo de um bom princípio de design a ser lembrado ao utilizar o Akka. Muitas vezes é desejável estruturar seu sistema de agente para que etapas de processamento intermediárias sejam realizadas por agentes especiais criados para aquela finalidade específica. Um exemplo comum é a necessidade de mesclar resultados assíncronos diferentes antes de passar para a próxima fase de processamento. Caso use mensagens para os resultados diferentes, você pode fazer um agente coletar os resultados até que tudo esteja pronto e depois iniciar a próxima etapa. Isso é basicamente uma generalização do agente de uso único usado pelo padrão ask.

Agentes Akka são leves (aproximadamente entre 300 a 400 bytes por instância de agente, mais qualquer armazenamento que a classe de agente utilizar). Portanto, você pode estruturar seu desenho com segurança para o uso de muitos agentes quando adequado. A utilização de agentes especializados ajuda a manter seu código simples e fácil de compreender, que é uma grande vantagem ao escrever programas simultâneos do que ao escrever programas sequenciais. Não hesite em apresentar um novo agente em seu design sempre que ele ajudar a manipular as operações assíncronas de maneira limpa.

Intermissões

A Akka é um sistema poderoso, mas a Akka e o modelo de agentes em geral exige um estilo de programação diferente do código processual linear. Com o código processual, você tem uma estrutura de programa onde todas as chamadas são determinísticas e é possível visualizar toda a arvore de chamada do programa. No modelo de agente, as mensagens são disparadas de forma otimista sem nenhuma garantia de que serão entregues, e geralmente é difícil estabelecer a sequência em que as coisas acontecem. O benefício do modelo de agentes é a maneira fácil de estruturar aplicativos para alta simultaneidade e escalabilidade, um ponto que voltarei a abordar em uma parte posterior do artigo.

Espero que este artigo tenha fornecido uma imagem suficiente da Akka para que você queira saber mais. Da próxima vez, iremos nos aprofundar nos sistemas de agente e interações de agentes, incluindo uma visão sobre como é fácil monitorar as interações entre agentes em seu sistema.


Recursos para download


Temas relacionados


Comentários

Acesse ou registre-se para adicionar e acompanhar os comentários.

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=80
Zone=Tecnologia Java, Software livre
ArticleID=1025905
ArticleTitle=Simultaneidade JVM: Agir de forma assíncrona com Akka
publish-date=01222016