Quando NON è bene usare attori in Akka / Erlang?


58

Lavoro con Akka da 7-8 mesi ogni giorno. Quando ho iniziato, avrei lavorato sulle applicazioni e avrei notato che gli attori sarebbero stati usati praticamente ovunque nel sistema dell'attore per comunicare tra la maggior parte degli oggetti. Quindi ho fatto lo stesso: girare un altro attore per x / y / z.

Mi sembra che questo possa essere troppo indiscriminato, aggiungendo complessità laddove non è necessario, ma non riesco a trovare alcuna discussione su dove dovrebbero essere usati attori rispetto a una logica sincrona o persino asincrona tramite i futures. Ho iniziato a meditare sulla mia posizione dopo che il mio collega ha menzionato qualcosa di simile. Di recente ho realizzato diversi casi in cui ho ponderato un compito e quindi ho evitato di creare un altro attore perché avrei potuto ottenere lo stesso risultato in modo sicuro in un'implementazione immutabile, ad esempio qualcosa come ottenere valori di configurazione da un db o da un file da qualche parte in cui accedi molto raramente e attendere il risultato è il caso d'uso reale.

In particolare, mi sembra che in tutti i casi in cui stai giocando con uno stato immutabile, gli attori creano complessità e limitano il rendimento - una funzione pura in un oggetto, ad esempio, può essere chiamata contemporaneamente senza alcun rischio con qualsiasi livello di concorrenza, eppure un attore può elaborare solo un messaggio alla volta. La considerazione alternativa è che parcheggi il thread se devi aspettare il risultato a meno che non inizi a utilizzare i futures, ma nei casi in cui non devi preoccuparti di messaggistica asincrona o ridimensionare sembra che potrebbe essere eccessivo impiegare un attore.

Quindi la mia domanda è: c'è un brutto momento per usare gli attori? Sono curioso di come appaia Erlang e mi piacerebbe davvero la comprensione degli altri. O se ci sono alcuni principi sull'uso dell'attore.


Che tipo di lavoro offre l'opportunità di lavorare con Akka ogni giorno per mesi?
Den,

3
Quelli buoni. :) O fai tu stesso il cambiamento.
JasonG,

Vorrei sapere in particolare quali sono i compromessi tra l' askinging un attore e il semplice utilizzo di una pianura Future.
Max Heiber,

2
Ho finito per scrivere un libro su Akka un paio di anni dopo e penso che puoi riassumerlo in questo modo: se hai qualche stato - ad esempio un contatore - più thread che leggono e scrivono da / a quel valore finiranno per calpestarsi l'un l'altro senza sincronizzazione e blocco. Se metti quello stato in un attore, all'improvviso puoi essere sicuro che si accede in modo sicuro e non stai facendo cadere scritture o ottenere letture stantie. Gli attori di erlang e akka presumono anche che lo stato può andare male e l'attore può lanciare errori in modo da avere alcune caratteristiche del sistema di auto-guarigione. I futuri sono più semplici se non hai bisogno di mutare
JasonG,

molti anni dopo sono un programmatore di erlang / elixir e continuo a tornare su questo con nuove intuizioni :) L'elisir è abbastanza diverso in quanto non ci sono oggetti in alternativa ai processi, quindi i processi vengono sempre creati quando è necessario lo stato . Capita solo di essere simultaneo. Questa è la migliore euristica ancora.
JasonG,

Risposte:


23

