Condividi tramite


Progettazione di applicazioni di carichi di lavoro cruciali in Azure

Quando si progetta un'applicazione, i requisiti dell'applicazione funzionale e non funzionale sono fondamentali. Questa area di progettazione descrive i modelli di architettura e le strategie di ridimensionamento che consentono di rendere resiliente l'applicazione agli errori.

Importante

Questo articolo fa parte della serie di carichi di lavoro cruciali di Azure Well-Architected Framework. Se non si ha familiarità con questa serie, è consigliabile iniziare con Che cos'è un carico di lavoro cruciale?.

Architettura delle unità di scala

Tutti gli aspetti funzionali di una soluzione devono essere in grado di ridimensionare per soddisfare le variazioni della domanda, idealmente la scalabilità automatica in risposta al carico. È consigliabile usare un'architettura di unità di scala per ottimizzare la scalabilità end-to-end tramite raggruppamento e standardizzare anche il processo di aggiunta e rimozione della capacità. Un'unità di scala è un'unità logica o una funzione che può essere ridimensionata in modo indipendente. Un'unità può essere costituita da componenti di codice, piattaforme di hosting di applicazioni, stamp di distribuzione che coprono i componenti correlati e persino sottoscrizioni per supportare i requisiti multi-tenant.

È consigliabile questo approccio perché risolve i limiti di scalabilità delle singole risorse e dell'intera applicazione. Consente di gestire scenari di distribuzione e aggiornamento complessi perché un'unità di scala può essere distribuita come un'unità. Inoltre, è possibile testare e convalidare versioni specifiche dei componenti in un'unità prima di indirizzare il traffico utente a esso.

Si supponga che l'applicazione mission-critical sia un catalogo di prodotti online. Ha un flusso utente per l'elaborazione di commenti e valutazioni del prodotto. Il flusso usa le API per recuperare e pubblicare commenti e classificazioni e componenti di supporto come un endpoint OAuth, un archivio dati e code di messaggi. Gli endpoint API senza stato rappresentano unità funzionali granulari che devono adattarsi alle modifiche su richiesta. Anche la piattaforma dell'applicazione sottostante deve essere in grado di ridimensionare di conseguenza. Per evitare colli di bottiglia delle prestazioni, anche i componenti e le dipendenze downstream devono essere ridimensionati in modo appropriato. Possono essere ridimensionati in modo indipendente, come unità di scala separate o insieme, come parte di una singola unità logica.

Unità di scala di esempio

L'immagine seguente mostra i possibili ambiti per le unità di scala. Gli ambiti sono compresi tra pod di microservizi e nodi del cluster e indicatori di distribuzione a livello di area.

Diagramma che mostra più ambiti per le unità di scala.

Considerazioni relative alla progettazione

  • Ambito. L'ambito di un'unità di scala, la relazione tra le unità di scala e i relativi componenti devono essere definiti in base a un modello di capacità. Prendere in considerazione i requisiti non funzionali per le prestazioni.

  • Limiti di scalabilità. I limiti e le quote di scalabilità delle sottoscrizioni di Azure possono avere un impatto sulla progettazione delle applicazioni, sulle scelte tecnologiche e sulla definizione di unità di scala. Le unità di scala consentono di ignorare i limiti di scalabilità di un servizio. Ad esempio, se un cluster del servizio Azure Kubernetes in un'unità può avere solo 1.000 nodi, è possibile usare due unità per aumentare tale limite a 2.000 nodi.

  • Caricamento previsto. Usare il numero di richieste per ogni flusso utente, la frequenza massima prevista delle richieste (richieste al secondo) e i modelli di traffico giornalieri/settimanali/stagionali per informare i requisiti di scalabilità principali. Inoltre, tenere conto dei modelli di crescita previsti sia per il traffico che per il volume di dati.

  • Prestazioni ridotte accettabili. Determinare se un servizio danneggiato con tempi di risposta elevati è accettabile durante il caricamento. Quando si modella la capacità necessaria, le prestazioni richieste della soluzione sotto carico sono un fattore critico.

  • Requisiti non funzionali. Gli scenari tecnici e aziendali hanno considerazioni distinte per resilienza, disponibilità, latenza, capacità e osservabilità. Analizzare questi requisiti nel contesto dei flussi utente end-to-end principali. Si avrà una flessibilità relativa nella progettazione, nel processo decisionale e nelle scelte tecnologico a livello di flusso utente.

