Usare la ridondanza geografica per progettare applicazioni a disponibilità elevata

Le infrastrutture basate sul cloud come Archiviazione di Azure offrono una piattaforma a disponibilità elevata e durevole per ospitare dati e applicazioni. Gli sviluppatori di applicazioni basate sul cloud devono considerare attentamente come sfruttare questa piattaforma per ottimizzare questi vantaggi per gli utenti. Archiviazione di Azure offre opzioni di ridondanza geografica per garantire la disponibilità elevata anche durante un'interruzione a livello di area. Gli account di archiviazione configurati per la replica con ridondanza geografica vengono replicati in modo sincrono nell'area primaria e replicati in modo asincrono in un'area secondaria centinaia di chilometri di distanza.

Archiviazione di Azure offre due opzioni per la replica con ridondanza geografica: archiviazione con ridondanza geografica (GRS) e archiviazione con ridondanza geografica (GZRS). Per usare le opzioni di ridondanza geografica di Archiviazione di Azure, assicurarsi che l'account di archiviazione sia configurato per l'archiviazione con ridondanza geografica (RA-GRS) di accesso in lettura o per l'archiviazione con ridondanza geografica della zona (RA-GZRS). In caso contrario, è possibile altre informazioni su come modificare il tipo di replica dell'account di archiviazione.

Questo articolo illustra come progettare un'applicazione che continuerà a funzionare, anche se in una capacità limitata, anche quando si verifica un'interruzione significativa nell'area primaria. Se l'area primaria non è disponibile, l'applicazione può passare facilmente per eseguire operazioni di lettura rispetto all'area secondaria fino a quando l'area primaria non viene nuovamente reattiva.

Considerazioni sulla progettazione di applicazioni

È possibile progettare l'applicazione per gestire errori temporanei o interruzioni significative leggendo dall'area secondaria quando si verifica un problema che interferisce con la lettura dall'area primaria. Quando l'area primaria è nuovamente disponibile, l'applicazione può tornare alla lettura dall'area primaria.

Tenere presente queste considerazioni chiave quando si progetta l'applicazione per la disponibilità e la resilienza usando RA-GRS o RA-GZRS:

  • Una copia di sola lettura dei dati archiviati nell'area primaria viene replicata in modo asincrono in un'area secondaria. Questa replica asincrona significa che la copia di sola lettura nell'area secondaria è infine coerente con i dati nell'area primaria. Il servizio di archiviazione determina la posizione dell'area secondaria.

  • È possibile usare le librerie client di Archiviazione di Azure per eseguire richieste di lettura e aggiornamento rispetto all'endpoint dell'area primaria. Se l'area primaria non è disponibile, è possibile reindirizzare automaticamente le richieste di lettura all'area secondaria. È anche possibile configurare l'app per inviare richieste di lettura direttamente all'area secondaria, se necessario, anche quando l'area primaria è disponibile.

  • Se l'area primaria diventa non disponibile, è possibile avviare il failover di un account. Quando si effettua il failover all'area secondaria, le voci DNS che puntano all'area primaria vengono modificate in modo da puntare all'area secondaria. Dopo aver completato il failover, viene ripristinato l'accesso in scrittura per gli account con archiviazione con ridondanza geografica e RA-GRS. Per altre informazioni, vedere Ripristino di emergenza e failover dell'account di archiviazione.

Uso dei dati coerenti

La soluzione proposta presuppone che sia accettabile restituire dati potenzialmente non aggiornati all'applicazione chiamante. Poiché alla fine i dati nell'area secondaria sono coerenti, è possibile che l'area primaria possa diventare inaccessibile prima che un aggiornamento all'area secondaria abbia terminato la replica.

Si supponga ad esempio che un cliente invii un aggiornamento completato correttamente, ma che l'area primaria diventi non disponibile prima che l'aggiornamento venga propagato all'area secondaria. Quando il cliente chiede di leggere nuovamente i dati, riceve i dati non aggiornati dall'area secondaria anziché i dati aggiornati. Quando si progetta l'applicazione, è necessario decidere se questo comportamento è accettabile. In caso affermativo, è anche necessario considerare come inviare una notifica all'utente.

Più avanti in questo articolo verranno fornite altre informazioni sulla gestione dei dati coerenti e su come controllare la proprietà Last Sync Time per valutare eventuali discrepanze tra i dati nelle aree primarie e secondarie.

Gestione separata o collettiva dei servizi

