Quando não é bom usar atores em akka / erlang?

58

Estou trabalhando com akka por 7-8 meses agora diariamente. Quando comecei, trabalhava nos aplicativos e notava que os atores seriam usados ​​basicamente em qualquer lugar do sistema de atores para se comunicar entre a maioria dos objetos. Então eu fiz o mesmo - criar outro ator por x / y / z.

Parece-me que isso pode ser muito indiscriminado, adicionando complexidade onde não é necessário - mas não consigo encontrar nenhuma discussão sobre onde os atores versus a lógica síncrona ou assíncrona simples, via futuros, devem ser usados. Comecei a ponderar minha posição depois que meu colega de trabalho mencionou algo semelhante. Percebi vários casos, mais recentemente, em que ponderei uma tarefa e evitei criar outro ator porque consegui o mesmo resultado com segurança em uma implementação imutável - por exemplo, algo como obter valores de configuração de um banco de dados ou arquivo em algum lugar em que você acessa com pouca frequência e aguardar o resultado é o caso de uso real.

Em particular, parece-me que, em qualquer caso em que você esteja jogando com um estado imutável, os atores criam complexidade e limitam a taxa de transferência - uma função pura em um objeto, por exemplo, pode ser chamada simultaneamente, sem risco de qualquer nível de simultaneidade. um ator pode processar apenas uma mensagem de cada vez. A consideração alternativa é que você estacionará o encadeamento se precisar esperar pelo resultado, a menos que comece a usar futuros, mas nos casos em que não precisa se preocupar com mensagens ou escala assíncronas, parece que pode ser um exagero empregar um ator.

Então, minha pergunta é - há um momento ruim para usar atores? Estou curioso para saber como erlang parece e realmente gostaria do insight de outras pessoas. Ou se existem alguns princípios sobre o uso do ator.

JasonG
fonte
Que tipo de trabalho oferece uma oportunidade de trabalhar com a Akka diariamente por meses?
Den
3
Bons. :) Ou faça a mudança você mesmo.
JasonG
Eu gostaria de saber especificamente quais são as trocas entre askum ator e o uso de uma planície Future.
quer
2
Acabei escrevendo um livro sobre Akka alguns anos depois e acho que você pode resumir da seguinte forma: Se você tiver algum estado - por exemplo, um contador - vários tópicos lendo e escrevendo de / para esse valor acabarão se chocando. sem sincronização e bloqueio. Se você colocar esse estado dentro de um ator, de repente, poderá ter certeza de que ele será acessado com segurança e não estará descartando gravações ou obtendo leituras obsoletas. Os atores erlang e akka também assumem que o estado pode ficar ruim e o ator pode gerar erros, para que você tenha algumas características do sistema de autocura. Futuros são mais simples se você não precisa sofrer mutações
JasonG
muitos anos depois, sou um programador erlang / elixir e continuo voltando a isso com novas idéias :) Elixir é bem diferente, pois não há objetos como uma alternativa aos processos, de modo que os processos são sempre construídos quando você precisa de estado . Por acaso é simultâneo. Essa é a melhor heurística ainda.
21418 JasonG

Respostas:

23