Suggerimenti per la progettazione

  • Definire l'ambito di un'unità di scala e i limiti che attiveranno la scalabilità dell'unità.

  • Assicurarsi che tutti i componenti dell'applicazione possano essere ridimensionati in modo indipendente o come parte di un'unità di scala che include altri componenti correlati.

  • Definire la relazione tra unità di scala, in base a un modello di capacità e a requisiti non funzionali.

  • Definire un timbro di distribuzione a livello di area per unificare il provisioning, la gestione e il funzionamento delle risorse dell'applicazione a livello di area in un'unità di scala eterogenea ma interdipendente. Con l'aumentare del carico, è possibile distribuire indicatori aggiuntivi, all'interno della stessa area di Azure o di quelli diversi, per ridimensionare orizzontalmente la soluzione.

  • Usare una sottoscrizione di Azure come unità di scala in modo che i limiti di scalabilità all'interno di una singola sottoscrizione non vincolano la scalabilità. Questo approccio si applica agli scenari di applicazioni su larga scala con un volume di traffico significativo.

  • Modellare la capacità necessaria in base ai modelli di traffico identificati per assicurarsi che venga effettuato il provisioning di capacità sufficiente nei momenti di picco per evitare la riduzione delle prestazioni del servizio. In alternativa, ottimizzare la capacità durante le ore di minore attività.

  • Misurare il tempo necessario per eseguire operazioni di scalabilità orizzontale e scalabilità orizzontale per garantire che le variazioni naturali del traffico non creino un livello inaccettabile di riduzione del servizio. Tenere traccia delle durate dell'operazione di scalabilità come metrica operativa.

Nota

Quando si esegue la distribuzione in una zona di destinazione di Azure, assicurarsi che la sottoscrizione della zona di destinazione sia dedicata all'applicazione per fornire un limite di gestione chiaro ed evitare l'antipattern Noisy Neighbor.

Distribuzione globale

È impossibile evitare errori in qualsiasi ambiente altamente distribuito. In questa sezione vengono fornite strategie per attenuare molti scenari di errore. L'applicazione deve essere in grado di resistere a errori regionali e di zona. Deve essere distribuito in un modello attivo/attivo in modo che il carico venga distribuito tra tutte le aree.

Guardare questo video per una panoramica di come pianificare gli errori nelle applicazioni cruciali e ottimizzare la resilienza:

Considerazioni relative alla progettazione

  • Ridondanza. L'applicazione deve essere distribuita in più aree. Inoltre, all'interno di un'area, è consigliabile usare le zone di disponibilità per consentire la tolleranza di errore a livello di data center. Le zone di disponibilità hanno un perimetro di latenza inferiore a 2 millisecondi tra le zone di disponibilità. Per i carichi di lavoro "chatty" tra zone, questa latenza può comportare una riduzione delle prestazioni per il trasferimento dei dati tra zone.

  • Modello attivo/attivo. È consigliabile una strategia di distribuzione attiva/attiva perché ottimizza la disponibilità e offre un contratto di servizio composito più elevato. Tuttavia, può presentare problemi relativi alla sincronizzazione e alla coerenza dei dati per molti scenari dell'applicazione. Affrontare le sfide a livello di piattaforma dati considerando i compromessi dell'aumento dei costi e delle attività di progettazione.

    Una distribuzione attiva/attiva in più provider di servizi cloud è un modo per ridurre potenzialmente la dipendenza dalle risorse globali all'interno di un singolo provider di servizi cloud. Tuttavia, una strategia di distribuzione attiva/attiva multicloud introduce una notevole complessità per CI/CD. Inoltre, considerando le differenze tra le specifiche e le funzionalità delle risorse tra i provider di servizi cloud, sono necessari indicatori di distribuzione specializzati per ogni cloud.

  • Distribuzione geografica. Il carico di lavoro potrebbe avere requisiti di conformità per la residenza geografica dei dati, la protezione dei dati e la conservazione dei dati. Valutare se sono presenti aree specifiche in cui devono risiedere i dati o dove devono essere distribuite le risorse.

  • Richiedere l'origine. La prossimità geografica e la densità degli utenti o dei sistemi dipendenti devono informare le decisioni di progettazione sulla distribuzione globale.

  • Connettività. La modalità di accesso al carico di lavoro da parte di utenti o sistemi esterni influirà sulla progettazione. Valutare se l'applicazione è disponibile tramite la rete Internet pubblica o le reti private che usano circuiti VPN o Azure ExpressRoute.

