Migliorare le prestazioni e l'affidabilità di Funzioni di Azure

Questo articolo fornisce indicazioni per migliorare le prestazioni e l'affidabilità delle app per le funzioni senza server. Per un set più generale di procedure consigliate di Funzioni di Azure, vedere Funzioni di Azure procedure consigliate.

Questo articolo definisce le procedure consigliate per creare e definire l'architettura di soluzioni senza server tramite Funzioni di Azure.

Evitare funzioni con esecuzione prolungata

Le funzioni con esecuzione prolungata e di grandi dimensioni possono causare problemi di timeout imprevisti. Per altre informazioni sui timeout per un determinato piano di hosting, vedere durata del timeout dell'app per le funzioni.

Una funzione può diventare grande a causa di molte dipendenze Node.js. L'importazione delle dipendenze può anche fare aumentare i tempi di caricamento causando timeout imprevisti. Le dipendenze vengono caricate in modo sia esplicito che implicito. Un singolo modulo caricato dal codice potrebbe caricare i propri moduli aggiuntivi.

Quando è possibile, suddividere le funzioni di grandi dimensioni in gruppi di funzioni più piccoli che possono interagire tra loro e restituire rapidamente le risposte. Ad esempio, una funzione trigger WEBhook o HTTP potrebbe richiedere una risposta di riconoscimento entro un determinato limite di tempo; è comune che i webhook richiedano una risposta immediata. È possibile passare il payload del trigger HTTP in una coda perché venga elaborato da una funzione di trigger della coda. Questo approccio consente di rinviare il lavoro effettivo e restituire una risposta immediata.

Assicurarsi che le attività in background vengano completate

Quando la funzione avvia qualsiasi attività, callback, thread, processi, deve completare prima che il codice della funzione venga restituito. Poiché Funzioni non tiene traccia di questi thread in background, l'arresto del sito può verificarsi indipendentemente dallo stato del thread in background, che può causare un comportamento imprevisto nelle funzioni.

Ad esempio, se una funzione avvia un'attività in background e restituisce una risposta riuscita prima del completamento dell'attività, il runtime di Funzioni considera l'esecuzione come completata correttamente, indipendentemente dal risultato dell'attività in background. Se questa attività in background esegue un lavoro essenziale, può essere preceduta dall'arresto del sito, lasciando tale lavoro in uno stato sconosciuto.

Comunicazioni tra funzioni

Le funzioni permanenti e le app per la logica di Azure sono progettate per gestire transazioni di stato e comunicazioni tra più funzioni.

Se non si usa Durable Functions o App per la logica per l'integrazione con più funzioni, è consigliabile usare le code di archiviazione per la comunicazione tra funzioni. Il motivo principale è che le code di archiviazione sono più economiche e molto più facili da effettuare rispetto ad altre opzioni di archiviazione.

Le dimensioni dei singoli messaggi in una coda di archiviazione sono limitate a 64 KB. Se è necessario passare messaggi più grandi tra le funzioni, è possibile usare una coda bus di servizio di Azure per supportare le dimensioni dei messaggi fino a 256 KB nel livello Standard e fino a 100 MB nel livello Premium.

Gli argomenti del bus di servizio sono utili se è necessario filtrare i messaggi prima dell'elaborazione.

Gli hub eventi sono utili per supportare comunicazioni con volumi elevati.

Scrivere le funzioni in modo che siano senza stato

Le funzioni devono essere senza stato e idempotenti se possibile. Associare ai dati le eventuali informazioni di stato necessarie. Ad esempio, un ordine in fase di elaborazione probabilmente ha un membro state associato. Una funzione può elaborare un ordine basato su tale stato rimanendo però una funzione senza stato.