Sebbene improbabile, è possibile che un servizio (BLOB, code, tabelle o file) diventi non disponibile mentre gli altri servizi sono ancora completamente funzionali. È possibile gestire i tentativi per ogni servizio separatamente oppure è possibile gestire i tentativi genericamente per tutti i servizi di archiviazione insieme.

Ad esempio, se si usano code e BLOB nell'applicazione, è possibile decidere di inserire in codice separato per gestire gli errori riprovabili per ogni servizio. In questo modo, un errore del servizio BLOB influisce solo sulla parte dell'applicazione che gestisce i BLOB, lasciando le code per continuare l'esecuzione come normale. Se, tuttavia, si decide di gestire tutti i tentativi del servizio di archiviazione insieme, le richieste ai servizi BLOB e code saranno interessate se un servizio restituisce un errore riprovabile.

In definitiva, questa decisione dipende dalla complessità dell'applicazione. È consigliabile gestire gli errori in base al servizio per limitare l'impatto dei tentativi. In alternativa, è possibile decidere di reindirizzare le richieste di lettura per tutti i servizi di archiviazione all'area secondaria quando si rileva un problema con qualsiasi servizio di archiviazione nell'area primaria.

Esecuzione dell'applicazione in modalità di sola lettura

Per preparare in modo efficace un'interruzione nell'area primaria, l'applicazione deve essere in grado di gestire sia richieste di lettura non riuscite che richieste di aggiornamento non riuscite. Se l'area primaria ha esito negativo, è possibile reindirizzare le richieste di lettura all'area secondaria. Tuttavia, le richieste di aggiornamento non possono essere reindirizzate perché i dati replicati nell'area secondaria sono di sola lettura. Per questo motivo, è necessario progettare l'applicazione per poter essere eseguita in modalità di sola lettura.

È ad esempio possibile impostare un flag che viene verificato prima che le richieste di aggiornamento siano inviate ad Archiviazione di Azure. Quando viene eseguita una richiesta di aggiornamento, è possibile ignorare la richiesta e restituire una risposta appropriata all'utente. È anche possibile scegliere di disabilitare completamente determinate funzionalità fino a quando il problema non viene risolto e notificare agli utenti che le funzionalità sono temporaneamente non disponibili.

Se si decide di gestire gli errori per ogni servizio separatamente, è necessario gestire anche la possibilità di eseguire l'applicazione in modalità di sola lettura per servizio. Ad esempio, è possibile configurare flag di sola lettura per ogni servizio. È quindi possibile abilitare o disabilitare i flag nel codice, in base alle esigenze.

La possibilità di eseguire l'applicazione in modalità di sola lettura offre anche la possibilità di garantire funzionalità limitate durante un aggiornamento principale dell'applicazione. È possibile impostare l'applicazione in modo che venga eseguita in sola lettura e punti al data center secondario, impedendo così l'accesso ai dati nell'area primaria durante gli aggiornamenti.

Gestione degli aggiornamenti durante l'esecuzione in modalità di sola lettura

Esistono molti modi per gestire le richieste di aggiornamento durante l'esecuzione in modalità di sola lettura. Questa sezione si concentra su alcuni modelli generali da considerare.

  • È possibile rispondere all'utente e avvisarli che le richieste di aggiornamento non vengono attualmente elaborate. Ad esempio, un sistema di gestione dei contatti potrebbe consentire agli utenti di accedere alle informazioni di contatto ma non apportare aggiornamenti.

  • È possibile accodare gli aggiornamenti in un'altra area. In questo caso, si scriverebbero le richieste di aggiornamento in sospeso in una coda in un'area diversa, quindi elaborare tali richieste dopo che il data center primario viene nuovamente online. In questo scenario è necessario informare l'utente che la richiesta di aggiornamento viene accodata per l'elaborazione successiva.

  • È possibile scrivere gli aggiornamenti in un account di archiviazione di un'altra area. Quando l'area primaria torna online, è possibile unire tali aggiornamenti nei dati primari, a seconda della struttura dei dati. Ad esempio, se si creano file separati con un indicatore data/ora nel nome, è possibile copiare tali file nell'area primaria. Questa soluzione può essere applicata ai carichi di lavoro, ad esempio la registrazione e i dati IoT.

Gestione dei tentativi

Le applicazioni che comunicano con i servizi in esecuzione nel cloud devono essere sensibili agli eventi e agli errori non pianificati che possono verificarsi. Questi errori possono essere temporanei o persistenti, che vanno da una perdita momentanea di connettività a un'interruzione significativa a causa di una emergenza naturale. È importante progettare applicazioni cloud con una gestione appropriata dei tentativi per ottimizzare la disponibilità e migliorare la stabilità complessiva dell'applicazione.