Esta é uma pergunta em que estou interessado e tenho pesquisado. Para outros pontos de vista, consulte esta postagem de Noel Walsh ou esta pergunta no Stack Overflow. Tenho algumas opiniões que gostaria de oferecer:

  • Eu acho que o Akka, por trabalhar com mensagens, incentiva uma "mentalidade de empurrar". Muitas vezes, por concorrência, eu argumentaria que não é isso que você deseja. Puxar é muito mais seguro. Por exemplo, um padrão comum para sistemas distribuídos é ter um conjunto de trabalhadores processando informações em uma fila . Obviamente isso é possível em Akka, mas não parece necessariamente ser a primeira abordagem que as pessoas tentam . O Akka também oferece caixas de correio duráveis, mas novamente depende de como você as usa - uma única fila compartilhada é muito mais flexível do que as filas por trabalhador para equilibrar / reatribuir o trabalho.
  • É fácil entrar na mentalidade de substituir suas aulas por atores. De fato, algumas pessoas parecem advogar dizendo que os atores devem fazer apenas uma coisa . Levado à sua conclusão lógica, isso aumenta a complexidade do código, como Jason descreve, porque se toda classe é um ator, há muitas mensagens extras e blocos de recebimento / envio. Também torna mais difícil entender e testar o código porque você perde a formalidade das interfaces - e não estou convencido de que os comentários sejam uma solução para isso . Além disso, apesar da lendária eficiência da Akka , suspeito que a proliferação de atores não seja uma boa ideia em termos de desempenho - quando usamos encadeamentos Java, sabemos que eles são preciosos e os conservam adequadamente.
  • Está relacionado ao ponto anterior, mas outro aborrecimento é a perda de informações de tipo que Noel e Pino destacam, pois para muitos de nós é por isso que estamos usando Scala em vez de outras linguagens como Python. Existem algumas maneiras de contornar isso, mas elas não são padrão , não são recomendadas ou experimentais .
  • Finalmente, a simultaneidade, mesmo se você tiver uma mentalidade de "deixe travar" , é difícil. Modelos de programação alternativos podem ajudar, mas eles não fazem os problemas desaparecerem - eles os modificam - é por isso que é bom pensar sobre eles formalmente . É também por isso que o desenvolvedor Joe Average busca ferramentas prontas, como os bancos de dados RabbitMQ, Storm, Hadoop, Spark, Kafka ou NoSQL. A Akka possui algumas ferramentas e componentes pré-construídos, o que é legal, mas também parece um nível bastante baixo; portanto, elementos comuns mais construídos de sistemas distribuídos ajudariam os desenvolvedores e garantiriam a criação correta dos sistemas.

Como Jason, estou ansioso para ouvir a visão de outras pessoas aqui. Como posso resolver alguns dos problemas acima e usar o Akka melhor?

Mark Butler
fonte
11
Eu encontrei este post sobre testes no Akka spot-on timgilbert.wordpress.com/2013/07/07/…
Mark Butler
"Composição sobre herança" ainda é útil para injeção de dependência. Por exemplo, passe a dependência como props (usa parâmetros de construtor). O problema então seria a supervisão - o ator criado seria supervisionado em outro lugar que não o ator. Um roteador pode ser usado para envolver uma estratégia de supervisão em torno de atores criados no nível superior, mas santo, isso é bastante complexo agora! Eu acho que o bolo seria uma solução melhor - tenha uma característica com a criação do ator para um ator de serviço de banco de dados, por exemplo. Em seguida, basta usar uma característica de serviço mock / stub db de teste no contexto de texto.
JasonG
11
Sim, exatamente - a supervisão não funciona bem com a injeção de dependência. Também tive casos em que era necessário injetar uma fábrica em vez da classe, caso contrário, houve problemas de fechamento estranhos - acho que devido ao tratamento de threads por Akka.
Mark Butler
Eu acho que não é uma má idéia - obrigado, marca - eu posso tentar escrever um pouco sobre isso e socializá-lo. Você pode injetar algo que dê o Props, talvez? Uma espécie de fábrica de atores em que o consumidor pode instanciar o ator. por exemplo, def method (arg) = return Props (new ActorWithArgs (arg)) dessa fábrica (psuedo?) para que você possa fazer o ator no contexto certo. Parece uma boa idéia e um bom alvo para soluções de bolo para di também.
JasonG
Comecei a seguir este curso: coursera.org/course/posa Embora seja voltado principalmente para pessoas que programam o Android, também é uma boa visão geral sobre simultaneidade em Java. Então, uma coisa que me pergunto é: "Akka não é apenas uma forma sofisticada de loops de eventos com alguns sinos e assobios (porque você pode colocar loops de eventos em diferentes segmentos)?"
Mark Butler
21

Vale a pena considerar para que serve o modelo de ator: o modelo de ator é

  1. um modelo de simultaneidade
  2. que evita o acesso simultâneo ao estado mutável
  3. usando mecanismos de comunicação assíncrona para fornecer simultaneidade.

Isso é valioso porque o uso do estado compartilhado de vários encadeamentos fica realmente difícil, especialmente quando há relacionamentos entre diferentes componentes do estado compartilhado que devem ser mantidos sincronizados. No entanto, se você tiver componentes de domínio nos quais:

  • Você não permite simultaneidade OU
  • Você não permite estado mutável (como na programação funcional), OU
  • Você deve confiar em algum mecanismo de comunicação síncrona,

então o modelo do ator não trará muitos benefícios (se houver).

Espero que ajude.

