Ciclo di vita di Reliable Services

Reliable Services è uno dei modelli di programmazione disponibili in Azure Service Fabric. Quando si parla del ciclo di vita di Reliable Services, è estremamente importante comprendere gli eventi di base del ciclo di vita. L'ordine esatto degli eventi dipende dai dettagli di configurazione.

In generale, il ciclo di vita di Reliable Services include gli eventi seguenti:

  • Durante l'avvio:
    • I servizi vengono costruiti.
    • I servizi hanno la possibilità di costruire e restituire zero o più listener.
    • Qualsiasi listener restituito viene aperto per la comunicazione con il servizio.
    • Viene chiamato il metodo runAsync del servizio, in modo che il servizio stesso possa eseguire operazioni a esecuzione prolungata o in background.
  • Durante l'arresto:
    • Il token di annullamento che è stato passato a runAsync viene annullato e i listener vengono chiusi.
    • L'oggetto servizio stesso viene eliminato.

L'ordine degli eventi in Reliable Services potrebbe cambiare leggermente a seconda che il servizio Reliable sia con o senza stato.

Inoltre, per i servizi con stato, è necessario gestire lo scenario di scambio del ruolo primario. Durante questa sequenza il ruolo di primario viene trasferito a un'altra replica (o ritorna) senza che il servizio venga arrestato.

Infine è necessario considerare le condizioni di errore.

Avvio di un servizio senza stato

Il ciclo di vita di un servizio senza stato è piuttosto semplice. Di seguito è riportato l'ordine degli eventi:

  1. Il servizio viene costruito.
  2. Viene richiamato StatelessService.createServiceInstanceListeners() e i listener restituiti vengono aperti. Viene richiamato CommunicationListener.openAsync() su ogni listener.
  3. Poi, in parallelo:
    • Viene chiamato il metodo runAsync del servizio (StatelessService.runAsync()).
    • Se presente, viene chiamato il metodo onOpenAsync del servizio. Nello specifico, viene chiamato StatelessService.onOpenAsync(). Si tratta di un override insolito ma comunque disponibile.

Arresto di un servizio senza stato

Quando si arresta un servizio senza stato, si segue lo stesso modello, ma in senso inverso:

  1. Tutti i listener aperti vengono chiusi. Viene richiamato CommunicationListener.closeAsync() su ogni listener.
  2. Il token di annullamento che è stato passato a runAsync() viene annullato. La verifica della proprietà isCancelled del token di annullamento restituisce true e, se viene chiamato, il metodo throwIfCancellationRequested del token genera un'eccezione CancellationException.
  3. Dopo il completamento di runAsync(), se presente, viene chiamato il metodo StatelessService.onCloseAsync() del servizio. Anche in questo caso si tratta di un override insolito, ma è possibile usarlo per chiudere in modo sicuro le risorse, arrestare qualsiasi elaborazione in background, completare il salvataggio dello stato esterno o chiudere le connessioni esistenti.
  4. Dopo il completamento di StatelessService.onCloseAsync(), l'oggetto servizio viene eliminato.

Avvio di un servizio con stato

I servizi con stato hanno un modello simile ai servizi senza stato, con poche modifiche. L'ordine degli eventi per l'avvio di un servizio con stato è il seguente:

  1. Il servizio viene costruito.
  2. Viene chiamato StatefulServiceBase.onOpenAsync(). L'override della chiamata nel servizio non è comune.
  3. Viene richiamato StatefulServiceBase.createServiceReplicaListeners().
    • Se il servizio è di tipo primario, tutti i listener restituiti vengono aperti. Viene richiamato CommunicationListener.openAsync() su ogni listener.
    • Se il servizio è di tipo secondario, solo i listener contrassegnati come listenOnSecondary = true vengono aperti. La presenza di listener aperti nei servizi secondari è meno comune.
  4. Poi, in parallelo:
    • Se il servizio è attualmente di tipo primario, viene chiamato il metodo StatefulServiceBase.runAsync() del servizio.
    • Viene chiamato StatefulServiceBase.onChangeRoleAsync(). L'override della chiamata nel servizio non è comune.

Nota

Per una nuova replica secondaria, StatefulServiceBase.onChangeRoleAsync() viene chiamato due volte. Una volta dopo il passaggio 2, quando diventa un secondario inattivo e di nuovo durante il passaggio 4, quando diventa un secondario attivo. Per altre informazioni sul ciclo di vita della replica e dell'istanza, vedere Ciclo di vita della replica e dell'istanza.