Questa è una domanda che mi interessa e su cui ho svolto alcune ricerche. Per altri punti di vista, vedi questo post sul blog di Noel Walsh o questa domanda su Stack Overflow. Ho alcune opinioni che vorrei offrire:

  • Penso che Akka, perché funziona con i messaggi, incoraggi una "mentalità push". Spesso, per la concorrenza, direi che non è quello che vuoi. Pull è molto più sicuro. Ad esempio, un modello comune per i sistemi distribuiti è avere un insieme di lavoratori che elaborano le informazioni in una coda . Ovviamente questo è possibile in Akka ma non sembra necessariamente essere il primo approccio che le persone provano . Akka offre anche cassette postali durature, ma dipende ancora da come le usi: una singola coda condivisa è molto più flessibile rispetto alle code per lavoratore per bilanciare / riassegnare il lavoro.
  • È facile entrare nella mentalità di sostituire le tue lezioni con attori. In effetti alcune persone sembrano addirittura sostenerlo dicendo che gli attori dovrebbero fare solo una cosa . Preso alla sua logica conclusione, questo aumenta la complessità del codice come descrive Jason, perché se ogni classe è un attore, ci sono molti messaggi extra e blocchi di ricezione / invio. Inoltre rende più difficile comprendere e testare il codice perché si perde la formalità delle interfacce - e non sono convinto che i commenti siano una soluzione a questo . Inoltre, nonostante la leggendaria efficienza di Akka , sospetto che la proliferazione degli attori non sia una buona idea per quanto riguarda le prestazioni: quando usiamo i thread Java sappiamo che sono preziosi e li conserviamo di conseguenza.
  • È legato al punto precedente, ma un altro fastidio è la perdita di informazioni sul tipo evidenziate da Noel e Pino, poiché per molti di noi è per questo che stiamo usando Scala piuttosto che altre lingue come Python. Ci sono alcuni modi per aggirare questo, ma sono o non standard , non consigliati o sperimentali .
  • Infine, la concorrenza, anche se hai una mentalità "lascia che si blocchi" , è difficile. Modelli di programmazione alternativi possono essere d'aiuto, ma non risolvono i problemi - li cambiano - ecco perché è bene pensarci formalmente . È anche il motivo per cui lo sviluppatore di Joe Average cerca strumenti già pronti come database RabbitMQ, Storm, Hadoop, Spark, Kafka o NoSQL. Akka ha alcuni strumenti e componenti predefiniti, il che è interessante, ma sembra anche piuttosto basso livello, quindi elementi comuni più precisi e predefiniti dei sistemi distribuiti aiuterebbero gli sviluppatori e garantire che i sistemi siano costruiti correttamente.

Come Jason, mi piacerebbe sentire le opinioni degli altri qui. Come posso affrontare alcuni dei problemi di cui sopra e utilizzare meglio Akka?


1
Ho trovato questo post sui test in Akka spot-on timgilbert.wordpress.com/2013/07/07/…
Mark Butler

La "composizione per ereditarietà" è ancora utile per l'iniezione di dipendenza. Ad esempio, passa la dipendenza come oggetti di scena (usa i parametri del costruttore). Il problema sarebbe quindi la supervisione: l'attore creato sarebbe supervisionato da qualche parte diverso dall'attore. Un router potrebbe essere usato per avvolgere una strategia di supervisione attorno agli attori creati al livello più alto ma santo che ora è piuttosto complesso! Immagino che la torta sia una soluzione migliore, ad esempio avere un tratto con la creazione dell'attore per un attore di servizi di db. Quindi basta usare un tratto di servizio db mock / stub di prova nel contesto del testo.
JasonG,

1
Sì, esattamente - la supervisione non funziona bene con l'iniezione di dipendenza. Inoltre ho avuto casi in cui era necessario iniettare una fabbrica piuttosto che la classe, altrimenti c'erano problemi di chiusura più stretti - immagino a causa del trattamento dei fili di Akka.
Mark Butler,

Penso che non sia una cattiva idea - grazie Mark - potrei davvero provare a scriverne un po 'e socializzarlo. Potresti iniettare qualcosa che dia forse i puntelli? Una sorta di fabbrica di attori in cui il consumatore può creare un'istanza dell'attore. ad esempio def method (arg) = return Props (new ActorWithArgs (arg)) da quella fabbrica (psuedo?) in modo da poter rendere l'attore nel giusto contesto. Questa sembra una buona idea e un buon obiettivo anche per le soluzioni per torte di Di.
JasonG,

Ho appena iniziato a seguire questo corso: coursera.org/course/posa Sebbene sia principalmente rivolto a persone che programmano Android, è anche una buona panoramica della concorrenza in Java. Quindi una cosa di cui mi chiedo è "Akka non è solo una forma elaborata di loop di eventi con campane e fischietti (perché puoi mettere loop di eventi su thread diversi)?"
Mark Butler,

21

Vale la pena considerare a cosa serve il modello dell'attore: il modello dell'attore è

  1. un modello di concorrenza
  2. che evita l'accesso simultaneo allo stato mutabile
  3. utilizzando meccanismi di comunicazione asincroni per fornire concorrenza.

Ciò è prezioso perché l'utilizzo dello stato condiviso da più thread diventa davvero difficile, specialmente quando ci sono relazioni tra i diversi componenti dello stato condiviso che devono essere mantenuti sincronizzati. Tuttavia, se si dispone di componenti di dominio in cui:

  • Non si consente la concorrenza, OR
  • Non si consente lo stato modificabile (come nella programmazione funzionale), OR
  • Devi fare affidamento su alcuni meccanismi di comunicazione sincrona,

quindi il modello dell'attore non fornirà molti (se ve ne sono) benefici.

Spero che aiuti.