Richieste di lettura

Se l'area primaria non è disponibile, è possibile reindirizzare le richieste di lettura all'archiviazione secondaria. Come indicato in precedenza, deve essere accettabile per l'applicazione leggere i dati non aggiornati. La libreria client di Archiviazione di Azure offre opzioni per gestire i tentativi e reindirizzare le richieste di lettura a un'area secondaria.

In questo esempio la gestione dei tentativi per l'archiviazione BLOB è configurata nella BlobClientOptions classe e verrà applicata all'oggetto BlobServiceClient creato usando queste opzioni di configurazione. Questa configurazione è un approccio primario e secondario , in cui i tentativi di richiesta di lettura dall'area primaria vengono reindirizzati all'area secondaria. Questo approccio è ottimale quando gli errori nell'area primaria devono essere temporanei.

string accountName = "<YOURSTORAGEACCOUNTNAME>";
Uri primaryAccountUri = new Uri($"https://{accountName}.blob.core.windows.net/");
Uri secondaryAccountUri = new Uri($"https://{accountName}-secondary.blob.core.windows.net/");

// Provide the client configuration options for connecting to Azure Blob storage
BlobClientOptions blobClientOptions = new BlobClientOptions()
{
    Retry = {
        // The delay between retry attempts for a fixed approach or the delay
        // on which to base calculations for a backoff-based approach
        Delay = TimeSpan.FromSeconds(2),

        // The maximum number of retry attempts before giving up
        MaxRetries = 5,

        // The approach to use for calculating retry delays
        Mode = RetryMode.Exponential,

        // The maximum permissible delay between retry attempts
        MaxDelay = TimeSpan.FromSeconds(10)
    },

    // If the GeoRedundantSecondaryUri property is set, the secondary Uri will be used for 
    // GET or HEAD requests during retries.
    // If the status of the response from the secondary Uri is a 404, then subsequent retries
    // for the request will not use the secondary Uri again, as this indicates that the resource 
    // may not have propagated there yet.
    // Otherwise, subsequent retries will alternate back and forth between primary and secondary Uri.
    GeoRedundantSecondaryUri = secondaryAccountUri
};

// Create a BlobServiceClient object using the configuration options above
BlobServiceClient blobServiceClient = new BlobServiceClient(primaryAccountUri, new DefaultAzureCredential(), blobClientOptions);

Se si determina che l'area primaria potrebbe non essere disponibile per un lungo periodo di tempo, è possibile configurare tutte le richieste di lettura da puntare all'area secondaria. Questa configurazione è un approccio secondario . Come illustrato in precedenza, è necessario una strategia per gestire le richieste di aggiornamento durante questo periodo e un modo per informare gli utenti che vengono elaborate solo le richieste di lettura. In questo esempio viene creata una nuova istanza di BlobServiceClient che usa l'endpoint dell'area secondaria.

string accountName = "<YOURSTORAGEACCOUNTNAME>";
Uri primaryAccountUri = new Uri($"https://{accountName}.blob.core.windows.net/");
Uri secondaryAccountUri = new Uri($"https://{accountName}-secondary.blob.core.windows.net/");

// Create a BlobServiceClient object pointed at the secondary Uri
// Use blobServiceClientSecondary only when issuing read requests, as secondary storage is read-only
BlobServiceClient blobServiceClientSecondary = new BlobServiceClient(secondaryAccountUri, new DefaultAzureCredential(), blobClientOptions);

Sapere quando passare alla modalità di sola lettura e alle richieste secondarie fa parte di un modello di progettazione dell'architettura denominato modello di interruttore, che verrà illustrato in una sezione successiva.

Richieste di aggiornamento

Le richieste di aggiornamento non possono essere reindirizzate all'archiviazione secondaria, che è di sola lettura. Come descritto in precedenza, l'applicazione deve essere in grado di gestire le richieste di aggiornamento quando l'area primaria non è disponibile.

Il modello a interruttore può anche essere applicato alle richieste di aggiornamento. Per gestire gli errori delle richieste di aggiornamento, è possibile impostare una soglia nel codice, ad esempio 10 errori consecutivi e tenere traccia del numero di errori per le richieste all'area primaria. Una volta soddisfatta la soglia, è possibile passare all'applicazione in modalità di sola lettura in modo che le richieste di aggiornamento all'area primaria non vengano più rilasciate.

Come implementare il modello di interruttore