Arresto di un servizio con stato

Analogamente ai servizi senza stato, gli eventi del ciclo di vita durante l'arresto corrispondono a quelli durante l'avvio, ma invertiti. Quando viene arrestato un servizio con stato, si verificano gli eventi seguenti:

  1. Tutti i listener aperti vengono chiusi. Viene richiamato CommunicationListener.closeAsync() su ogni listener.
  2. Il token di annullamento che è stato passato a runAsync() viene annullato. Una chiamata del metodo isCancelled() del token di annullamento restituisce true e, se viene chiamato, il metodo throwIfCancellationRequested() del token genera un'eccezione OperationCanceledException. Service Fabric attende il completamento di runAsync().

Nota

È necessario attendere il completamento di runAsync solo se la replica è una replica primaria.

  1. Dopo il completamento di runAsync(), se presente, viene chiamato il metodo StatefulServiceBase.onCloseAsync() del servizio. Questa chiamata è un override insolito, ma comunque disponibile.
  2. Dopo il completamento di StatefulServiceBase.onCloseAsync(), l'oggetto servizio viene eliminato.

Scambi primari di un servizio con stato

Quando un servizio con stato è in esecuzione, i listener di comunicazione vengono aperti e il metodo runAsync viene chiamato solo per le repliche primarie di tali servizi con stato. Le repliche secondarie vengono costruite, ma non ricevono altre chiamate. Quando un servizio con stato è in esecuzione, la replica primaria può cambiare. Gli eventi del ciclo di vita che una replica con stato può vedere dipendono dal fatto che la replica venga abbassata o alzata di livello durante lo scambio.

Per la replica primaria abbassata di livello

Service Fabric richiede che la replica primaria abbassata di livello interrompa l'elaborazione dei messaggi e qualsiasi attività in background. Questo passaggio è simile a quando il servizio viene arrestato. Una differenza è che il servizio non viene eliminato o chiuso, in quanto rimane come secondario. Si verificano gli eventi seguenti:

  1. Tutti i listener aperti vengono chiusi. Viene richiamato CommunicationListener.closeAsync() su ogni listener.
  2. Il token di annullamento che è stato passato a runAsync() viene annullato. Una verifica del metodo isCancelled() del token di annullamento restituisce true. Se chiamato, il metodo throwIfCancellationRequested() del token genera un'eccezione OperationCanceledException. Service Fabric attende il completamento di runAsync().
  3. I listener contrassegnati come listenOnSecondary = true vengono aperti.
  4. Viene chiamato il metodo StatefulServiceBase.onChangeRoleAsync() del servizio. L'override della chiamata nel servizio non è comune.

Per la replica secondaria alzata di livello

In modo analogo, Service Fabric richiede che la replica secondaria alzata di livello inizi ad ascoltare i messaggi in transito e avvii le attività in background necessarie. Questo processo è simile a quando il servizio viene creato. La differenza è che la replica stessa esiste già. Si verificano gli eventi seguenti:

  1. CommunicationListener.closeAsync() viene chiamato per tutti i listener aperti (contrassegnati con listenOnSecondary = true).
  2. Tutti i listener di comunicazione vengono aperti. Viene richiamato CommunicationListener.openAsync() su ogni listener.
  3. Poi, in parallelo:
    • Viene chiamato il metodo StatefulServiceBase.runAsync() del servizio.
    • Viene chiamato StatefulServiceBase.onChangeRoleAsync(). L'override della chiamata nel servizio non è comune.

Nota

createServiceReplicaListeners viene chiamato una sola volta e non viene chiamato di nuovo durante il processo di innalzamento di livello o abbassamento di livello della replica; vengono usate le stesse istanze di ServiceReplicaListener , ma vengono create nuove istanze di CommunicationListener (chiamando il metodo ServiceReplicaListener.createCommunicationListener ) dopo la chiusura delle istanze precedenti.

Problemi comuni durante l'arresto del servizio con stato e l'abbassamento di livello primario

Service Fabric modifica lo stato primario di un servizio con stato per diversi motivi. Le ragioni più comuni sono il ribilanciamento del cluster e l'aggiornamento dell'applicazione. Durante queste operazioni, è importante che il servizio rispetti il cancellationToken. Questo vale anche durante l'arresto normale del servizio, ad esempio se il servizio è stato eliminato.

