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 hai familiarità con questa serie, è consigliabile iniziare con Che cos'è un carico di lavoro critico per missione?.
Architettura delle unità di scala
Tutti gli aspetti funzionali di una soluzione devono essere scalabili per soddisfare le variazioni della domanda, idealmente con autoscalabilità 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 composta da componenti di codice, piattaforme di hosting di applicazioni, timbri di distribuzione che riguardano 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 utilizza le API per recuperare e pubblicare commenti e valutazioni, e componenti di supporto come un endpoint OAuth, un database e code di messaggistica. Gli endpoint API senza stato rappresentano unità funzionali granulari che devono adattarsi alle modifiche su richiesta. Anche la piattaforma sottostante dell'applicazione deve essere in grado di scalare 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 spaziano dai pod di microservizi ai nodi del cluster e agli stampi di distribuzione regionale.
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 scala I limiti e le quote di sottoscrizione di Azure possono influire sulla progettazione delle applicazioni, sulle scelte tecnologiche e sulla definizione delle 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 degradate accettabili. Determinare se un servizio degradato con tempi di risposta elevati è accettabile sotto carico. 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 regionale per unificare il provisioning, la gestione e il funzionamento delle risorse applicative regionali in un'unità di scala eterogenea ma interdipendente. Con l'aumentare del carico, è possibile distribuire istanze aggiuntive, all'interno della stessa area di Azure o in aree diverse, per scalare 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 riduzione della scalabilità per garantire che le variazioni naturali del traffico non creino un livello inaccettabile di riduzione del servizio. Monitorare le durate delle operazioni di scala come metrica operativa.
Nota
Quando distribuisci in una zona di destinazione di Azure, assicurati che l'abbonamento della zona di destinazione sia dedicato all'applicazione, per fornire un confine gestionale 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, consigliamo vivamente di usare le zone di disponibilità per consentire la tolleranza di errore a livello di datacenter. Le zone di disponibilità hanno un perimetro di latenza inferiore a 2 millisecondi tra le zone di disponibilità. Per i carichi di lavoro "loquaci" tra zone, questa latenza può comportare una penalizzazione delle prestazioni per il trasferimento interzonale dei dati.
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à nelle operazioni di 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.
Origine della richiesta 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 di importanza critica. Facilita la disponibilità elevata impedendo errori a catena.
Per l'accoppiamento libero, consigliamo vivamente di incorporare la progettazione architetturale 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 scalare 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.
Tracciamento distribuito. 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.
Usa modelli come Outbox e Sessione transazionale per garantire la coerenza, assicurando che ogni messaggio venga elaborato correttamente.
Esempio: approccio basato su eventi
L'implementazione di riferimento per Mission-Critical Online utilizza microservizi per elaborare una singola transazione aziendale. Applica le operazioni di scrittura in modo asincrono con un broker di messaggi e un worker. Le operazioni di lettura sono sincrone, con il risultato restituito direttamente al chiamante.
Diagramma della 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 auto-riparazione, che possono essere implementate usando modelli di progettazione come tentativi con backoff e Circuit Breaker.
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 tracciabilità distribuita per fornire al chiamante un messaggio di errore completo contenente un ID di correlazione in caso di errore.
Strumenti come Application Insights consentono di eseguire query, correlare e visualizzare le tracce dell'applicazione.
Considerazioni relative alla progettazione
Configurazioni adeguate. Non è raro che i problemi temporanei causi errori a catena. Ad esempio, riprovare senza un adeguato intervallo esacerba il problema quando un servizio viene limitato. È possibile distribuire i ritardi dei tentativi in modo lineare o aumentarli in modo esponenziale per gestire i ritardi in crescita.
Endpoint di salute. È possibile esporre controlli funzionali all'interno del codice dell'applicazione utilizzando endpoint di integrità che le soluzioni esterne possono interrogare per recuperare lo stato di integrità dei componenti dell'applicazione.
Suggerimenti per la progettazione
Ecco alcuni pattern comuni di ingegneria del software per applicazioni resilienti:
Modello | Riepilogo |
---|---|
Bilanciamento del carico basato su coda | Introduce un buffer tra i consumatori e le risorse richieste per assicurare livelli di carico coerenti. Quando le richieste dei consumatori vengono accodate, un processo lavoratore le gestisce sulla risorsa richiesta a un ritmo stabilito dal processo stesso 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 di circuito | 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. |
Paratia | 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 controbilanciare i passaggi di aggiornamento del servizio precedenti. I singoli passaggi di aggiornamento del servizio possono implementare modelli di resilienza, ad esempio riprovare. |
Monitoraggio degli endpoint di integrità del sistema | 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 arretramento appropriata man mano che aumentano i ritardi 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.
È consigliabile implementare modelli di resilienza usando pacchetti standardizzati collaudati, ad esempio Polly per C# o Sentinel per Java. Inoltre, i framework di messaggistica come NServiceBus o MassTransit offrono funzionalità di resilienza predefinite, che consentono di evitare la necessità di codice di affidabilità aggiuntivo.
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 ulteriori informazioni, vedere Raccogliere, aggregare e archiviare i dati di monitoraggio per applicazioni cloud.
Assicurarsi che i dati operativi vengano utilizzati insieme ai requisiti aziendali per orientare un modello di salute 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
Le 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.
Molti 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.