La gestione degli errori che possono richiedere un intervallo di tempo variabile da cui eseguire il ripristino fa parte di un modello di progettazione dell'architettura denominato modello Di interruzione del circuito. L'implementazione corretta di questo modello può impedire a un'applicazione di tentare ripetutamente di eseguire un'operazione che potrebbe non riuscire, migliorando così la stabilità e la resilienza dell'applicazione.

Un aspetto del modello di interruzione circuito è l'identificazione quando si verifica un problema in corso con un endpoint primario. Per effettuare questa determinazione, è possibile monitorare la frequenza con cui il client rileva errori riprovabili. Poiché ogni scenario è diverso, è necessario determinare una soglia appropriata da usare per la decisione di passare all'endpoint secondario ed eseguire l'applicazione in modalità di sola lettura.

Ad esempio, è possibile decidere di eseguire l'opzione se sono presenti 10 errori consecutivi nell'area primaria. È possibile tenere traccia di questa operazione mantenendo un conteggio degli errori nel codice. Se si verifica un esito positivo prima di raggiungere la soglia, impostare il conteggio su zero. Se il conteggio raggiunge la soglia, passare all'applicazione per usare l'area secondaria per le richieste di lettura.

Come approccio alternativo, è possibile decidere di implementare un componente di monitoraggio personalizzato nell'applicazione. Questo componente potrebbe eseguire continuamente il ping dell'endpoint di archiviazione primario con semplici richieste di lettura (ad esempio la lettura di un BLOB di piccole dimensioni) per determinarne l'integrità. Questo approccio richiederebbe alcune risorse, ma non una quantità significativa. Quando viene rilevato un problema che raggiunge la soglia, passare alle richieste di sola lettura secondarie e alla modalità di sola lettura. Per questo scenario, quando si esegue il ping dell'endpoint di archiviazione primario, è possibile tornare all'area primaria e continuare a consentire gli aggiornamenti.

La soglia di errore usata per determinare quando eseguire il passaggio può variare da servizio a servizio all'interno dell'applicazione, pertanto è consigliabile prendere in considerazione la possibilità di renderli parametri configurabili.

È necessario considerare anche come gestire più istanze di un'applicazione e come procedere quando si rilevano errori non irreversibili in ogni istanza. È ad esempio possibile avere in esecuzione 20 macchine virtuali nelle quali è caricata la stessa applicazione. Ogni istanza viene gestita separatamente? Se un'istanza inizia a riscontrare problemi, limitare la risposta a una sola istanza? Oppure si vuole che tutte le istanze rispondano nello stesso modo quando un'istanza presenta un problema? La gestione delle istanze separatamente è molto più semplice rispetto al tentativo di coordinare la risposta tra di esse, ma l'approccio dipende dall'architettura dell'applicazione.

Gestione di dati con coerenza finale

L'archiviazione con ridondanza geografica funziona replicando le transazioni dal database primario all'area secondaria. Il processo di replica garantisce che i dati nell'area secondaria siano infine coerenti. Ciò significa che tutte le transazioni nell'area primaria verranno eventualmente visualizzate nell'area secondaria, ma potrebbero verificarsi un ritardo prima che vengano visualizzate. Non esiste anche alcuna garanzia che le transazioni arrivino nell'area secondaria nello stesso ordine in cui sono state originariamente applicate nell'area primaria. Se le transazioni non giungono nell'area secondaria nell'ordine originario, è possibile che i dati nell'area secondaria siano incoerenti finché il servizio non si allinea.

L'esempio seguente per l'archiviazione tabelle di Azure mostra cosa potrebbe accadere quando si aggiornano i dettagli di un dipendente per renderli membri del ruolo di amministratore. Ai fini di questo esempio è necessario aggiornare l'entità employee e aggiornare un'entità administrator role con un conteggio del numero totale di amministratori. Si noti il modo in cui gli aggiornamenti vengono applicati in un ordine diverso nell'area secondaria.

