Come gestire lo stato iniziale in un'architettura basata su eventi?

33

In una architettura basata sugli eventi ogni componente agisce solo quando un evento viene inviato attraverso il sistema.

Immagina un'auto ipotetica con un pedale del freno e una luce del freno.

  • La spia del freno si accende on quando riceve un evento brake_on e off quando riceve brake_off evento.
  • Il pedale del freno invia un evento brake_on quando viene premuto, e un evento brake_off quando viene rilasciato.

Questo è tutto a posto, finché non si ha la situazione in cui la macchina è accesa con il pedale del freno già premuto . Poiché la luce del freno non ha mai ricevuto un evento brake_on , rimarrà spenta - chiaramente una situazione indesiderabile. L'accensione della luce del freno per impostazione predefinita inverte solo la situazione.

Che cosa si potrebbe fare per risolvere questo "problema di stato iniziale"?

EDIT: grazie per tutte le risposte. La mia domanda non riguardava un'automobile reale. Nelle auto hanno risolto questo problema inviando continuamente lo stato - quindi non c'è alcun problema di avvio in quel dominio. Nel mio dominio software, quella soluzione userebbe molti cicli di CPU non necessari.

EDIT 2: oltre alla risposta di @ gbjbaanb , sto andando per un sistema in cui:

  • l'ipotetico pedale del freno, dopo l'inizializzazione, invia un evento con il suo stato, e
  • l'ipotetica luce del freno, dopo l'inizializzazione, invia un evento che richiede un evento di stato dal pedale del freno.

Con questa soluzione, non ci sono dipendenze tra componenti, nessuna condizione di gara, nessuna coda di messaggi per andare in stallo e nessun componente "master".

    
posta Frank Kusters 11.02.2015 - 15:41
fonte

6 risposte

32

Ci sono molti modi per farlo, ma preferisco mantenere un sistema basato su messaggi il più possibile disaccoppiato. Ciò significa che il sistema generale non può leggere lo stato di alcun componente, né alcun componente leggere lo stato di nessun altro (in questo modo si trovano i legami di spaghetti delle dipendenze).

Quindi, mentre il sistema in esecuzione si prenderà cura di se stesso, abbiamo bisogno di un modo per dire ad ogni componente di iniziare se stesso, e abbiamo già una cosa del genere nella registrazione del componente, cioè all'avvio il sistema principale deve informare ogni componente che ora è registrato (o chiederà a ciascun componente di restituire i suoi dettagli in modo che possa essere registrato). Questo è lo stadio in cui il componente può eseguire le sue attività di avvio e può inviare messaggi come farebbe nel normale funzionamento.

Quindi il pedale del freno, all'avvio dell'accensione, riceverebbe un messaggio di registrazione / controllo dalla direzione della macchina e non restituirebbe solo un messaggio "Sono qui e funzionante", ma controllerebbe il suo stato e inviare i messaggi per quello stato (ad es. un messaggio con pedale premuto).

Il problema diventa quindi una dipendenza di avvio, come se la luce del freno non fosse ancora registrata, quindi non riceverà il messaggio, ma è facilmente risolvibile accodando tutti questi messaggi finché il sistema principale non ha completato l'avvio, la registrazione e controlla la routine.

Il più grande vantaggio è che non è necessario alcun codice speciale per gestire l'inizializzazione, tranne che devi già scrivere (ok, se l'invio del messaggio per gli eventi del pedale del freno è in un pedale del freno dovrai chiamarlo anche la tua inizializzazione, ma di solito non è un problema a meno che tu non abbia scritto quel codice legato pesantemente alla logica del gestore) e nessuna interazione tra componenti eccetto quelli che già si inviano reciprocamente come normali. A causa di questo, le architetture di passaggio dei messaggi sono molto buone!

    
risposta data 11.02.2015 - 17:48
fonte
4

È possibile avere un evento di inizializzazione che imposta gli stati in modo appropriato al momento del caricamento / avvio. Ciò può essere auspicabile per sistemi o programmi semplici che non includono più parti hardware, tuttavia per sistemi più complicati con più componenti fisici quando si corre lo stesso rischio di non inizializzarsi affatto - se un evento di "frenatura su" viene perso o perso lungo la comunicazione sistema (ad esempio, un sistema basato su CAN) è possibile inavvertitamente impostare il sistema all'indietro come se lo si avviava con il freno premuto. Più controller puoi avere, ad esempio con un'auto, maggiore è la probabilità che qualcosa venga perso.

Per tener conto di ciò, è possibile che la logica "freno attivo" invii ripetutamente eventi di "frenata". Forse ogni 1/100 di secondo o qualcosa del genere. Il tuo codice contenente il cervello può ascoltare questi eventi e innescare il "freno" mentre li sta ricevendo. Dopo 1 / 10sec di non ricevere i segnali "brake on", si attiva un evento "brake_off" interno.

Diversi eventi avranno requisiti di temporizzazione notevolmente diversi. In una macchina, la luce dei freni deve essere molto più veloce di quella della luce del carburante (dove un ritardo di più secondi è probabilmente accettabile) o altri sistemi meno importanti.

La complessità del tuo sistema fisico determinerà quale di questi approcci è più appropriato. Dato il tuo esempio è un veicolo, tu probabilmente vorresti qualcosa di simile a quest'ultimo.

In ogni caso, con un sistema fisico, NON si vuole fare affidamento su un singolo evento ricevuto / elaborato correttamente. I microcontrollori collegati su un sistema in rete spesso hanno un timeout "I'm alive" per questo motivo.

    
risposta data 11.02.2015 - 16:12
fonte
1