Per consigli di progettazione e scelte di configurazione a livello di piattaforma, vedere Piattaforma dell'applicazione: Distribuzione globale.

Architettura basata su eventi ad accoppiamento libero

L'accoppiamento consente la comunicazione tra servizi tramite interfacce ben definite. Un accoppiamento libero consente a un componente dell'applicazione di operare in modo indipendente. Uno stile di architettura di microservizi è coerente con i requisiti cruciali. Facilita la disponibilità elevata impedendo errori a catena.

Per l'accoppiamento libero, è consigliabile incorporare la progettazione basata su eventi. L'elaborazione asincrona dei messaggi tramite un intermediario può creare resilienza.

Diagramma che illustra la comunicazione asincrona basata su eventi.

In alcuni scenari, le applicazioni possono combinare accoppiamenti separati e stretti, a seconda degli obiettivi aziendali.

Considerazioni relative alla progettazione

  • Dipendenze di runtime. I servizi ad accoppiamento libero non devono essere vincolati all'uso della stessa piattaforma di calcolo, del linguaggio di programmazione, del runtime o del sistema operativo.

  • Ridimensionamento. I servizi devono essere in grado di ridimensionare in modo indipendente. Ottimizzare l'uso delle risorse dell'infrastruttura e della piattaforma.

  • Tolleranza di errore. Gli errori devono essere gestiti separatamente e non devono influire sulle transazioni client.

  • Integrità transazionale. Prendere in considerazione l'effetto della creazione e della persistenza dei dati che si verifica in servizi separati.

  • Traccia distribuita. La traccia end-to-end potrebbe richiedere un'orchestrazione complessa.

Suggerimenti per la progettazione

  • Allineare i limiti dei microservizi ai flussi utente critici.

  • Usare la comunicazione asincrona guidata dagli eventi, se possibile, per supportare scalabilità sostenibile e prestazioni ottimali.

  • Usare modelli come Outbox e Sessione transazionale per garantire la coerenza in modo che ogni messaggio venga elaborato correttamente.

Esempio: approccio basato su eventi

L'implementazione di riferimento Mission-Critical Online usa microservizi per elaborare una singola transazione aziendale. Applica le operazioni di scrittura in modo asincrono con un broker di messaggi e un ruolo di lavoro. Le operazioni di lettura sono sincrone, con il risultato restituito direttamente al chiamante.

Diagramma che mostra la comunicazione basata su eventi.

Modelli di resilienza e gestione degli errori nel codice dell'applicazione

Un'applicazione mission-critical deve essere progettata per essere resiliente in modo che affronti il maggior numero possibile di scenari di errore. Questa resilienza ottimizza la disponibilità e l'affidabilità del servizio. L'applicazione deve avere funzionalità di riparazione automatica, che è possibile implementare usando modelli di progettazione come tentativi con backoff e interruttore.