Aidan Cully
fonte
Obrigado - acho que você realmente precisa avaliar duas coisas: estado mutável e simultaneidade? Não há problema para resolver se uma classe é sem estado ou não é simultânea. Mais uma consideração - acho que a tolerância a falhas é outro ponto? por exemplo, se você possui um cliente redis imutável, mas ele pode falhar durante a execução - isso não seria um bom caso de uso? que reiniciar esse ator pode ser necessário? Como - embora a classe possa ser puramente imutável - é muito possível que exista um estado corrompido externo ao ator imutável que possa causar falhas.
JasonG
Acho que o ator responsável pela comunicação com os redis receberia a exceção e reiniciaria.
JasonG
@ JasonG Na minha opinião, "tolerância a falhas" não é um aspecto do modelo de ator em geral - é algo que vem com a implementação do modelo de ator em Erlang (e talvez Akka?). Não posso falar com redis, embora deva admitir que "cliente imutável" soa estranho para mim ... Se um componente de software pode falhar, não vejo como ele pode ser considerado imutável.
Aidan Cully
A tolerância a falhas e a supervisão existem no akka e é muito semelhante ao erlang. Entendo o que você está dizendo sobre cliente imutável, mas imutável significa que o estado do código não muda em lugar algum. Se eu inicializar uma conexão na inicialização, é possível que o código do cliente possa ser imutável com uma estratégia de reinicialização usada em qualquer tipo de falha, simplesmente reinicializando o ator ao encontrar um problema.
JasonG
12

Sua intuição está correta, IMHO. Usar atores em todos os lugares é como ter o martelo proverbial e ver apenas pregos.

A melhor prática de Erlang é usar processos / atores para todas as atividades que ocorrem simultaneamente. Ou seja, assim como na vida real. Às vezes, é difícil encontrar a granularidade certa, mas na maioria das vezes você apenas conhece o domínio modelado e usa um pouco de bom senso. Receio não ter um método melhor do que isso, mas espero que ajude.

Vlad Dumitrescu
fonte
Legal obrigado. Estou realmente interessado em ver o que acontece nesta discussão.
JasonG
0

Em ordem de entrada / saída de mensagens:

Encontrei recentemente um aplicativo baseado em akka, em que o modelo de ator realmente causava problemas de simultaneidade; um modelo mais simples seria suficiente sob carga.

O problema era que as mensagens recebidas estavam se movendo em diferentes 'faixas' (por diferentes caminhos de ator), mas o código supunha que as mensagens chegariam ao seu destino final na mesma ordem em que chegaram. Desde que os dados chegassem com intervalos suficientemente grandes, isso funcionaria porque haveria apenas uma única mensagem conflitante correndo para o destino. Quando os intervalos diminuíram, começaram a chegar fora de ordem e a causar comportamentos estranhos.

O problema poderia ter sido resolvido corretamente com um pouco menos de atores, mas é um erro fácil de cometer ao usá-los em excesso.

DHa
fonte
Isso é fundamental e amplamente relevante. Você só pode garantir pedidos entre dois atores. doc.akka.io/docs/akka/current/scala/general/… Você não pode garantir a entrega de pedidos em qualquer sistema em que se abra / saia. andreasohlund.net/2012/01/18/dont-assume-message-ordering
JasonG
-1

Na minha opinião, existem dois casos de uso para atores. Recursos compartilhados, como portas e similares, e estado grande. O primeiro foi bem coberto pela discussão até agora, mas o estado grande também é uma razão válida.

Uma grande estrutura sendo passada a cada chamada de procedimento pode usar muita pilha. Esse estado pode ser colocado em um processo separado, a estrutura substituída por uma identificação de processo e esse processo consultado conforme necessário.

Bancos de dados como mnésia podem ser considerados como um estado de armazenamento externo ao processo de consulta.

Tony Wallace
fonte
11
Você pode esclarecer? Você quer dizer grande estado na rede? Dentro de um processo local, passar uma referência a uma estrutura imutável quase não tem custo e você não pode passar por estruturas mutáveis. Supondo que você esteja falando de uma grande estrutura mutável que precisa ser copiada para ser enviada? Então isso é basicamente a mesma coisa novamente - estado compartilhado. O tamanho realmente não muda nada. Deixe-me saber se estou entendendo mal.
JasonG
Então, como você passa por referência? Certamente, a maneira de fazer isso é colocar a estrutura em um processo e passar o processid. As chamadas locais colocam os dados na pilha e todas as chamadas recursivas o fazem novamente (com exceção da recursão final). O processamento recursivo da estrutura pode ser feito usando listas para transferir o estado de uma chamada para outra; esse estado pode fazer referência à estrutura no outro processo.
wallace tony