Considerazioni sulla progettazione delle applicazioni per carichi di lavoro cruciali
L'architettura di riferimento mission-critical di base usa una semplice applicazione del catalogo online per illustrare un carico di lavoro altamente affidabile. Gli utenti possono esplorare un catalogo di elementi, esaminare i dettagli dell'elemento e pubblicare valutazioni e commenti per gli elementi. Questo articolo è incentrato sugli aspetti relativi all'affidabilità e alla resilienza di un'applicazione cruciale, ad esempio l'elaborazione asincrona delle richieste e il modo in cui ottenere una velocità effettiva elevata all'interno di una soluzione.
Importante
Implementazione di riferimento di livello di produzione che illustra lo sviluppo di applicazioni cruciali su supporto tecnico di Azure le linee guida contenute in questo articolo. È possibile usare questa implementazione come base per un ulteriore sviluppo di soluzioni nel primo passaggio verso la produzione.
Composizione dell'applicazione
Per le applicazioni cruciali su larga scala, è necessario ottimizzare l'architettura per la scalabilità end-to-end e la resilienza. È possibile separare i componenti in unità funzionali che possono funzionare in modo indipendente. Applicare questa separazione a tutti i livelli nello stack di applicazioni in modo che ogni parte del sistema possa essere ridimensionata in modo indipendente e soddisfare le variazioni della domanda. L'implementazione illustra questo approccio.
L'applicazione usa endpoint API senza stato che separano in modo asincrono le richieste di scrittura a esecuzione prolungata tramite un broker di messaggistica. La composizione del carico di lavoro consente di eliminare e ricreare interi cluster servizio Azure Kubernetes (servizio Azure Kubernetes) e altre dipendenze nel timbro in qualsiasi momento. I componenti principali dell'applicazione sono:
Interfaccia utente: un'applicazione Web a pagina singola a cui gli utenti possono accedere. L'interfaccia utente è ospitata nell'hosting di siti Web statici di un account Archiviazione di Azure.
API (
CatalogService
): API REST chiamata dall'applicazione dell'interfaccia utente, ma ancora disponibile per altre potenziali applicazioni client.Ruolo di lavoro (
BackgroundProcessor
): ruolo di lavoro in background che resta in ascolto di nuovi eventi nel bus di messaggi ed elabora le richieste di scrittura nel database. Questo componente non espone alcuna API.API del servizio integrità (
HealthService
): UN'API che segnala l'integrità dell'applicazione controllando se i componenti critici funzionano, ad esempio il database o il bus di messaggistica.
Il carico di lavoro è costituito dall'API, dal ruolo di lavoro e dalle applicazioni di controllo dell'integrità. Uno spazio dei nomi del servizio Azure Kubernetes dedicato denominato workload
ospita il carico di lavoro come contenitori. Non si verifica alcuna comunicazione diretta tra i pod. I pod sono senza stato e possono essere ridimensionati in modo indipendente.
Altri componenti di supporto eseguiti nel cluster includono:
Un controller di ingresso NGINX: instrada le richieste in ingresso al carico di lavoro e il bilanciamento del carico tra i pod. Il controller di ingresso NGINX viene esposto tramite Azure Load Balancer con un indirizzo IP pubblico, ma è accessibile solo tramite Frontdoor di Azure.
Gestione certificati: i certificati TLS (Transport Layer Security) di
cert-manager
Jetstack vengono automaticamente usata usando Let's Encrypt for the ingress rules (Let's Encrypt for the ingress rules).Driver CSI dell'archivio segreti: il provider di Azure Key Vault per il driver CSI dell'archivio segreti legge in modo sicuro i segreti, ad esempio stringa di connessione da Key Vault.
Agente di monitoraggio: la configurazione predefinita omsAgentForLinux viene modificata per ridurre la quantità di dati di monitoraggio inviati all'area di lavoro Log di Monitoraggio di Azure.
Connessione del database
A causa della natura temporanea dei francobolli di distribuzione, evitare di rendere persistente lo stato all'interno del timbro il più possibile. È consigliabile mantenere lo stato in un archivio dati esternato. Per supportare l'obiettivo SLO (Reliability Service Level Objective), creare un archivio dati resiliente. È consigliabile usare soluzioni PaaS (Platform as a Service), gestite o di piattaforma distribuita in combinazione con librerie SDK native che gestiscono automaticamente timeout, disconnessioni e altri stati di errore.
Nell'implementazione di riferimento, Azure Cosmos DB funge da archivio dati principale per l'applicazione. Azure Cosmos DB fornisce scritture in più aree. Ogni stamp può scrivere nella replica di Azure Cosmos DB nella stessa area e Azure Cosmos DB gestisce internamente la replica e la sincronizzazione dei dati tra aree. Azure Cosmos DB per NoSQL supporta tutte le funzionalità del motore di database.
Per maggiori informazioni, consultare la sezione Piattaforma dati per carichi di lavoro cruciali.
Nota
Usare Azure Cosmos DB per NoSQL per le nuove applicazioni. Per le applicazioni legacy che usano un altro protocollo NoSQL, valutare il percorso di migrazione ad Azure Cosmos DB.
Per le applicazioni cruciali che assegnano priorità alla disponibilità rispetto alle prestazioni, è consigliabile eseguire operazioni di scrittura in un'area singola e in più aree con un livello di coerenza elevato.
Questa architettura usa Archiviazione per archiviare temporaneamente lo stato nel timbro per Hub eventi di Azure checkpoint.
Tutti i componenti del carico di lavoro usano Azure Cosmos DB .NET Core SDK per comunicare con il database. L'SDK include una logica affidabile per gestire le connessioni al database e gestire gli errori. Le impostazioni di configurazione principali includono:
Modalità di connettività diretta: questa impostazione è un'impostazione predefinita per .NET SDK v3 perché offre prestazioni migliori. La modalità di connettività diretta ha meno hop di rete rispetto alla modalità gateway, che usa HTTP.
Restituisce la risposta al contenuto in fase di scrittura: questo approccio è disabilitato in modo che il client Azure Cosmos DB non possa restituire il documento dalle operazioni di creazione, upsert e sostituzione e sostituzione, riducendo il traffico di rete. Un'ulteriore elaborazione nel client non richiede questa impostazione.
Serializzazione personalizzata: questo processo imposta i criteri di denominazione delle proprietà JSON per
JsonNamingPolicy.CamelCase
convertire le proprietà .NET in proprietà JSON standard. Può anche convertire le proprietà JSON in proprietà .NET. La condizione di ignora predefinita ignora le proprietà con valori Null, ad esempioJsonIgnoreCondition.WhenWritingNull
, durante la serializzazione.ApplicationRegion: questa proprietà è impostata sull'area del timbro, che consente all'SDK di trovare l'endpoint di connessione più vicino. L'endpoint deve essere preferibilmente nella stessa area.
Nell'implementazione di riferimento viene visualizzato il blocco di codice seguente:
//
// /src/app/AlwaysOn.Shared/Services/CosmosDbService.cs
//
CosmosClientBuilder clientBuilder = new CosmosClientBuilder(sysConfig.CosmosEndpointUri, sysConfig.CosmosApiKey)
.WithConnectionModeDirect()
.WithContentResponseOnWrite(false)
.WithRequestTimeout(TimeSpan.FromSeconds(sysConfig.ComsosRequestTimeoutSeconds))
.WithThrottlingRetryOptions(TimeSpan.FromSeconds(sysConfig.ComsosRetryWaitSeconds), sysConfig.ComsosMaxRetryCount)
.WithCustomSerializer(new CosmosNetSerializer(Globals.JsonSerializerOptions));
if (sysConfig.AzureRegion != "unknown")
{
clientBuilder = clientBuilder.WithApplicationRegion(sysConfig.AzureRegion);
}
_dbClient = clientBuilder.Build();
Messaggistica asincrona
Quando si implementa l'accoppiamento libero, i servizi non hanno dipendenze da altri servizi. L'aspetto libero consente a un servizio di operare in modo indipendente. L'aspetto di accoppiamento consente la comunicazione tra servizi tramite interfacce ben definite. Per un'applicazione mission-critical, l'accoppiamento libero impedisce agli errori downstream di propagarsi ai front-end o ad altri stamp di distribuzione, che offre disponibilità elevata.
Le caratteristiche principali della messaggistica asincrona includono:
I servizi non devono usare la stessa piattaforma di calcolo, il linguaggio di programmazione o il sistema operativo.
I servizi sono ridimensionati in modo indipendente.
Gli errori downstream non influiscono sulle transazioni client.
L'integrità transazionale è difficile da gestire perché la creazione e la persistenza dei dati si verificano in servizi separati. L'integrità transazionale è una sfida tra i servizi di messaggistica e persistenza. Per altre informazioni, vedere Elaborazione dei messaggi Idempotenti.
La traccia end-to-end richiede un'orchestrazione complessa.
È consigliabile usare modelli di progettazione noti, ad esempio il modello di livellamento del carico basato su coda e il modello Consumer concorrenti. Questi modelli distribuiscono il carico dal producer ai consumer e abilitano l'elaborazione asincrona da parte dei consumer. Ad esempio, il ruolo di lavoro consente all'API di accettare la richiesta e tornare rapidamente al chiamante e il ruolo di lavoro elabora separatamente un'operazione di scrittura del database.
Hub eventi broker messaggi tra l'API e il ruolo di lavoro.
Importante
Non usare il broker di messaggi come archivio dati permanente per lunghi periodi di tempo. Il servizio Hub eventi supporta la funzionalità di acquisizione. La funzionalità di acquisizione consente a un hub eventi di scrivere automaticamente una copia dei messaggi in un account di archiviazione collegato. Questo processo controlla l'utilizzo e funge da meccanismo per eseguire il backup dei messaggi.
Dettagli sull'implementazione delle operazioni di scrittura
Le operazioni di scrittura, ad esempio post rating e post commento, vengono elaborate in modo asincrono. L'API invia prima un messaggio con tutte le informazioni pertinenti, ad esempio il tipo di azione e i dati dei commenti, alla coda dei messaggi e restituisce HTTP 202 (Accepted)
immediatamente con l'intestazione Location
dell'oggetto che verrà creato.
BackgroundProcessor
Le istanze elaborano i messaggi nella coda e gestiscono la comunicazione effettiva del database per le operazioni di scrittura. BackgroundProcessor
aumenta e aumenta il numero di istanze in modo dinamico in base al volume dei messaggi della coda. Il limite di scalabilità orizzontale delle istanze del processore è definito dal numero massimo di partizioni di Hub eventi, ovvero 32 per i livelli Basic e standard, 100 per il livello Premium e 1.024 per il livello Dedicato.
La libreria del processore di Hub eventi di Azure in BackgroundProcessor
usa Archiviazione BLOB di Azure per gestire la proprietà della partizione, il bilanciamento del carico tra istanze di lavoro diverse e usare checkpoint per tenere traccia dello stato di avanzamento. I checkpoint non vengono scritti nell'archivio BLOB dopo ogni evento perché aggiunge un ritardo costoso per ogni messaggio. I checkpoint vengono invece scritti in un ciclo timer ed è possibile configurare la durata. L'impostazione predefinita è 10 secondi.
Nell'implementazione di riferimento viene visualizzato il blocco di codice seguente:
while (!stoppingToken.IsCancellationRequested)
{
await Task.Delay(TimeSpan.FromSeconds(_sysConfig.BackendCheckpointLoopSeconds), stoppingToken);
if (!stoppingToken.IsCancellationRequested && !checkpointEvents.IsEmpty)
{
string lastPartition = null;
try
{
foreach (var partition in checkpointEvents.Keys)
{
lastPartition = partition;
if (checkpointEvents.TryRemove(partition, out ProcessEventArgs lastProcessEventArgs))
{
if (lastProcessEventArgs.HasEvent)
{
_logger.LogDebug("Scheduled checkpointing for partition {partition}. Offset={offset}", partition, lastProcessEventArgs.Data.Offset);
await lastProcessEventArgs.UpdateCheckpointAsync();
}
}
}
}
catch (Exception e)
{
_logger.LogError(e, "Exception during checkpointing loop for partition={lastPartition}", lastPartition);
}
}
}
Se l'applicazione del processore rileva un errore o viene arrestata prima di poter elaborare il messaggio:
Un'altra istanza di recupera il messaggio per la rielaborazione perché non è stato eseguito correttamente il checkpoint in Archiviazione.
Si verifica un conflitto se il ruolo di lavoro precedente ha salvato in modo permanente il documento nel database prima che il ruolo di lavoro non sia riuscito. Questo errore si verifica perché vengono usati lo stesso ID e la stessa chiave di partizione. Il processore può ignorare il messaggio in modo sicuro perché il documento è già persistente.
Una nuova istanza ripete i passaggi e finalizza la persistenza se il ruolo di lavoro precedente è stato terminato prima della scrittura nel database.
Leggere i dettagli dell'implementazione delle operazioni
L'API elabora direttamente le operazioni di lettura e restituisce immediatamente i dati all'utente.
Un metodo back-channel non viene stabilito per comunicare con il client se l'operazione viene completata correttamente. L'applicazione client deve eseguire in modo proattivo il polling dell'API per gli aggiornamenti relativi all'elemento specificato nell'intestazione Location
HTTP.
Scalabilità
I singoli componenti del carico di lavoro devono aumentare in modo indipendente perché ogni componente ha modelli di carico diversi. I requisiti di scalabilità dipendono dalla funzionalità del servizio. Alcuni servizi influiscono direttamente sugli utenti e devono aumentare le istanze in modo aggressivo per garantire risposte rapide e un'esperienza utente positiva.
L'implementazione crea un pacchetto dei servizi come immagini contenitore e usa grafici Helm per distribuire i servizi in ogni stamp. I servizi sono configurati per avere le richieste e i limiti di Kubernetes previsti e una regola di scalabilità automatica preconfigurata. I CatalogService
componenti del carico di lavoro e possono essere ridimensionati singolarmente perché entrambi i BackgroundProcessor
servizi sono senza stato.
Gli utenti interagiscono direttamente con , CatalogService
quindi questa parte del carico di lavoro deve rispondere in qualsiasi carico. Per ogni cluster è necessario distribuire almeno tre istanze in tre zone di disponibilità in un'area di Azure. L'utilità di scalabilità automatica orizzontale dei pod (HPA) nel servizio Azure Kubernetes aggiunge automaticamente altri pod in base alle esigenze. La funzionalità di scalabilità automatica di Azure Cosmos DB può aumentare e ridurre dinamicamente le unità richiesta (UR) disponibili per la raccolta. CatalogService
E Azure Cosmos DB combinano per formare un'unità di scala all'interno di un timbro.
L'HPA viene distribuito con un grafico Helm con un numero massimo configurabile e un numero minimo di repliche. Il test di carico ha determinato che ogni istanza può gestire circa 250 richieste al secondo con un modello di utilizzo standard.
Il BackgroundProcessor
servizio ha requisiti diversi ed è considerato un ruolo di lavoro in background che ha un effetto limitato sull'esperienza utente. Ha quindi BackgroundProcessor
una configurazione di scalabilità automatica diversa rispetto a CatalogService
e può essere ridimensionata tra 2 e 32 istanze. Determinare questo limite in base al numero di partizioni usate negli hub eventi. Non sono necessari più ruoli di lavoro rispetto alle partizioni.
Componente | minReplicas |
maxReplicas |
---|---|---|
CatalogService | 3 | 20 |
BackgroundProcessor | 2 | 32 |
Ogni componente del carico di lavoro che include dipendenze, ad ingress-nginx
esempio, ha l'impostazione dei budget di interruzione dei pod (PDB) configurata per garantire che un numero minimo di istanze rimanga disponibile quando i cluster cambiano.
Nell'implementazione di riferimento viene visualizzato il blocco di codice seguente:
#
# /src/app/charts/healthservice/templates/pdb.yaml
# Example pod distribution budget configuration.
#
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: {{ .Chart.Name }}-pdb
spec:
minAvailable: 1
selector:
matchLabels:
app: {{ .Chart.Name }}
Nota
Determinare il numero minimo effettivo e il numero massimo di pod per ogni componente tramite test di carico. Il numero di pod può variare per ogni carico di lavoro.
Strumentazione
Usare la strumentazione per valutare i colli delle prestazioni e i problemi di integrità che i componenti del carico di lavoro possono introdurre nel sistema. Per quantificare le decisioni, ogni componente deve generare informazioni sufficienti tramite metriche e log di traccia. Quando si instrumenta l'applicazione, tenere presenti le considerazioni chiave seguenti:
- Inviare log, metriche e altri dati di telemetria al sistema di log dello stamp.
- Usare la registrazione strutturata anziché testo normale in modo da poter eseguire query sulle informazioni.
- Implementare la correlazione degli eventi per ottenere una visualizzazione delle transazioni end-to-end. Nell'implementazione di riferimento ogni risposta API contiene un ID operazione come intestazione HTTP per la tracciabilità.
- Non basarsi solo sulla registrazione stdout o sulla registrazione della console. È tuttavia possibile usare questi log per risolvere immediatamente un errore del pod.
Questa architettura implementa la traccia distribuita con Application Insights e un'area di lavoro Log di Monitoraggio di Azure per i dati di monitoraggio delle applicazioni. Usare i log di Monitoraggio di Azure per i log e le metriche dei componenti del carico di lavoro e dell'infrastruttura. Questa architettura implementa la traccia completa end-to-end delle richieste provenienti dall'API, passa attraverso Hub eventi e quindi ad Azure Cosmos DB.
Importante
Distribuire le risorse di monitoraggio stamp in un gruppo di risorse di monitoraggio separato. Le risorse hanno un ciclo di vita diverso rispetto al timbro stesso. Per altre informazioni, vedere Monitoraggio dei dati per le risorse stamp.
Dettagli sull'implementazione del monitoraggio delle applicazioni
Il BackgroundProcessor
componente usa il Microsoft.ApplicationInsights.WorkerService
pacchetto NuGet per ottenere la strumentazione predefinita dall'applicazione. Serilog viene usato anche per tutte le registrazioni all'interno dell'applicazione. Application Insights è configurato come sink oltre al sink della console. Un'istanza TelemetryClient
di Application Insights viene usata direttamente solo quando è necessario tenere traccia di altre metriche.
Nell'implementazione di riferimento viene visualizzato il blocco di codice seguente:
//
// /src/app/AlwaysOn.BackgroundProcessor/Program.cs
//
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureServices((hostContext, services) =>
{
Log.Logger = new LoggerConfiguration()
.ReadFrom.Configuration(hostContext.Configuration)
.Enrich.FromLogContext()
.WriteTo.Console(outputTemplate: "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} {Level:u3}] {Message:lj} {Properties:j}{NewLine}{Exception}")
.WriteTo.ApplicationInsights(hostContext.Configuration[SysConfiguration.ApplicationInsightsConnStringKeyName], TelemetryConverter.Traces)
.CreateLogger();
}
Per dimostrare la tracciabilità pratica delle richieste, ogni richiesta API riuscita e non riuscita restituisce l'intestazione ID di correlazione al chiamante. Il team di supporto dell'applicazione può eseguire ricerche in Application Insights con questo identificatore e ottenere una visualizzazione dettagliata della transazione completa, illustrata nel diagramma precedente.
Nell'implementazione di riferimento viene visualizzato il blocco di codice seguente:
//
// /src/app/AlwaysOn.CatalogService/Startup.cs
//
app.Use(async (context, next) =>
{
context.Response.OnStarting(o =>
{
if (o is HttpContext ctx)
{
// ... code omitted for brevity
context.Response.Headers.Add("Server-Location", sysConfig.AzureRegion);
context.Response.Headers.Add("Correlation-ID", Activity.Current?.RootId);
context.Response.Headers.Add("Requested-Api-Version", ctx.GetRequestedApiVersion()?.ToString());
}
return Task.CompletedTask;
}, context);
await next();
});
Nota
Il campionamento adattivo è abilitato per impostazione predefinita in Application Insights SDK. Il campionamento adattivo significa che non tutte le richieste vengono inviate al cloud ed è ricercabile in base all'ID. I team delle applicazioni cruciali devono tracciare in modo affidabile ogni richiesta, motivo per cui l'implementazione del riferimento ha disabilitato il campionamento adattivo nell'ambiente di produzione.
Dettagli sull'implementazione del monitoraggio di Kubernetes
È possibile usare le impostazioni di diagnostica per inviare i log e le metriche del servizio Azure Kubernetes ai log di Monitoraggio di Azure. È anche possibile usare la funzionalità informazioni dettagliate sui contenitori con il servizio Azure Kubernetes. Abilitare informazioni dettagliate sui contenitori per distribuire OMSAgentForLinux tramite un DaemonSet kubernetes in ognuno dei nodi nei cluster del servizio Azure Kubernetes. OMSAgentForLinux può raccogliere altri log e metriche dall'interno del cluster Kubernetes e inviarli all'area di lavoro Log di Monitoraggio di Azure corrispondente. Questa area di lavoro contiene dati granulari sui pod, le distribuzioni, i servizi e l'integrità complessiva del cluster.
La registrazione estesa può influire negativamente sui costi e non offre vantaggi. Per questo motivo, la raccolta di log stdout e lo scraping Prometheus sono disabilitati per i pod del carico di lavoro nella configurazione di informazioni dettagliate sui contenitori perché tutte le tracce sono già acquisite tramite Application Insights, che genera record duplicati.
Nell'implementazione di riferimento viene visualizzato il blocco di codice seguente:
#
# /src/config/monitoring/container-azm-ms-agentconfig.yaml
# This is just a snippet showing the relevant part.
#
[log_collection_settings]
[log_collection_settings.stdout]
enabled = false
exclude_namespaces = ["kube-system"]
Per altre informazioni, vedere il file di configurazione completo.
Monitoraggio dell'integrità delle applicazioni
È possibile usare il monitoraggio e l'osservabilità delle applicazioni per identificare rapidamente i problemi di sistema e informare il modello di integrità sullo stato dell'applicazione corrente. È possibile visualizzare il monitoraggio dell'integrità tramite gli endpoint di integrità. I probe di integrità usano i dati di monitoraggio dell'integrità per fornire informazioni. Il servizio di bilanciamento del carico principale usa tali informazioni per rimuovere immediatamente il componente non integro dalla rotazione.
Questa architettura applica il monitoraggio dell'integrità ai livelli seguenti:
Pod del carico di lavoro eseguiti nel servizio Azure Kubernetes. Questi pod hanno probe di integrità e attività, in modo che il servizio Azure Kubernetes possa gestire il ciclo di vita.
Servizio integrità, ovvero un componente dedicato nel cluster. Frontdoor di Azure è configurato per eseguire il probe Servizio integrità in ogni timbro e rimuovere indicatori non integri dal bilanciamento automatico del carico.
Servizio integrità dettagli sull'implementazione
HealthService
è un componente del carico di lavoro eseguito insieme ad altri componenti, ad esempio CatalogService
e BackgroundProcessor
, nel cluster di calcolo. HealthService
fornisce un'API REST che chiama il controllo dell'integrità di Frontdoor di Azure per determinare la disponibilità di un timbro. A differenza dei probe di liveness di base, Servizio integrità è un componente più complesso che fornisce lo stato delle dipendenze oltre al proprio stato.
Servizio integrità non risponde se il cluster del servizio Azure Kubernetes è inattivo, che esegue il rendering del carico di lavoro non integro. Quando il servizio viene eseguito, esegue controlli periodici sui componenti critici della soluzione. Tutti i controlli vengono eseguiti in modo asincrono e in parallelo. Se uno dei controlli ha esito negativo, l'intero timbro non è disponibile.
Avviso
I probe di integrità di Frontdoor di Azure possono imporre un carico significativo sui Servizio integrità perché le richieste provengono da più posizioni di presenza (PoP). Per evitare l'overload dei componenti downstream, implementare una memorizzazione nella cache efficace.
Servizio integrità viene usato anche per i test ping URL configurati in modo esplicito con la risorsa application insights di ogni stamp.
Per altre informazioni sull'implementazioneHealthService
, vedere Application Servizio integrità.