Per gli errori non temporanei che non è possibile attenuare completamente nella logica dell'applicazione, il modello di integrità e i wrapper operativi devono eseguire azioni correttive. Il codice dell'applicazione deve incorporare la strumentazione e la registrazione appropriate per informare il modello di integrità e facilitare la successiva risoluzione dei problemi o l'analisi della causa radice in base alle esigenze. È necessario implementare la traccia distribuita per fornire al chiamante un messaggio di errore completo che include un ID di correlazione quando si verifica un errore.

Strumenti come Application Insights consentono di eseguire query, correlare e visualizzare le tracce dell'applicazione.

Considerazioni relative alla progettazione

  • Configurazioni appropriate. Non è raro che i problemi temporanei causi errori a catena. Ad esempio, riprovare senza back-off appropriato esacerbato il problema quando un servizio viene limitato. È possibile ritardare i tentativi di spazio in modo lineare o aumentarli in modo esponenziale per tornare indietro attraverso ritardi crescenti.

  • Endpoint di integrità. È possibile esporre controlli funzionali all'interno del codice dell'applicazione usando gli endpoint di integrità che le soluzioni esterne possono eseguire il polling per recuperare lo stato di integrità dei componenti dell'applicazione.

Suggerimenti per la progettazione

Ecco alcuni modelli comuni di progettazione software per applicazioni resilienti:

Modello Riepilogo
Livellamento del carico basato sulle code Introduce un buffer tra i consumer e le risorse richieste per garantire livelli di carico coerenti. Quando le richieste dei consumer vengono accodate, un processo di lavoro li gestisce sulla risorsa richiesta a un ritmo impostato dal ruolo di lavoro e dalla capacità della risorsa richiesta di elaborare le richieste. Se i consumer si aspettano risposte alle richieste, è necessario implementare un meccanismo di risposta separato. Applicare un ordine con priorità in modo che le attività più importanti vengano eseguite per prime.
Interruttore Fornisce stabilità attendendo il ripristino o rifiutando rapidamente le richieste anziché bloccando l'attesa di un servizio remoto o di una risorsa non disponibile. Questo modello gestisce anche gli errori che potrebbero richiedere una variabile quantità di tempo per il ripristino da quando viene stabilita una connessione a un servizio remoto o a una risorsa.
A scomparti Tenta di partizionare le istanze del servizio in gruppi in base ai requisiti di carico e disponibilità, isolando gli errori per sostenere la funzionalità del servizio.
Saga Gestisce la coerenza dei dati tra microservizi che dispongono di archivi dati indipendenti assicurandosi che i servizi si aggiornino tra loro tramite canali di eventi o messaggi definiti. Ogni servizio esegue transazioni locali per aggiornare il proprio stato e pubblica un evento per attivare la successiva transazione locale nella saga. Se un aggiornamento del servizio non riesce, la saga esegue transazioni di compensazione per contrastare i passaggi di aggiornamento del servizio precedenti. I singoli passaggi di aggiornamento del servizio possono implementare modelli di resilienza, ad esempio riprovare.
Monitoraggio endpoint di integrità Implementa controlli funzionali in un'applicazione a cui gli strumenti esterni possono accedere tramite endpoint esposti a intervalli regolari. È possibile interpretare le risposte dagli endpoint usando le metriche operative chiave per informare l'integrità dell'applicazione e attivare risposte operative, ad esempio la generazione di un avviso o l'esecuzione di una distribuzione di rollback di compensazione.
Riprova Gestisce gli errori temporanei in modo elegante e trasparente.
- Annulla se è improbabile che l'errore sia temporaneo ed è improbabile che abbia esito positivo se l'operazione viene tentata di nuovo.
- Riprovare se l'errore è insolito o raro e è probabile che l'operazione abbia esito positivo se si tenta di ripetere immediatamente il tentativo.
- Riprovare dopo un ritardo se l'errore è causato da una condizione che potrebbe richiedere un breve tempo per il ripristino, ad esempio connettività di rete o errori di carico elevato. Applicare una strategia di back-off appropriata man mano che aumentano i ritardi di ripetizione dei tentativi.
Limitazione Controlla l'utilizzo delle risorse usate dai componenti dell'applicazione, proteggendole dal sovraccarico. Quando una risorsa raggiunge una soglia di carico, rinvia le operazioni con priorità inferiore e riduce le funzionalità non essenziali in modo che le funzionalità essenziali possano continuare fino a quando non sono disponibili risorse sufficienti per tornare al normale funzionamento.