I servizi che non gestiscono correttamente l'annullamento possono essere soggetti a diversi problemi. Queste operazioni sono lente perché Service Fabric attende l'arresto normale dei servizi. Questo può infine comportare la mancata riuscita degli aggiornamenti che raggiungono il timeout ed eseguono il rollback. Il mancato rispetto del token di annullamento può anche provocare uno sbilanciamento dei cluster. I cluster diventano sbilanciati perché viene eseguito un accesso frequente ai nodi. Tuttavia i servizi non possono essere ribilanciati in quanto il loro spostamento richiede troppo tempo.

Trattandosi di servizi con stato, è anche probabile che i servizi usino raccolte Reliable Collections. In Service Fabric, quando un servizio primario viene abbassato di livello, una delle prime cose che accade è che viene revocato l'accesso in scrittura allo stato sottostante. Ciò comporta una seconda serie di problemi che potrebbero influire sul ciclo di vita del servizio. Le raccolte restituiscono eccezioni in base alla tempistica e al fatto che la replica venga spostata o arrestata. È importante gestire queste eccezioni correttamente.

Le eccezioni generate da Service Fabric sono permanenti (FabricException) o temporanee (FabricTransientException). Le eccezioni permanenti dovrebbero essere registrate e generate, mentre quelle temporanee possono essere ripetute in base a una logica di ripetizione.

Una parte importante del test e della convalida di Reliable Services consiste nella gestione delle eccezioni che derivano dall'uso di ReliableCollections in combinazione con gli eventi del ciclo di vita del servizio. Si consiglia sempre di eseguire il servizio in condizioni di carico. È inoltre opportuno eseguire aggiornamenti e test CHAOS prima della distribuzione nell'ambiente di produzione. Questi passaggi di base contribuiscono ad assicurare che il servizio sia implementato correttamente e che gestisca gli eventi del ciclo di vita nel modo giusto.

Note sul ciclo di vita del servizio

  • Entrambe le chiamate del metodo runAsync() e di createServiceInstanceListeners/createServiceReplicaListeners sono facoltative. Un servizio potrebbe averne una, entrambe o nessuna. Ad esempio, se il servizio esegue tutte le attività in risposta alle chiamate dell'utente, non è necessario implementare runAsync(). Sono necessari solo i listener di comunicazione e il codice associato. Analogamente, la creazione e la restituzione di listener di comunicazione sono facoltative. Il servizio potrebbe avere solo attività in background da eseguire, pertanto è necessario implementare solo runAsync().
  • È un comportamento valido per un servizio completare runAsync() correttamente e ritornare. Questa non è considerata una condizione di errore e rappresenta il completamento dell'attività in background del servizio. Per i servizi Reliable Services con stato viene chiamato nuovamente runAsync() se il servizio è stato abbassato dal livello primario e poi alzato di nuovo al livello primario.
  • Se un servizio esce da runAsync() generando un'eccezione imprevista, questo è un errore. L'oggetto servizio viene arrestato e viene segnalato un errore di integrità.
  • Anche se non c'è alcun limite di tempo per il completamento di questi metodi, si perde immediatamente la possibilità di scrivere. Pertanto, non è possibile completare alcuna operazione effettiva. Si consiglia di completare al più presto la richiesta di annullamento ricevuta. Se il servizio non risponde a queste chiamate API entro un intervallo di tempo ragionevole, Service Fabric potrebbe terminare forzatamente il servizio. In genere ciò accade solo durante gli aggiornamenti delle applicazioni o quando viene eliminato un servizio. Questo timeout è di 15 minuti per impostazione predefinita.
  • Gli errori nel percorso onCloseAsync() comportano la chiamata di onAbort(). Questa chiamata rappresenta l'ultima e migliore opportunità del servizio di pulire e rilasciare le risorse che sono state richieste. Questo metodo in genere viene chiamato quando viene rilevato un errore permanente sul nodo o quando Service Fabric non è in grado di gestire in modo affidabile il ciclo di vita dell'istanza del servizio a causa di errori interni.
  • OnChangeRoleAsync() viene chiamato quando la replica del servizio con stato cambia ruolo, ad esempio primario o secondario. Alle repliche primarie viene assegnato lo stato di scrittura (sono autorizzate a creare raccolte Reliable Collections e a scrivervi). Alle repliche secondarie viene assegnato lo stato di lettura (possono solo leggere da raccolte Reliable Collections esistenti). La maggior parte delle operazioni in un servizio con stato viene eseguita nella replica primaria. Le repliche secondarie possono eseguire la convalida di sola lettura, la generazione di report, il data mining o altri processi di sola lettura.

Passaggi successivi