In questo caso, non modellerei il freno come un semplice on / off. Piuttosto, vorrei inviare eventi di "pressione dei freni". Ad esempio, una pressione di 0 indica off e una pressione di 100 viene completamente depressa. Il sistema (nodo) invierebbe costantemente gli eventi di pressione di rottura (ad un certo intervallo) al (i) controller (i) secondo necessità.

Quando il sistema è stato avviato, avrebbe iniziato a ricevere eventi di pressione finché non è stato spento.

    
risposta data 11.02.2015 - 16:38
fonte
1

Se il tuo unico mezzo per passare informazioni di stato è attraverso eventi, allora sei nei guai. Invece, è necessario essere in grado di entrambi:

  1. interrogare lo stato corrente del pedale del freno e
  2. registrati per gli eventi "stato modificato" dal pedale del freno.

La luce del freno può essere vista come un osservatore del pedale del freno. In altre parole, il pedale del freno non sa nulla della luce del freno e può funzionare senza di esso. (Ciò significa che qualsiasi nozione del pedale del freno che invii proattivamente un evento di "stato iniziale" alla luce del freno è mal concepita.)

All'istanziazione del sistema, la luce del freno si registra con il pedale del freno per ricevere le notifiche di frenata, e legge anche lo stato corrente del pedale del freno e si accende o spegne.

Quindi, le notifiche di frenata possono essere implementate in tre modi:

  1. come eventi "stato pedale di frenatura senza parametri"
  2. come un paio di "pedale del freno è ora premuto" e "pedale del freno è ora rilasciato" eventi
  3. come un "nuovo stato di pedale in rottura" con un parametro "depresso" o "rilasciato".

Preferisco il primo approccio, il che significa che al ricevimento della notifica, la luce del freno farà semplicemente quello che sa già come fare: leggere lo stato corrente del pedale del freno e accendersi o spegnersi.

    
risposta data 11.02.2015 - 17:02
fonte
0

In un sistema basato sugli eventi (che attualmente utilizzo e amo), trovo importante mantenere le cose il più disaccoppiate possibile. Con questa idea in mente, esaminiamo bene.

È importante avere uno stato predefinito. La tua luce del freno assumerebbe lo stato predefinito di "spento" e il pedale del freno assumerebbe lo stato predefinito di "alto". Qualsiasi modifica successiva sarà un evento.

Ora per rispondere alla tua domanda. Immagina che il tuo pedale del freno venga inizializzato e premuto, l'evento si attiva, ma non ci sono ancora luci dei freni per ricevere l'evento. Ho trovato più facile separare la creazione degli oggetti (dove i listener di eventi sarebbero stati inizializzati) come un passaggio separato prima inizializzando qualsiasi logica. Ciò impedirà qualsiasi condizione di gara come hai descritto.

Inoltre trovo scomodo utilizzare due diversi eventi per ciò che è effettivamente la stessa cosa . brake_off e brake_on potrebbero essere semplificati in e_brake con un parametro bool on . Puoi semplificare i tuoi eventi in questo modo aggiungendo dati di supporto.

    
risposta data 11.02.2015 - 18:59
fonte
0

Ciò di cui hai bisogno è un evento broadcast e messaggi in arrivo. Una trasmissione è un messaggio che viene pubblicato su un numero non specificato di ascoltatori. Un componente può iscriversi per eventi broadcast in modo che riceva solo gli eventi a cui è interessato. Ciò fornisce il disaccoppiamento, poiché il mittente non ha bisogno di sapere chi sono i destinatari. La tabella di sottoscrizione deve essere configurata staticamente durante l'installazione del componente (anziché quando viene inizializzata). La posta in arrivo è una parte del router di messaggi che funge da buffer per contenere i messaggi quando il componente di destinazione è offline.

L'uso delle fatture comporta un problema, che è la dimensione della casella di posta. Non si desidera che il sistema debba conservare un numero crescente di messaggi per componenti che non saranno mai più in linea. Questo è importante soprattutto con il sistema embedded con rigidi vincoli di memoria. Per superare il limite di dimensioni della casella di posta, tutti i messaggi trasmessi devono seguire alcune regole. Le regole sono:

  1. ogni evento di trasmissione richiede un nome
  2. in qualsiasi momento, il mittente di un evento di trasmissione può avere solo una trasmissione attiva con un nome specificato
  3. l'effetto causato dall'evento deve essere idempotente

Il nome della trasmissione deve essere dichiarato durante l'installazione del componente. Se un componente invia una seconda trasmissione con lo stesso nome prima che il ricevente elabori quella precedente, la nuova trasmissione sovrascrive quella precedente. Ora puoi avere un limite per le dimensioni della casella di posta statica, che può essere garantita per non superare mai una determinata dimensione e può essere precalcolata in base alle tabelle di iscrizione.

Infine, hai anche bisogno di un archivio di trasmissione. L'archivio di trasmissione è una tabella che contiene l'ultimo evento di ciascun nome di trasmissione. I nuovi componenti appena installati avranno la posta in arrivo prepopolata con i messaggi dall'archivio di trasmissione. Come il messaggio in arrivo, anche l'archivio broadcast può avere dimensioni statiche.

Inoltre, per far fronte a situazioni in cui il router dei messaggi è offline, sono necessari anche i messaggi in uscita. Il messaggio in uscita è parte del componente che contiene temporaneamente il messaggio in uscita.

    
risposta data 12.02.2015 - 01:39
fonte

Leggi altre domande sui tag