Ecco alcune raccomandazioni aggiuntive:

  • Usare sdk forniti dal fornitore, come gli SDK di Azure, per connettersi ai servizi dipendenti. Usare funzionalità di resilienza predefinite anziché implementare funzionalità personalizzate.

  • Applicare una strategia di back-off appropriata quando si ritentano chiamate di dipendenza non riuscite per evitare uno scenario DDoS autoflitto.

  • Definire criteri di progettazione comuni per tutti i team di microservizi dell'applicazione per favorire la coerenza e la velocità nell'uso di modelli di resilienza a livello di applicazione.

  • Implementare modelli di resilienza usando pacchetti standardizzati collaudati, ad esempio Polly per C# o Sentinel per Java.

  • Usare gli ID di correlazione per tutti gli eventi di traccia e registrare i messaggi per collegarli a una determinata richiesta. Restituisce gli ID di correlazione al chiamante per tutte le chiamate, non solo le richieste non riuscite.

  • Usare la registrazione strutturata per tutti i messaggi di log. Selezionare un sink di dati operativi unificato per le tracce, le metriche e i log dell'applicazione per consentire agli operatori di eseguire facilmente il debug dei problemi. Per altre informazioni, vedere Raccogliere, aggregare e archiviare i dati di monitoraggio per le applicazioni cloud.

  • Assicurarsi che i dati operativi vengano usati insieme ai requisiti aziendali per informare un modello di integrità dell'applicazione.

Selezione del linguaggio di programmazione

È importante selezionare i framework e i linguaggi di programmazione corretti. Queste decisioni sono spesso guidate dai set di competenze o dalle tecnologie standardizzate nell'organizzazione. Tuttavia, è essenziale valutare le prestazioni, la resilienza e le funzionalità complessive di vari linguaggi e framework.

Considerazioni relative alla progettazione

  • Funzionalità del kit di sviluppo. Esistono differenze nelle funzionalità offerte dagli SDK dei servizi di Azure in diversi linguaggi. Queste differenze possono influire sulla scelta di un servizio o di un linguaggio di programmazione di Azure. Ad esempio, se Azure Cosmos DB è un'opzione fattibile, Go potrebbe non essere un linguaggio di sviluppo appropriato perché non esiste un SDK di prima parte.

  • Aggiornamenti delle funzionalità. Considerare la frequenza con cui l'SDK viene aggiornato con nuove funzionalità per la lingua selezionata. Gli SDK di uso comune, ad esempio le librerie .NET e Java, vengono aggiornati di frequente. Altri SDK o SDK per altri linguaggi potrebbero essere aggiornati meno frequentemente.

  • Più linguaggi di programmazione o framework. È possibile usare più tecnologie per supportare vari carichi di lavoro compositi. Tuttavia, è consigliabile evitare lo sprawl perché introduce complessità di gestione e sfide operative.

  • Opzione di calcolo. Il software legacy o proprietario potrebbe non essere eseguito nei servizi PaaS. Inoltre, potrebbe non essere possibile includere software legacy o proprietario nei contenitori.

Suggerimenti per la progettazione

  • Valutare tutti gli SDK di Azure pertinenti per le funzionalità necessarie e i linguaggi di programmazione scelti. Verificare l'allineamento con i requisiti non funzionali.

  • Ottimizzare la selezione di linguaggi e framework di programmazione a livello di microservizio. Usare più tecnologie in base alle esigenze.

  • Classificare in ordine di priorità .NET SDK per ottimizzare l'affidabilità e le prestazioni. Gli SDK di Azure .NET offrono in genere più funzionalità e vengono aggiornati di frequente.

Passaggio successivo

Esaminare le considerazioni per la piattaforma dell'applicazione.