Le funzioni idempotenti sono consigliate in particolare con i trigger timer. Ad esempio, se si dispone di un elemento che deve essere eseguito una volta al giorno, scriverlo in modo che possa essere eseguito in qualsiasi momento durante il giorno con gli stessi risultati. La funzione può uscire quando non c'è lavoro per un determinato giorno. Anche se un'esecuzione precedente non è stata completata, l'esecuzione successiva riprenderà da dove era stata interrotta. Ciò è particolarmente importante per le associazioni basate su messaggi che riprovano a eseguire un errore. Per altre informazioni, vedere Progettazione di Funzioni di Azure per l'input identico.

Scrivere funzioni difensive

Si supponga che la funzione possa rilevare un'eccezione in qualsiasi momento. Progettare le funzioni con la possibilità di continuare da un punto di errore precedente durante l'esecuzione successiva. Si consideri uno scenario che richiede le azioni seguenti:

  1. Query per 10.000 righe in un database.
  2. Creare un messaggio in coda per ognuna delle righe da elaborare ulteriormente in un secondo tempo.

A seconda del modo in cui è complesso il sistema, è possibile che siano presenti servizi downstream che comportano problemi, interruzioni di rete o limiti di quota raggiunti e così via. Tutti questi possono influire sulla funzione in qualsiasi momento. È necessario progettare le funzioni in modo che siano preparate.

Come reagisce il codice in caso di errore dopo l'inserimento di 5.000 di tali elementi in una coda per l'elaborazione? Tenere traccia degli elementi in un set già completato. In caso contrario, è possibile inserirli di nuovo la volta successiva. Questo doppio inserimento può avere un impatto grave sul flusso di lavoro, quindi rendere le funzioni idempotenti.

Se un elemento della coda è già stato elaborato, consentire alla funzione di essere no-op.

Sfruttare le misure difensive già messe a disposizione per i componenti usati nella piattaforma Funzioni di Azure. Ad esempio, vedere Gestione di messaggi della coda non elaborabili nella documentazione relativa a trigger e associazioni della coda di Archiviazione di Azure.

Per le funzioni basate su HTTP prendere in considerazione le strategie di controllo delle versioni api con Azure Gestione API. Ad esempio, se è necessario aggiornare l'app per le funzioni basata su HTTP, distribuire il nuovo aggiornamento in un'app per le funzioni separata e usare Gestione API revisioni o versioni per indirizzare i client alla nuova versione o alla revisione. Una volta che tutti i client usano la versione o la revisione e non vengono più esecuzioni nell'app per le funzioni precedente, è possibile eseguire il deprovisioning dell'app per le funzioni precedente.

Procedure consigliate per l'organizzazione delle funzioni

Come parte della soluzione, è possibile sviluppare e pubblicare più funzioni. Queste funzioni vengono spesso combinate in un'unica app per le funzioni, ma possono essere eseguite anche in app per le funzioni separate. Nei piani di hosting Premium e dedicati (servizio app), più app per le funzioni possono anche condividere le stesse risorse eseguendo nello stesso piano. In che modo si raggruppano le funzioni e le app per le funzioni possono influire sulle prestazioni, il ridimensionamento, la configurazione, la distribuzione e la sicurezza della soluzione complessiva. Non esistono regole che si applicano a ogni scenario, quindi prendere in considerazione le informazioni contenute in questa sezione durante la pianificazione e lo sviluppo delle funzioni.

Organizzare le funzioni per prestazioni e scalabilità

Ogni funzione creata ha un footprint di memoria. Anche se questo footprint è in genere piccolo, la presenza di troppe funzioni all'interno di un'app per le funzioni può causare un avvio più lento dell'app in nuove istanze. Significa anche che l'utilizzo complessivo della memoria dell'app per le funzioni potrebbe essere superiore. È difficile dire quanti funzioni devono trovarsi in una singola app, che dipende dal carico di lavoro specifico. Tuttavia, se la funzione archivia molti dati in memoria, valutare la possibilità di avere meno funzioni in una singola app.