Grazie - suppongo che dovresti davvero valutare due cose allora - stato mutevole e concorrenza? Nessun problema da risolvere se una classe è apolide O non concorrente. Un'altra considerazione: penso che la tolleranza agli errori sia un altro punto? ad es. se si dispone di un client Redis immutabile ma può non funzionare durante l'esecuzione - non sarebbe un buon caso d'uso? che riavviare quell'attore potrebbe essere necessario? Come - sebbene la classe possa essere puramente immutabile - è molto probabile che ci sia uno stato corrotto esterno all'attore immutabile che può causarne il fallimento.
JasonG,

Immagino che l'attore responsabile della comunicazione con Redis otterrà l'eccezione e riavvierà comunque.
JasonG,

@JasonG A mio avviso, la "tolleranza agli errori" non è un aspetto del modello di attore in generale - è qualcosa che viene fornito con l'implementazione del modello di attore in Erlang (e forse Akka?). Non posso parlare con Redis, anche se devo ammettere che "client immutabile" mi sembra strano ... Se un componente software può fallire, non vedo come possa essere considerato immutabile.
Aidan Cully,

La tolleranza ai guasti e la supervisione esistono in Akka ed è molto simile a Erlang. Capisco quello che stai dicendo sul client immutabile ma immutabile significa che lo stato del codice non cambia da nessuna parte. Se inizializzo una connessione all'avvio, è possibile che il codice client possa essere immutabile con una strategia di riavvio utilizzata su qualsiasi tipo di errore, semplicemente reinizializzando l'attore in caso di problemi.
JasonG,

12

La tua intuizione è corretta, IMHO. Usare gli attori ovunque è come avere il proverbiale martello e vedere solo le unghie.

La migliore pratica di Erlang consiste nell'utilizzare processi / attori per tutte le attività che si svolgono contemporaneamente. Cioè, proprio come nella vita reale. A volte è difficile trovare la giusta granularità, ma la maggior parte delle volte lo sai solo guardando il dominio modellato e usando un po 'di buon senso. Temo di non avere un metodo migliore di quello, ma spero che aiuti.


Figo, grazie. Sono davvero interessato a vedere cosa succede in questa discussione.
JasonG,

0

Per ordinare i messaggi di input / output:

Di recente ho incontrato un'applicazione basata su Akka in cui il modello dell'attore ha effettivamente causato problemi di concorrenza, un modello più semplice sarebbe stato meglio sotto carico.

Il problema era che i messaggi in arrivo si muovevano in diverse "corsie" (attraverso percorsi di attori diversi) ma il codice presupponeva che i messaggi sarebbero arrivati ​​alla loro destinazione finale nello stesso ordine in cui sono arrivati. Finché i dati sono arrivati ​​con intervalli sufficientemente ampi, ha funzionato perché ci sarebbe stato un solo messaggio in conflitto verso la destinazione. Quando gli intervalli diminuirono, iniziarono ad arrivare fuori servizio e causando comportamenti strani.

Il problema avrebbe potuto essere risolto correttamente con un po 'meno attori, ma è un errore facile da fare quando li si abusa.


Questo è fondamentale e ampiamente rilevante. Puoi solo garantire l'ordine tra due attori. doc.akka.io/docs/akka/current/scala/general/… Non è possibile garantire la consegna degli ordini in nessun sistema in cui si esegue il fan out / in. andreasohlund.net/2012/01/18/dont-assume-message-ordering
JasonG

-1

Secondo me ci sono due casi d'uso per gli attori. Risorse condivise come porte e simili e stato di grandi dimensioni. Il primo è stato ben discusso finora dalla discussione, ma anche il grande stato è un valido motivo.

Una grande struttura che viene passata ad ogni chiamata di procedura può usare molto stack. Questo stato può essere inserito in un processo separato, la struttura sostituita da un ID di processo e quel processo richiesto su base richiesta.

Database come la mnesia possono essere pensati come memorizzazione dello stato esternamente al processo di interrogazione.


1
Puoi chiarire? Intendi lo stato grande attraverso la rete? All'interno di un processo locale passare un riferimento a una struttura immutabile non ha quasi alcun costo e non è possibile passare strutture mutabili. Supponendo che stai parlando di una grande struttura mutabile che deve essere copiata per inviare? Quindi è sostanzialmente la stessa cosa di nuovo: lo stato condiviso. Le dimensioni non cambiano davvero nulla. Fammi sapere se sto fraintendendo.
JasonG,

Quindi, come si passa per riferimento? Sicuramente il modo per farlo è quello di mettere la struttura in un processo e passare il processid. Le chiamate locali metteranno i dati in pila e ogni chiamata ricorsiva lo farà di nuovo (ad eccezione della ricorsione della coda). L'elaborazione ricorsiva della struttura può essere eseguita utilizzando elenchi per trasferire lo stato da una chiamata all'altra, questo stato può quindi fare riferimento alla struttura nell'altro processo.
tony wallace,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.