Time Transazione Replica Ora ultima sincronizzazione Risultato
T0 Transazione A:
Inserimento dell'entità
employee nell'area primaria
Transazione A inserita nell'area primaria,
non ancora replicata.
T1 Transazione A
replicata
nell'area secondaria
T1 Transazione A replicata nell'area secondaria.
Ora ultima sincronizzazione aggiornata.
T2 Transazione B:
Aggiornamento
dell'entità employee
nell'area primaria
T1 Transazione B scritta nell'area primaria,
non ancora replicata.
T3 Transazione C:
Aggiornamento
entità
administrator role nell'area
primaria
T1 Transazione C scritta nell'area primaria,
non ancora replicata.
T4 Transazione C
replicata
nell'area secondaria
T1 Transazione C replicata nell'area secondaria.
LastSyncTime non aggiornato perché
la transazione B non è stata ancora replicata.
T5 Lettura delle entità
dall'area secondaria
T1 Si ottiene un valore non aggiornato per l'entità
employee perché la transazione B
non è stata ancora replicata. Si ottiene il nuovo valore per
l'entità administrator role perché C è stata
replicata. Ora ultima sincronizzazione non ancora
aggiornata perché la transazione B
non è stata replicata. È possibile stabilire che
l'entità administrator role è incoerente
perché la data/ora dell'entità è successiva
all'ora dell'ultima sincronizzazione.
T6 Transazione B
replicata
nell'area secondaria
T6 T6: tutte le transazioni fino alla C sono
state replicate, ora ultima sincronizzazione
aggiornata.

In questo esempio presupporre che il client passi alla lettura dell'area secondaria in corrispondenza di T5. È possibile leggere l'entità ruolo amministratore al momento, ma l'entità contiene un valore per il conteggio degli amministratori che non è coerente con il numero di entità dipendente contrassegnate come amministratori nell'area secondaria in questo momento. Il client potrebbe visualizzare questo valore, con il rischio che le informazioni siano incoerenti. In alternativa, il client può determinare che administrator role si trova in uno stato potenzialmente incoerente perché gli aggiornamenti non hanno seguito l'ordine e informare l'utente.

Per determinare se un account di archiviazione contiene dati potenzialmente incoerenti, il client può controllare il valore della proprietà Ora ultima sincronizzazione . Ora ultima sincronizzazione indica l'ora dell'ultima coerenza dei dati nell'area secondaria e dell'applicazione di tutte le transazioni prima di tale momento. Nell'esempio illustrato in precedenza, dopo che il servizio ha inserito l'entità employee nell'area secondaria l'ora dell'ultima sincronizzazione viene impostata su T1. Rimane in T1 fino a quando il servizio aggiorna l'entità dipendente nell'area secondaria quando è impostata su T6. Se recupera l'ora dell'ultima sincronizzazione quando legge l'entità su T5, il client può confrontare questo dato con il timestamp dell'entità. Se il timestamp dell'entità è successivo all'ora dell'ultima sincronizzazione, l'entità si trova in uno stato potenzialmente incoerente ed è possibile eseguire l'azione appropriata. Per usare questo campo è necessario sapere quando è stato completato l'ultimo aggiornamento nell'area primaria.

Per informazioni su come controllare l'ora dell'ultima sincronizzazione, vedere Controllare la proprietà Ora ultima sincronizzazione per un account di archiviazione.

Test

È importante verificare che l'applicazione si comporti come previsto quando si verificano errori non irreversibili. Ad esempio, è necessario verificare che l'applicazione passi all'area secondaria quando rileva un problema e quindi torna quando l'area primaria diventa nuovamente disponibile. Per testare correttamente questo comportamento, è necessario un modo per simulare errori riprovabili e controllare la frequenza con cui si verificano.

Un'opzione consiste nell'usare Fiddler per intercettare e modificare le risposte HTTP in uno script. Questo script può identificare le risposte provenienti dall'endpoint primario e modificare il codice di stato HTTP in uno che la libreria client di archiviazione riconosce come errore riprovabile. Questo frammento di codice descrive un esempio semplice di script Fiddler che intercetta le risposte alle richieste di lettura sulla tabella employeedata per restituire uno stato 502:

static function OnBeforeResponse(oSession: Session) {
    ...
    if ((oSession.hostname == "\[YOURSTORAGEACCOUNTNAME\].table.core.windows.net")
      && (oSession.PathAndQuery.StartsWith("/employeedata?$filter"))) {
        oSession.responseCode = 502;
    }
}

È possibile estendere questo esempio per intercettare una gamma più ampia di richieste e modificare responseCode solo in alcune di esse per simulare meglio uno scenario reale. Per altre informazioni sulla personalizzazione degli script Fiddler, vedere Modifying a Request or Response (Modifica di una richiesta o risposta) nella documentazione di Fiddler.

Se sono state configurate soglie configurabili per il passaggio dell'applicazione in sola lettura, sarà più semplice testare il comportamento con volumi di transazioni non di produzione.


Passaggi successivi

Per un esempio completo che illustra come eseguire il passaggio avanti e indietro tra gli endpoint primari e secondari, vedere Esempi di Azure - Uso del modello di interruttore con archiviazione RA-GRS.