Se si eseguono più app per le funzioni in un singolo piano Premium o in un piano dedicato (servizio app), queste app condividono tutte le stesse risorse allocate al piano. Se si dispone di un'app per le funzioni con un requisito di memoria molto più elevato rispetto agli altri, usa una quantità sproporzionata di risorse di memoria in ogni istanza in cui viene distribuita l'app. Poiché ciò potrebbe lasciare meno memoria disponibile per le altre app in ogni istanza, potrebbe essere necessario eseguire un'app per le funzioni con memoria elevata come questa nel proprio piano di hosting separato.

Nota

Quando si usa il piano di consumo, è consigliabile inserire sempre ogni app nel proprio piano, poiché le app vengono ridimensionate in modo indipendente. Per altre informazioni, vedere Più app nello stesso piano.

Valutare se si desidera raggruppare le funzioni con profili di carico diversi. Ad esempio, se si dispone di una funzione che elabora molte migliaia di messaggi di coda e un altro che viene chiamato solo occasionalmente, ma ha requisiti di memoria elevati, potrebbe essere necessario distribuirli in app per le funzioni separate in modo da ottenere i propri set di risorse e ridimensionare in modo indipendente l'uno dall'altro.

Organizzare le funzioni per la configurazione e la distribuzione

Le app per le funzioni hanno un file, usato per configurare il host.json comportamento avanzato dei trigger di funzione e il runtime di Funzioni di Azure. Le modifiche apportate al host.json file si applicano a tutte le funzioni all'interno dell'app. Se si dispone di alcune funzioni che richiedono configurazioni personalizzate, prendere in considerazione lo spostamento in un'app per le funzioni.

Tutte le funzioni nel progetto locale vengono distribuite insieme come set di file all'app per le funzioni in Azure. Potrebbe essere necessario distribuire singole funzioni separatamente o usare funzionalità come gli slot di distribuzione per alcune funzioni e non altri. In questi casi, è necessario distribuire queste funzioni (in progetti di codice separati) in diverse app per le funzioni.

Organizzare le funzioni per privilegio

Le stringhe di connessione e altre credenziali archiviate nelle impostazioni dell'applicazione conferiscono a tutte le funzioni nell'app per le funzioni lo stesso set di autorizzazioni nella risorsa associata. Provare a ridurre al minimo il numero di funzioni con accesso a credenziali specifiche spostando le funzioni che non usano tali credenziali in un'app per le funzioni separata. È sempre possibile usare tecniche come il concatenamento delle funzioni per spostare i dati tra funzioni in app per le funzioni diverse.

Procedure consigliate per la scalabilità

Esistono diversi fattori che influiscono sulla scalabilità delle istanze dell'app per le funzioni. Informazioni dettagliate sono disponibili nella documentazione relativa alla scalabilità delle funzioni. Di seguito sono descritte alcune procedure consigliate per garantire la scalabilità ottimale di un'app per le funzioni.

Condividere e gestire le connessioni

Riutilizzare le connessioni alle risorse esterne ogni volta che è possibile. Vedere come gestire le connessioni in Funzioni di Azure.

Evitare di condividere gli account di archiviazione

Quando si crea un'app per le funzioni, è necessario associarla a un account di archiviazione. La connessione dell'account di archiviazione viene mantenuta nell'impostazione dell'applicazione AzureWebJobsStorage.

Per incrementare le prestazioni, usare un account di archiviazione diverso per ogni app per le funzioni. Questo accorgimento è particolarmente importante in presenza di funzioni attivate da Hub eventi o Durable Functions, che generano entrambe un volume elevato di transazioni di archiviazione. Quando la logica dell'applicazione interagisce con Archiviazione di Azure, direttamente (usando Storage SDK) o tramite una delle associazioni di archiviazione, è necessario usare un account di archiviazione dedicato. Ad esempio, se si dispone di una funzione attivata dall'hub eventi che scrive alcuni dati nell'archiviazione BLOB, usare due account di archiviazione, uno per l'app per le funzioni e un altro per i BLOB archiviati dalla funzione.

Non combinare codice di test e di produzione nella stessa app per le funzioni

Le funzioni all'interno di un'app per le funzioni condividono le risorse. Ad esempio, la memoria è condivisa. Se si usa un'app per le funzioni nell'ambiente di produzione, non aggiungere funzioni e risorse relative ai test, per evitare possibili sovraccarichi imprevisti durante l'esecuzione del codice di produzione.

Prestare attenzione a quello che si carica nelle app per le funzioni di produzione. La memoria viene calcolata in ogni funzione nell'app.

Se si dispone di un assembly condiviso a cui si fa riferimento in più funzioni .NET, inserirlo in una cartella condivisa comune. In caso contrario, è possibile distribuire accidentalmente più versioni dello stesso binario che si comportano in modo diverso tra le funzioni.

Non usare la registrazione dettagliata nel codice di produzione, che ha un impatto negativo sulle prestazioni.

Usare codice asincrono ma evitare di bloccare le chiamate

La programmazione asincrona è una procedura consigliata, soprattutto quando si bloccano le operazioni di I/O sono coinvolte.

In C#, evitare sempre di fare riferimento alla proprietà o al metodo chiamante Wait in un'istanza.ResultTask Questo approccio può causare l'esaurimento di un thread.

Suggerimento

Se si prevede di usare binding HTTP o WebHook, evitare l'esaurimento delle porte che può essere causato da un'errata creazione di istanze di HttpClient. Per altre informazioni, vedere How to manage connections in Azure Functions (Come gestire le connessioni in Funzioni di Azure).

Usare più processi di lavoro

Per impostazione predefinita, qualsiasi istanza host per Funzioni usa un singolo processo di lavoro. Per migliorare le prestazioni, soprattutto con runtime a thread singolo come Python, usare il FUNCTIONS_WORKER_PROCESS_COUNT per aumentare il numero di processi di lavoro per host (fino a 10). Funzioni di Azure prova quindi a distribuire uniformemente le chiamate di funzioni simultanee tra questi processi di lavoro.

FUNCTIONS_WORKER_PROCESS_COUNT si applica a ogni host creato da Funzioni quando le istanze dell'applicazione vengono aumentate per soddisfare la domanda.

Ricevere messaggi in batch, se possibile

Alcuni trigger come l'hub eventi consentono di ricevere un batch di messaggi in un'unica chiamata. L'invio di messaggi in batch offre prestazioni notevolmente migliori. È possibile configurare le dimensioni massime del batch nel file host.json come descritto nella documentazione di riferimento su host.json

Per le funzioni C# è possibile modificare il tipo in una matrice fortemente tipizzata. Anziché EventData sensorEvent, la firma del metodo può essere, ad esempio, EventData[] sensorEvent. Per altre lingue, è necessario impostare in modo esplicito la proprietà cardinalità in function.json modo da many abilitare il batch , come illustrato qui.

Configurare i comportamenti degli host per migliorare la gestione della concorrenza

Il file host.json nell'app per le funzioni consente di configurare i comportamenti del trigger e del runtime dell'host. Oltre ai comportamenti di invio in batch, è possibile gestire anche la concorrenza in una serie di trigger. La modifica dei valori di queste opzioni consente spesso di applicare a ogni istanza la scalabilità appropriata per soddisfare le esigenze delle funzioni richiamate.

Le impostazioni nel file host.json si applicano a tutte le funzioni all'interno dell'app, all'interno di una singola istanza della funzione. Ad esempio, se si dispone di un'app per le funzioni con due funzioni HTTP e maxConcurrentRequests le richieste impostate su 25, una richiesta a un trigger HTTP conteggierebbe i 25 richieste simultanee condivise. Quando l'app per le funzioni viene ridimensionata su 10 istanze, le dieci funzioni consentono in modo efficace 250 richieste simultanee (10 istanze * 25 richieste simultanee per istanza).

Altre opzioni di configurazione host sono disponibili nell'articolo di configurazione host.json.

Passaggi successivi

Per altre informazioni, vedere le seguenti risorse: