Indicazioni sulla memorizzazione nella cache

La memorizzazione nella cache è una tecnica comune che ha l'obiettivo di migliorare le prestazioni e la scalabilità del sistema. La memorizzazione nella cache copia temporaneamente i dati a cui si accede di frequente nell'archiviazione che si trova più vicino all'applicazione rispetto all'origine originale. Questo approccio può migliorare significativamente i tempi di risposta per le applicazioni client servendo i dati più rapidamente.

La memorizzazione nella cache è più efficace quando un'istanza client legge ripetutamente gli stessi dati, soprattutto se tutte le condizioni seguenti si applicano all'archivio dati originale:

  • L'archivio dati rimane relativamente statico.
  • È lento rispetto alla velocità della cache.
  • È soggetto a un elevato livello di contesa.
  • È abbastanza lontano dai client che la latenza di rete è significativa.

Memorizzazione nella cache nelle applicazioni distribuite

Le applicazioni distribuite implementano in genere una o entrambe le strategie seguenti durante la memorizzazione nella cache dei dati:

  • Usare una cache privata, in cui si contengono i dati in locale nel computer che esegue un'applicazione o un servizio.

  • Usare una cache condivisa, che funge da origine comune a cui possono accedere più processi e computer.

In entrambi i casi, è possibile eseguire la memorizzazione nella cache sul lato client e sul lato server. Il processo che fornisce l'interfaccia utente per un sistema, ad esempio un Web browser o un'applicazione desktop, gestisce la memorizzazione nella cache lato client. La memorizzazione nella cache lato server viene eseguita in remoto dal processo che fornisce i servizi aziendali.

Memorizzazione nella cache privata

Il tipo di cache più semplice è un archivio in memoria. Si trova nello spazio degli indirizzi di un singolo processo e il codice eseguito in tale processo accede direttamente alla cache. Questo tipo di cache è rapido da accedere. Può anche offrire un mezzo efficace per archiviare quantità modeste di dati statici. La quantità di memoria disponibile nel computer limita in genere le dimensioni di una cache.

Se è necessario memorizzare nella cache più informazioni di quanto sia fisicamente possibile in memoria, è possibile scrivere dati memorizzati nella cache nel file system locale. L'accesso ai dati dal file system richiede più tempo rispetto a se i dati vengono archiviati in memoria, ma deve essere ancora più veloce e affidabile rispetto al recupero dei dati in una rete.

Se si dispone di più istanze di un'applicazione che usa questo modello in esecuzione simultaneamente, ogni istanza dell'applicazione ha la propria cache indipendente che contiene la propria copia dei dati.

Si pensi a una cache come uno snapshot dei dati originali in un determinato momento del passato. Se questi dati non sono statici, è probabile che diverse istanze dell'applicazione contengano versioni diverse dei dati nella cache. Di conseguenza, la stessa query eseguita da queste istanze può restituire risultati diversi, come illustrato nel diagramma seguente.

Diagramma che mostra i risultati dell'uso di una cache in memoria in istanze diverse di un'applicazione.

Diagramma che mostra l'incoerenza della cache in più istanze dell'applicazione connesse a un database SQL condiviso. A destra è presente un database SQL rappresentato come cilindro. In alto a sinistra è presente un cerchio di grandi dimensioni con etichetta A dell'istanza dell'applicazione e in basso a sinistra è presente un cerchio di dimensioni simili con etichetta B. All'interno dell'istanza dell'applicazione A è un'icona a forma di ingranaggio che rappresenta il processo dell'applicazione. Sotto l'icona a forma di ingranaggio si trova una tabella a griglia che rappresenta una cache in memoria. All'esterno del limite, è presente una cache di lettura delle etichette uno snapshot dei dati al momento X. All'interno dell'istanza dell'applicazione B è un'altra icona a forma di ingranaggio. Sotto l'icona a forma di ingranaggio si trova una tabella a griglia che rappresenta una cache in memoria. All'esterno del limite, c'è un'etichetta con la dicitura: la cache è un'istantanea dei dati al momento Y. Dal database SQL, si estende una linea verso l'istanza dell'applicazione A, con l'etichetta: l'istanza dell'applicazione A recupera i dati al momento X e li memorizza nella cache in memoria. Una seconda riga si estende dal database SQL verso l'istanza dell'applicazione B, l'istanza dell'applicazione B recupera i dati al momento Y e lo memorizza nella cache in memoria. Tra queste due righe di annotazione è presente una nota con la dicitura che le informazioni nel database cambiano tra l'istante X e l'istante Y.

Memorizzazione nella cache condivisa

Usando una cache condivisa, è possibile assicurarsi che tutte le istanze dell'applicazione visualizzino la stessa visualizzazione dei dati memorizzati nella cache. La cache si trova in una posizione separata, che in genere è ospitata come parte di un servizio separato, come illustrato nel diagramma seguente.

Diagramma che mostra i risultati delle applicazioni che usano una cache condivisa.

Diagramma che mostra come un servizio cache condivisa risolve l'incoerenza della cache in più istanze dell'applicazione connesse a un database SQL. All'estrema destra è presente un database SQL rappresentato come cilindro. Al centro c’è una casella con l’etichetta “servizio cache condivisa”, contenente una tabella a griglia che rappresenta la cache condivisa. All'estrema sinistra è presente un'icona a forma di ingranaggio etichettata istanza dell'applicazione A. Sotto di essa è presente una seconda icona a forma di ingranaggio etichettata istanza dell'applicazione B. Una freccia nera punta dal database SQL alla griglia del servizio cache condivisa, a indicare che il database popola la cache condivisa. Dal servizio cache condivisa due frecce blu si estendono verso l'esterno, una verso l'istanza dell'applicazione A e una verso l'istanza dell'applicazione B. All'estrema sinistra, una parentesi curva si estende verticalmente tra le due istanze dell'applicazione, con un'etichetta adiacente che legge "Entrambe le istanze dell'applicazione visualizzano gli stessi dati memorizzati nella cache". Questa etichetta e parentesi quadre evidenziano il vantaggio chiave dell'architettura della cache condivisa: a differenza di una cache in memoria per istanza, entrambe le istanze dell'applicazione A e B recuperano i dati dalla stessa cache centralizzata.

Un importante vantaggio dell'approccio alla memorizzazione nella cache condivisa è la scalabilità. Molti servizi di cache condivisa vengono implementati usando un cluster di server e usano software per distribuire i dati nel cluster in modo trasparente. Un'istanza dell'applicazione invia una richiesta al servizio cache. L'infrastruttura sottostante determina la posizione dei dati memorizzati nella cache nel cluster. È possibile ridimensionare facilmente la cache aggiungendo altri server.

Esistono due svantaggi principali dell'approccio alla memorizzazione nella cache condivisa:

  • L'accesso alla cache è più lento perché non viene più mantenuto in locale con ogni istanza dell'applicazione.
  • L'implementazione di un servizio cache separato potrebbe aggiungere complessità alla soluzione.

Considerazioni sull'uso della memorizzazione nella cache

Le sezioni seguenti descrivono le considerazioni relative alla progettazione e all'uso di una cache.

Decidere quando memorizzare i dati nella cache

La memorizzazione nella cache può migliorare notevolmente le prestazioni, la scalabilità e la disponibilità. Maggiore è il numero di dati disponibili e maggiore è il numero di utenti che devono accedere a questi dati, maggiore è il vantaggio della memorizzazione nella cache. La memorizzazione nella cache riduce la latenza e la contesa associate alla gestione di grandi volumi di richieste simultanee nell'archivio dati originale.

Ad esempio, un database potrebbe supportare un numero limitato di connessioni simultanee. Il recupero di dati da una cache condivisa, tuttavia, anziché dal database sottostante, consente a un'applicazione client di accedere a questi dati anche se il numero di connessioni disponibili è attualmente esaurito. Inoltre, se il database non è più disponibile, le applicazioni client potrebbero continuare usando i dati nella cache.

Prendere in considerazione la memorizzazione nella cache dei dati letti di frequente ma modificati raramente. Ad esempio, i dati della cache con una percentuale maggiore di operazioni di lettura rispetto alle operazioni di scrittura. Tuttavia, non usare la cache come archivio autorevole di informazioni critiche. Salvare invece tutte le modifiche importanti in un archivio dati permanente. Se la cache non è disponibile, l'applicazione può continuare a funzionare usando l'archivio dati e non si perdono informazioni importanti.

Determinare come memorizzare nella cache i dati in modo efficace

Per usare una cache in modo efficace, determinare i dati più appropriati da memorizzare nella cache e memorizzarlo nella cache al momento giusto. È possibile aggiungere dati alla cache quando un'applicazione la recupera per la prima volta. L'applicazione deve recuperare i dati una sola volta dall'archivio dati e quindi l'accesso successivo può essere soddisfatto usando la cache.

In alternativa, è possibile popolare parzialmente o completamente una cache con i dati in anticipo, in genere all'avvio dell'applicazione. Questo approccio è noto come seeding. Tuttavia, potrebbe non essere consigliabile implementare il seeding per una cache di grandi dimensioni perché questo approccio può imporre un carico improvviso e elevato nell'archivio dati originale all'avvio dell'esecuzione dell'applicazione.

Analizzare i modelli di utilizzo per decidere se precompilare completamente o parzialmente una cache e scegliere quali dati memorizzare nella cache. Ad esempio, è possibile effettuare il seeding della cache con i dati del profilo utente statico per i clienti che usano l'applicazione ogni giorno, ma non per i clienti che usano l'applicazione una sola volta alla settimana.

La memorizzazione nella cache funziona in genere bene con i dati non modificabili o che cambiano raramente. Gli esempi includono informazioni di riferimento, ad esempio informazioni sui prodotti e sui prezzi in un'applicazione di e-commerce o risorse statiche condivise costose da costruire. Caricare alcuni o tutti questi dati nella cache all'avvio dell'applicazione per ridurre al minimo la domanda sulle risorse e migliorare le prestazioni. È anche possibile avere un processo in background che aggiorna periodicamente i dati di riferimento nella cache per assicurarsi che sia up-to-date. In alternativa, il processo in background può aggiornare la cache quando i dati di riferimento cambiano.

La memorizzazione nella cache è meno utile per i dati dinamici, anche se esistono alcune eccezioni. Per altre informazioni, vedere la sezione Cache highly dynamic data più avanti in questo articolo. Quando i dati originali cambiano regolarmente, le informazioni memorizzate nella cache diventano non aggiornate rapidamente o il sovraccarico della sincronizzazione della cache con l'archivio dati originale riduce l'efficacia della memorizzazione nella cache.

Una cache non deve includere i dati completi per un'entità. Ad esempio, se un elemento dati rappresenta un oggetto multivalore, ad esempio un cliente bancario con un nome, un indirizzo e un saldo del conto, alcuni di questi elementi potrebbero rimanere statici, ad esempio il nome e l'indirizzo. Altri elementi, ad esempio il saldo del conto, potrebbero essere più dinamici. In queste situazioni, può essere utile memorizzare nella cache le parti statiche dei dati e recuperare (o calcolare) solo le informazioni rimanenti quando necessario.

Eseguire test delle prestazioni e analisi dell'utilizzo per determinare se il caricamento prepopolato o su richiesta della cache, o una combinazione di entrambi, è appropriato. Basare la decisione sulla volatilità e sul modello di utilizzo dei dati. L'utilizzo della cache e l'analisi delle prestazioni sono importanti nelle applicazioni che riscontrano carichi elevati e devono essere altamente scalabili. Ad esempio, in scenari altamente scalabili, è possibile eseguire il seeding della cache per ridurre il carico nell'archivio dati in momenti di picco.

La memorizzazione nella cache può essere usata anche per evitare calcoli ripetuti mentre l'applicazione è in esecuzione. Se un'operazione trasforma i dati o esegue un calcolo complesso, può salvare i risultati dell'operazione nella cache. Se in seguito è necessario lo stesso calcolo, l'applicazione può recuperare i risultati dalla cache.

Un'applicazione può modificare i dati contenuti in una cache. Tuttavia, considerare la cache come un archivio dati temporaneo che potrebbe scomparire in qualsiasi momento. Non archiviare dati preziosi solo nella cache; assicurarsi di mantenere anche le informazioni nell'archivio dati originale. Questo approccio riduce al minimo la possibilità di perdere dati se la cache non è più disponibile.

Mantenere la cache coerente nelle scritture

Quando l'applicazione modifica anche i dati memorizzati nella cache, decidere in che modo la cache rimane coerente con il sistema di record. Due approcci sono comuni:

  • Invalidate in caso di scrittura: Scrivere la modifica nell'archivio dati e quindi rimuovere la voce della cache interessata. La lettura successiva ripopola la cache dall'archivio dati. Questo approccio mantiene semplice il percorso di scrittura, ma un lettore può riscontrare un mancato riscontro nella cache o visualizzare brevemente i dati non aggiornati immediatamente dopo una scrittura. Il modelloCache-Aside descrive questo approccio.

  • Write-through: Aggiornare l'archivio dati e la cache come parte della stessa operazione di scrittura e restituire la risposta di scrittura solo dopo l'esito positivo di entrambi gli aggiornamenti. Questo approccio offre ai lettori il valore aggiornato immediatamente dopo una scrittura corretta, a costo di una latenza di scrittura più elevata e di una logica di coordinamento maggiore.

    Per un esempio completo che usa Funzioni di Azure per coordinare gli aggiornamenti write-through in database SQL di Azure e Azure Managed Redis, vedi Write-through caching with Azure Managed Redis and database SQL di Azure.

Utilizzare il write-through solo per i percorsi di accesso a prevalenza di lettura che richiedono coerenza immediata in lettura dopo la scrittura. Per i dati che raramente vengono letti dopo la scrittura o che cambiano costantemente, invalidare in scrittura o ignorare la cache.

Memorizzare nella cache dati altamente dinamici

L'archiviazione di informazioni in rapida evoluzione in un archivio dati permanente può comportare un sovraccarico nel sistema. Si consideri, ad esempio, un dispositivo che segnala continuamente lo stato o altre misurazioni. Se un'applicazione sceglie di non memorizzare nella cache questi dati in base al fatto che le informazioni memorizzate nella cache sono in genere obsolete, la stessa considerazione potrebbe essere vera quando si archiviano e recuperano queste informazioni dall'archivio dati. Nel tempo necessario per salvare e recuperare questi dati, potrebbe cambiare.

In una situazione di questo tipo, considerare i vantaggi dell'archiviazione delle informazioni dinamiche direttamente nella cache anziché nell'archivio dati permanente. Se i dati non sono critici e non richiedono il controllo, non è importante se la modifica occasionale viene persa.

Gestire la scadenza dei dati in una cache

Nella maggior parte dei casi, una cache contiene dati che sono una copia dei dati dall'archivio dati originale. I dati nell'archivio dati originale possono cambiare dopo la memorizzazione nella cache, in modo da rendere i dati memorizzati nella cache non aggiornati. Molti sistemi di memorizzazione nella cache consentono di configurare la cache per la scadenza dei dati, riducendo il periodo durante il quale i dati potrebbero non essere aggiornati.

Dopo la scadenza dei dati memorizzati nella cache, la cache la rimuove. L'applicazione recupera quindi nuovi dati dall'archivio dati originale e può sostituire i dati scaduti nella cache. È possibile impostare un criterio di scadenza predefinito quando si configura la cache. In molti servizi della cache è anche possibile specificare il periodo di scadenza per singoli oggetti quando vengono archiviati a livello di codice nella cache. Alcune cache consentono di specificare il periodo di scadenza come valore assoluto o come valore scorrevole se l'elemento non è accessibile entro un determinato periodo di tempo. Questa impostazione esegue l'override di tutti i criteri di scadenza a livello di cache, ma solo per gli oggetti specificati.

Note

Prendere in considerazione il periodo di scadenza per la cache e gli oggetti che contiene attentamente. Se si rende troppo breve, gli oggetti scadono troppo rapidamente e si riducono i vantaggi derivanti dall'uso della cache. Se si rende troppo lungo il periodo, si rischia che i dati diventino obsoleti.

Se si consente la conservazione dei dati nella cache per molto tempo, la cache potrebbe riempirsi. In questo caso, le richieste di aggiunta di nuovi elementi alla cache potrebbero causare la rimozione forzata della cache di alcuni elementi in un processo noto come rimozione. I servizi di cache di solito eliminano i dati secondo il criterio dei meno utilizzati recentemente (LRU), ma si può solitamente aggirare questa politica e impedire che gli elementi vengano rimossi. Tuttavia, se si adotta questo approccio, si rischia di superare la memoria disponibile nella cache. Un'applicazione che tenta di aggiungere un elemento a una cache completa genera un'eccezione.

Alcune implementazioni di memorizzazione nella cache potrebbero fornire altri criteri di rimozione. I tipi di criteri di rimozione includono:

  • Un criterio usato più di recente: rimuove gli elementi usati più di recente dalla cache nell'attesa che i dati non vengano nuovamente richiesti.
  • Una politica first-in-first-out: Rimuove prima dalla cache i dati meno recenti.
  • Un criterio di rimozione esplicito: rimuove gli elementi dalla cache in base a un evento di attivazione, ad esempio i dati da modificare.

Invalidare i dati in una cache lato client

I dati contenuti in una cache lato client vengono considerati esterni al controllo del servizio che fornisce i dati al client. Un servizio non può forzare direttamente un client ad aggiungere o rimuovere informazioni da una cache lato client.

Questa limitazione significa che un client che usa una cache non configurata correttamente può continuare a usare informazioni obsolete. Ad esempio, se i criteri di scadenza della cache non sono implementati correttamente, un client potrebbe usare informazioni obsolete memorizzate nella cache in locale quando le informazioni nell'origine dati originale cambiano.

Se si compila un'applicazione Web che gestisce i dati tramite una connessione HTTP, è possibile forzare in modo implicito un client Web, ad esempio un browser o un proxy Web, per recuperare le informazioni più recenti. È possibile attivare questo aggiornamento modificando l'URI ogni volta che si aggiorna la risorsa. I client Web usano in genere l'URI di una risorsa come chiave nella cache lato client, quindi se l'URI cambia, il client Web ignora le versioni precedentemente memorizzate nella cache di una risorsa e recupera invece la nuova versione.

Gestire la concorrenza in una cache

Spesso si progettano cache da condividere da più istanze di un'applicazione. Ogni istanza dell'applicazione può leggere e modificare i dati nella cache, quindi gli stessi problemi di concorrenza che si verificano con qualsiasi archivio dati condiviso si applicano anche a una cache. In una situazione in cui un'applicazione deve modificare i dati contenuti nella cache, potrebbe essere necessario assicurarsi che gli aggiornamenti eseguiti da un'istanza dell'applicazione non sovrascrivano le modifiche apportate da un'altra istanza.

A seconda della natura dei dati e della probabilità di collisioni, adottare uno dei due approcci per la concorrenza:

  • Ottimistica: Prima che l'applicazione aggiorni i dati, controlla se i dati nella cache sono stati modificati dopo il recupero. Se i dati sono ancora uguali, l'applicazione apporta la modifica. In caso contrario, l'applicazione decide se aggiornarla. La logica di business che guida questa decisione è specifica dell'applicazione. Questo approccio è adatto per situazioni in cui gli aggiornamenti sono poco frequenti o in cui è improbabile che si verifichino conflitti.

  • Pessimistico: Quando l'applicazione recupera i dati, blocca i dati nella cache per impedire la modifica di un'altra istanza. Questo processo garantisce che i conflitti non possano verificarsi, ma possono anche bloccare altre istanze che devono elaborare gli stessi dati. La concorrenza pessimistica può influire sulla scalabilità di una soluzione ed è consigliata solo per le operazioni di breve durata. Questo approccio potrebbe essere appropriato per le situazioni in cui le collisioni sono più probabili, soprattutto se un'applicazione aggiorna più elementi nella cache e deve assicurarsi che queste modifiche vengano applicate in modo coerente.

Implementare disponibilità elevata e scalabilità e migliorare le prestazioni

Evitare di usare una cache come repository primario di dati. L'archivio dati originale, da cui viene popolata la cache, svolge questo ruolo. L'archivio dati originale garantisce la persistenza dei dati.

Prestare attenzione a non introdurre dipendenze critiche sulla disponibilità di un servizio cache condivisa nelle soluzioni. Un'applicazione deve essere in grado di continuare a funzionare se la cache condivisa non è disponibile. L'applicazione non dovrebbe diventare non responsiva o fallire mentre è in attesa che il servizio cache riprenda.

Pertanto, l'applicazione deve essere preparata per rilevare la disponibilità del servizio cache ed eseguire il fallback all'archivio dati originale se la cache non è accessibile. Il modello diCircuit-Breaker è utile per gestire questo scenario. Il servizio che fornisce la cache può essere recuperato e, dopo che diventa disponibile, la cache può essere ripopolata man mano che i dati vengono letti dall'archivio dati originale, seguendo una strategia come il modello diCache-Aside.

Tuttavia, la scalabilità del sistema potrebbe essere influenzata se l'applicazione esegue il fallback all'archivio dati originale quando la cache è temporaneamente non disponibile. Durante il recupero della cache, l'archivio dati originale potrebbe essere invaso da richieste di dati, con conseguente timeout e connessioni non riuscite.

Prendere in considerazione l'implementazione di una cache locale e privata in ogni istanza di un'applicazione, insieme alla cache condivisa a cui accedono tutte le istanze dell'applicazione. Quando l'applicazione recupera un elemento, può prima archiviare nella cache locale, quindi nella cache condivisa e infine nell'archivio dati originale. La cache locale può essere popolata usando i dati nella cache condivisa o nel database se la cache condivisa non è disponibile.

Questo approccio richiede un'attenta configurazione per impedire che la cache locale diventi troppo obsoleta rispetto alla cache condivisa. Tuttavia, la cache locale funge da buffer se la cache condivisa non è raggiungibile, come illustrato nel diagramma seguente.

Diagramma che mostra la struttura di una cache condivisa che può usare una cache privata locale come buffer.

Schema che mostra un'architettura di caching ibrida. All'estrema destra è presente un database SQL rappresentato come cilindro. Nella parte superiore destra si trova un grande cerchio etichettato "shared cache service", contenente una tabella a griglia che rappresenta la cache condivisa. Una freccia punta verso sinistra dal database SQL nella griglia del servizio cache condivisa, a indicare che il database popola la cache condivisa. In alto a sinistra si trova un grande cerchio etichettato “istanza dell’applicazione A”. All’interno del cerchio, in alto è presente un’icona a forma di ingranaggio e, sotto di essa, una tabella a griglia che rappresenta una cache privata locale. Una freccia nera a due punte collega l'icona a forma di ingranaggio alla griglia della cache locale, che indica una relazione di lettura/scrittura tra il processo dell'applicazione e la relativa cache locale. In basso a sinistra c'è un grande cerchio etichettato «istanza dell'applicazione B», con la stessa struttura interna. Dal servizio di cache condivisa, due frecce blu si estendono verso l’esterno: una è inclinata verso l’alto in direzione dell’icona a forma di ingranaggio nell’istanza dell’applicazione A, e l’altra è inclinata verso il basso in direzione dell’icona a forma di ingranaggio nell’istanza dell’applicazione B. Una linea nera rettangolare forma un percorso di fallback che si estende dal database SQL lungo i bordi superiore e destro, poi verso il basso e lungo il bordo inferiore, ricollegandosi alle griglie della cache locale all’interno di entrambe le istanze dell’applicazione. Un'etichetta nella parte inferiore centrale del diagramma legge, se il servizio cache condivisa non è disponibile, la logica dell'applicazione popola la cache locale dal database. All'estrema sinistra, una parentesi curva si estende tra le istanze dell'applicazione A e B, con una lettura di etichetta adiacente, se il servizio cache condivisa non è disponibile, le istanze dell'applicazione possono continuare a funzionare usando cache locali e private.

Per supportare cache di grandi dimensioni che contengono dati di durata relativamente lunga, alcuni servizi della cache offrono un'opzione a disponibilità elevata che implementa il failover automatico se la cache non è disponibile. Questo approccio comporta in genere la replica dei dati memorizzati nella cache archiviati in un server della cache primaria in un server cache secondario e il passaggio al server secondario se il server primario non riesce o la connettività viene persa.

Per ridurre la latenza che deriva dalla scrittura in più destinazioni, la replica nel server secondario potrebbe verificarsi in modo asincrono quando i dati vengono scritti nella cache nel server primario. Questo approccio comporta la possibilità che alcune informazioni memorizzate nella cache vadano perse in caso di errore, ma la proporzione di questi dati deve essere ridotta rispetto alle dimensioni complessive della cache.

Se una cache condivisa è di grandi dimensioni, può essere utile partizionare i dati memorizzati nella cache tra i nodi per ridurre le probabilità di contesa e migliorare la scalabilità. Molte cache condivise supportano la possibilità di aggiungere e rimuovere in modo dinamico i nodi e di ribilanciare i dati tra le partizioni. Questo approccio potrebbe comportare il clustering, in cui la raccolta di nodi viene presentata alle applicazioni client come una singola cache. Internamente, tuttavia, i dati vengono distribuiti tra i nodi seguendo una strategia di distribuzione predefinita che bilancia il carico in modo uniforme. Per altre informazioni, vedere il modello di partizionamento orizzontale.

Il clustering può anche aumentare la disponibilità della cache. Se un nodo non riesce, il resto della cache è ancora accessibile. Il clustering viene spesso usato con la replica e il failover. Ogni nodo può essere replicato e la replica può essere portata rapidamente online in caso di errore del nodo.

Molte operazioni di lettura e scrittura prevedono probabilmente valori o oggetti dati singoli. Tuttavia, a volte potrebbe essere necessario archiviare o recuperare rapidamente grandi volumi di dati. Ad esempio, il seeding di una cache potrebbe comportare la scrittura di centinaia o migliaia di elementi nella cache. Un'applicazione potrebbe anche dover recuperare un numero elevato di elementi correlati dalla cache come parte della stessa richiesta.

Molte cache su larga scala forniscono operazioni batch per questi scopi. Questa funzionalità consente a un'applicazione client di creare un pacchetto di un volume elevato di elementi in una singola richiesta, riducendo così il sovraccarico dell'esecuzione di un numero elevato di richieste di piccole dimensioni.

Memorizzazione nella cache e coerenza finale

Affinché il modello di Cache-Aside funzioni, l'istanza dell'applicazione che popola la cache deve avere accesso alla versione più recente e coerente dei dati. In un sistema che implementa la coerenza finale ,ad esempio un archivio dati replicato, questa condizione potrebbe non essere vera.

Un'istanza di un'applicazione può modificare un elemento di dati e invalidare la versione memorizzata nella cache di tale elemento. Un'altra istanza dell'applicazione potrebbe tentare di leggere questo elemento da una cache, causando un mancato riscontro nella cache. Legge quindi i dati dall'archivio dati e li aggiunge alla cache. Tuttavia, se l'archivio dati non è completamente sincronizzato con le altre repliche, l'istanza dell'applicazione potrebbe leggere e popolare la cache con il valore precedente.

Una cache distribuita introduce un altro livello a questo problema. Il teorema CAP indica che un sistema distribuito può fornire al massimo due di tre garanzie: coerenza, disponibilità e tolleranza di partizione. Poiché le partizioni di rete sono inevitabili negli ambienti cloud, è necessario scegliere tra coerenza e disponibilità. La maggior parte delle cache distribuite, tra cui Redis, assegna la priorità alla disponibilità e alla tolleranza di partizione rispetto alla coerenza assoluta. Questa priorità significa che le letture da una replica della cache potrebbero restituire dati non aggiornati durante una partizione di rete o immediatamente dopo una scrittura in un nodo diverso. Quando si progetta la strategia di memorizzazione nella cache, decidere la quantità di decadimento che l'applicazione può tollerare e impostare i valori TTL (Time-to-Live) di conseguenza. Per i dati che devono essere aggiornati, usare ITTL più brevi o ignorare completamente la cache e leggere dall'archivio dati di origine.

Per altre informazioni sulla gestione della coerenza dei dati nei sistemi distribuiti, vedere Considerazioni sui dati per i microservizi.

Proteggere i dati memorizzati nella cache

Indipendentemente dal servizio cache usato, valutare come proteggere i dati nella cache da accessi non autorizzati. Due problemi principali sono:

  • Privacy dei dati nella cache.
  • La privacy dei dati durante il flusso tra la cache e l'applicazione che usa la cache.

Per proteggere i dati nella cache, il servizio cache potrebbe implementare un meccanismo di autenticazione che richiede alle applicazioni di specificare i dettagli seguenti:

  • Quali identità possono accedere ai dati nella cache.
  • Quali operazioni di lettura e scrittura possono eseguire queste identità.

Per ridurre il sovraccarico associato alla lettura e alla scrittura dei dati, dopo che a un'identità viene concesso l'accesso in scrittura o in lettura alla cache, tale identità può usare tutti i dati nella cache.

Se è necessario limitare l'accesso ai subset dei dati memorizzati nella cache, usare uno degli approcci seguenti:

  • Suddividere la cache in partizioni usando server di cache diversi. Concedi alle identità l'accesso solo alle partizioni che sono autorizzate a utilizzare.

  • Crittografare i dati in ogni subset usando chiavi diverse. Fornire le chiavi di crittografia solo alle identità che devono avere accesso a ogni subset. Un'applicazione client potrebbe comunque essere in grado di recuperare tutti i dati nella cache, ma può decrittografare solo i dati per i quali dispone delle chiavi.

È anche necessario proteggere i dati durante il flusso nella cache e all'esterno della cache. Dipende dalle funzionalità di sicurezza fornite dall'infrastruttura di rete usata dalle applicazioni client per connettersi alla cache. Se la cache viene implementata usando un server sul sito all'interno della stessa organizzazione che ospita le applicazioni client, l'isolamento della rete stessa potrebbe non richiedere ulteriori passaggi. Se la cache si trova in modalità remota e richiede una connessione TCP o HTTP su una rete pubblica, ad esempio Internet, valutare la possibilità di implementare TLS.

Implementare la memorizzazione nella cache usando Azure Redis gestito

Le sezioni rimanenti di questo articolo descrivono come implementare i modelli di memorizzazione nella cache usando Azure Redis gestito. Redis gestito di Azure è un servizio Redis gestito che è possibile usare come cache condivisa tra istanze dell'applicazione. Supporta la memorizzazione nella cache chiave-valore, strutture di dati come set, set ordinati ed elenchi e la persistenza facoltativa per durabilità tra riavvii.

Per informazioni sui livelli disponibili, sulla pianificazione della capacità, sulla rete e sulle funzionalità, vedere la documentazione di Redis gestita di Azure.

Connettere e configurare applicazioni client

Redis supporta applicazioni client in molti linguaggi di programmazione. Per le applicazioni .NET sono disponibili diverse librerie client, ognuna adatta a carichi di lavoro Redis diversi. La scelta della libreria dipende dal fatto che si usi Redis esclusivamente come cache o come piattaforma dati multimodelli.

Per connettersi a un server Redis, usare il metodo statico Connect della ConnectionMultiplexer classe . La connessione creata da questo metodo viene creata per l'uso per tutta la durata dell'applicazione client. Più thread simultanei possono usare la stessa connessione. Non riconnettersi e disconnettersi ogni volta che si esegue un'operazione Redis perché ciò può ridurre le prestazioni.

Per esempi di connessione specifici del linguaggio, vedere Use Azure Managed Redis in .NET Core.

Scegliere una libreria client .NET

Quando si usa Azure Redis gestito per la memorizzazione nella cache, usare le librerie di .NET seguenti:

  • StackExchange.Redis: Un client Redis di basso livello che offre prestazioni elevate. Utilizzalo quando è necessario avere accesso diretto ai comandi di Redis, alle operazioni atomiche, alle transazioni, al pipelining o allo scripting Lua.

  • Microsoft. Extensions.Caching.StackExchangeRedis: Fornisce un'integrazione IDistributedCache per ASP.NET Core. Usarlo per la memorizzazione semplice nella cache chiave-valore in cui i valori vengono archiviati come matrici di byte opache. Questa astrazione non espone strutture di dati Redis avanzate.

Queste librerie forniscono le primitive necessarie per creare modelli di memorizzazione nella cache comuni, ma l'applicazione deve implementare la logica di memorizzazione nella cache stessa.

Implementare modelli di memorizzazione nella cache

Il modo più semplice per usare Redis per la memorizzazione nella cache consiste nell'archiviare i valori nelle chiavi usando il modello chiave-valore. I valori possono essere stringhe o dati binari di lunghezza arbitraria, rendendo Redis particolarmente adatto per la memorizzazione nella cache di oggetti serializzati, dati di configurazione, stato sessione o risultati precompilate.

Progetta il tuo keyspace con attenzione e usa chiavi significative (ma non prolisse). Ad esempio, usare chiavi strutturate come customer:100 (anziché solo 100) per rappresentare la chiave per il cliente con ID 100. Questo schema consente di distinguere tra valori che archivia tipi di dati diversi. Ad esempio, è anche possibile usare la chiave orders:100 per rappresentare la chiave per l'ordine con ID 100.

Sebbene le stringhe siano l'approccio di memorizzazione nella cache più comune, Redis supporta un set completo di tipi di dati nativi, ad esempio hash, elenchi, set, set ordinati e flussi, che consentono modelli di memorizzazione nella cache più flessibili. Per altre informazioni sui tipi di dati Redis, vedere la documentazione di Redis sui tipi di dati.

Implementare il modello Cache-Aside

Come descritto in Determinare come memorizzare nella cache i dati in modo efficace, un approccio comune consiste nel caricare i dati nella cache su richiesta. L'esempio seguente controlla prima la cache, recupera dall'origine dati in caso di mancato riscontro e archivia il risultato per le richieste successive:

var config = new ConfigurationOptions();
// ... configure endpoint, credentials, TLS, etc.
ConnectionMultiplexer redisHostConnection = ConnectionMultiplexer.Connect(config);
IDatabase cache = redisHostConnection.GetDatabase();

async Task<string> RetrieveItemAsync(string itemKey)
{
    // Attempt to retrieve the item from the Redis cache
    string itemValue = await cache.StringGetAsync(itemKey);

    // If the value returned is null, the item was not found in the cache
    // So retrieve the item from the data source and add it to the cache
    if (itemValue is null)
    {
        itemValue = await GetItemFromDataSourceAsync(itemKey);
        await cache.StringSetAsync(itemKey, itemValue);
    }

    return itemValue;
}

Eseguire operazioni atomiche e batch

Quando più client o istanze dell'applicazione condividono una cache, è necessario impedire agli aggiornamenti simultanei di danneggiare i dati. Le strategie generali di concorrenza sono descritte in Gestione della concorrenza in una cache più indietro in questo articolo. Redis offre diversi meccanismi che implementano tali strategie.

  • Operazioni atomiche su una singola chiave: Utilizza i comandi per aggiornare un valore in un unico passaggio, eliminando le condizioni di competizione che si verificano quando GET e SET vengono eseguiti separatamente.

    • INCR, INCRBY, DECR, incrementa o decrementa in modo atomico un valore numerico. In StackExchange.Redis usare IDatabase.StringIncrementAsync e IDatabase.StringDecrementAsync. Questi comandi sono utili per contatori, limiti di frequenza e rilevamento delle quote in cui più client aggiornano contemporaneamente la stessa chiave.

    • GETSET imposta atomicamente una chiave su un nuovo valore e restituisce il valore precedente. In StackExchange.Redis usare IDatabase.StringGetSetAsync:

      string oldValue = await cache.StringGetSetAsync("data:counter", 0);
      
  • Operazioni multi-chiave:MGET e MSET leggono o scrivono più valori di tipo stringa in una singola andata e ritorno, riducendo il sovraccarico di rete quando è necessario lavorare con più chiavi contemporaneamente. I metodi IDatabase.StringGetAsync e IDatabase.StringSetAsync sono sovraccaricati per supportare questa funzionalità.

    // Create a list of key-value pairs
    var keysAndValues =
        new KeyValuePair<RedisKey, RedisValue>[]
        {
            new("data:key1", "value1"),
            new("data:key99", "value2"),
            new("data:key322", "value3")
        };
    
    // Store the list of key-value pairs in the cache
    await cache.StringSetAsync(keysAndValues);
    ...
    // Find all values that match a list of keys
    RedisKey[] keys = ["data:key1", "data:key99", "data:key322"];
    // Values should contain { "value1", "value2", "value3" }
    RedisValue[] values = await cache.StringGetAsync(keys);
    
  • Transazioni (concorrenza ottimistica): È possibile usare il WATCH comando per monitorare una o più chiavi prima di avviare una transazione conMULTI/EXEC . Se una chiave osservata cambia prima dell'avvio della transazione, Redis rimuove la transazione e il client può riprovare. La libreria StackExchange fornisce supporto per le transazioni tramite l'interfaccia ITransaction .

    Per creare un ITransaction oggetto, usare il IDatabase.CreateTransaction metodo . I comandi vengono richiamati nella transazione usando i metodi forniti dall'oggetto ITransaction.

    L'interfaccia ITransaction fornisce l'accesso a un set di metodi simili ai metodi a cui si accede dall'interfaccia IDatabase , ad eccezione del fatto che tutti i metodi sono asincroni. Ciò significa che vengono eseguite solo quando viene richiamato il ITransaction.Execute metodo . Il valore restituito dal ITransaction.Execute metodo indica se la transazione è stata creata correttamente (true) o se ha avuto esito negativo (false).

    Il frammento di codice seguente mostra un esempio che incrementa e decrementa due contatori come parte della stessa transazione:

    ITransaction transaction = cache.CreateTransaction();
    
    var tx1 = transaction.StringIncrementAsync("data:counter1");
    var tx2 = transaction.StringDecrementAsync("data:counter2");
    
    bool result = await transaction.ExecuteAsync();
    
    Console.WriteLine($"Transaction {(result ? "succeeded" : "failed")}");
    
    if (result)
    {
        long increment = await tx1;
        long decrement = await tx2;
    
        Console.WriteLine($"Result of increment: {increment}");
        Console.WriteLine($"Result of decrement: {decrement}");
    }
    

    Le transazioni Redis sono diverse dalle transazioni nei database relazionali. Il Execute metodo accoda tutti i comandi che costituiscono la transazione da eseguire e, se un comando non è valido, la transazione viene arrestata. Se tutti i comandi vengono accodati correttamente, ogni comando viene eseguito in modo asincrono. Se un comando ha esito negativo, gli altri continuano comunque l'elaborazione. Se è necessario verificare che un comando sia stato completato correttamente, recuperare i risultati usando la Result proprietà dell'attività corrispondente, come illustrato nell'esempio precedente.

  • Scripting Lua. Per gli aggiornamenti con più passaggi che devono essere atomici tra più chiavi, è possibile eseguire uno script Lua nel server. Redis esegue l'intero script come un'unica operazione senza l'interpolazione di altri comandi.

    Note

    Nelle distribuzioni in cluster, tutte le chiavi coinvolte in una transazione o uno script Lua devono trovarsi nello stesso slot hash. Usare tag hash, customer:{123}:name ad esempio o customer:{123}:email per la colocazione di chiavi correlate.

Eseguire operazioni fire-and-forget sulla cache

Quando un aggiornamento della cache non influisce sulla correttezza dell'applicazione, ad esempio l'incremento di un contatore di visualizzazione o l'aggiornamento di una statistica non critica, è possibile ignorare l'attesa della risposta del server. In un'operazione della cache di tipo fire-and-forget, l'applicazione avvia un'attività in background e prosegue senza attendere il completamento dell'attività. Redis supporta operazioni di tipo fire-and-forget, che riducono la latenza di andata e ritorno per il client, tramite flag di comando:

await cache.StringSetAsync("data:key1", 99);
...
cache.StringIncrement("data:key1", flags: CommandFlags.FireAndForget);

Specificare automaticamente le chiavi in scadenza

Le strategie di scadenza descritte in Gestire la scadenza dei dati in una cache vengono implementate in Redis tramite TRL per chiave. Quando si archivia un elemento in una cache Redis, è possibile specificare un timeout dopo il quale l'elemento viene rimosso automaticamente. È anche possibile eseguire una query sul tempo di scadenza di una chiave usando il TTL comando . Questo comando è disponibile per le applicazioni StackExchange tramite il IDatabase.KeyTimeToLive metodo .

Il frammento di codice seguente illustra come impostare una scadenza di 20 secondi su una chiave ed eseguire una query sulla durata rimanente della chiave:

// Add a key with an expiration time of 20 seconds
await cache.StringSetAsync("data:key1", 99, TimeSpan.FromSeconds(20));
...
// Query how much time a key has left to live
// If the key has already expired, the KeyTimeToLive function returns null
TimeSpan? expiry = cache.KeyTimeToLive("data:key1");

È anche possibile impostare la scadenza su una data e un'ora specifiche usando il EXPIREAT comando , disponibile nella libreria StackExchange come KeyExpireAsync metodo . Accetta un DateTime parametro:

await cache.StringSetAsync("data:key1", 99);
await cache.KeyExpireAsync("data:key1",
    new DateTime(2026, 9, 1, 0, 0, 0, DateTimeKind.Utc));

Tip

È possibile rimuovere manualmente un elemento dalla cache usando il DEL comando , disponibile tramite la libreria StackExchange come IDatabase.KeyDeleteAsync metodo .

Quando Redis raggiunge il limite di memoria, rimuove le chiavi in base a un criterio di rimozione configurato. Il criterio predefinito è volatile-lru, che rimuove la chiave usata meno di recente con un valore TTL impostato. Altri criteri includono allkeys-lru, volatile-randome noeviction (che causa l'esito negativo delle operazioni di scrittura quando la memoria è piena). Scegliere un criterio di rimozione in base al fatto che l'applicazione usi TTLs in modo coerente e se si preferisce proteggere le chiavi che non hanno scadenza. Per altre informazioni, vedere Gestione della memoria.

Correlare incrociatamente gli elementi memorizzati nella cache

Quando si memorizzano nella cache gli elementi correlati, spesso è necessario trovarli in base alla relazione anziché alla sola chiave primaria. Ad esempio, è possibile memorizzare nella cache i post di blog e dover rispondere a query come "quali post condividono tag Y?" o "quali tag appartengono al post X?"

In Redis gestito di Azure, l'approccio consigliato consiste nell'usare RedisJSON e RediSearch. Archiviare ogni elemento memorizzato nella cache come documento JSON con i relativi metadati, quindi creare un indice RediSearch sui campi su cui è necessario eseguire query. RediSearch gestisce ricerche inversi, filtri basati su tag, query di intervallo e ricerca full-text senza richiedere all'applicazione di mantenere strutture di indice separate.

Per scenari più semplici, è anche possibile usare set Redis per compilare manualmente indici in avanti e inverso. Archiviare un set per post (contenente i relativi tag) e un set per tag (contenente gli ID dei post):

foreach (BlogPost post in posts)
{
    string postTagsKey = $"blog:posts:{post.Id}:tags";
    await cache.SetAddAsync(
        postTagsKey, post.Tags.Select(s => (RedisValue)s).ToArray());

    foreach (var tag in post.Tags)
    {
        await cache.SetAddAsync($"tag:{tag}:blog:posts", post.Id);
    }
}

È quindi possibile eseguire query sui tag per un post usando SetMembersAsync, trovare tag comuni tra i post usando SetCombineAsync(SetOperation.Intersect, ...)o trovare tutti i post per un determinato tag. Il compromesso è che l'applicazione deve mantenere sia l'insieme diretto sia quello inverso, il che aumenta la complessità con l'aumentare del numero di relazioni.

Trova i documenti a cui hai recentemente avuto accesso

Molte applicazioni devono tenere traccia degli elementi a cui si accede o visualizzati più di recente. Ad esempio, un sito di blogging potrebbe visualizzare i post letti più di recente a un visitatore di ritorno. Gli elenchi Redis offrono un modo efficiente per implementare modelli di memorizzazione nella cache basati sulla recency. Gli elementi possono essere inseriti in una delle estremità dell'elenco usando LPUSH o RPUSHe rimossi tramite LPOP o RPOP. Usare LTRIM per limitare la lunghezza dell'elenco e impedire la crescita illimitata della memoria.

Implementare un tabellone punteggi

I set ordinati Redis (ZSETs) mantengono classificazioni ordinate associando ogni elemento a un punteggio numerico. Redis mantiene automaticamente l'ordinamento. ZADD è O(log N)e le query di intervallo, ZRANGE ad esempio e ZREVRANGE sono O(log N + M),dove M è il numero di elementi restituiti, quindi i set ordinati rimangono efficienti anche con conteggi di elementi di grandi dimensioni.

Aggiungere elementi a un tabellone punteggi

L'esempio seguente illustra come aggiungere un post di blog e il relativo punteggio a una classifica usando il ZADD comando tramite SortedSetAddAsync:

var db = connection.GetDatabase();
string redisKey = "blog:post_rankings";

BlogPost blogPost = ...; // The blog post being ranked

await db.SortedSetAddAsync(redisKey, blogPost.Title, blogPost.Score);
Recuperare gli elementi classificati

È possibile recuperare gli elementi in ordine di punteggio crescente usando SortedSetRangeByRankWithScoresAsync:

var entries = await db.SortedSetRangeByRankWithScoresAsync(redisKey);

foreach (var entry in entries)
{
    Console.WriteLine($"{entry.Element}: {entry.Score}");
}

Note

SortedSetRangeByRankAsync restituisce solo i valori dei membri, non i punteggi.

Recuperare gli elementi primi N

Per ottenere gli elementi con punteggio più alto, ad esempio i primi 10 post, usare l'ordine decrescente:

foreach (var post in await cache.SortedSetRangeByRankWithScoresAsync(
                               redisKey, 0, 9, Order.Descending))
{
    Console.WriteLine(post);
}
Recuperare gli elementi in base all'intervallo di punteggi

È anche possibile eseguire query sugli elementi in base ai limiti del punteggio anziché alla classificazione:

foreach (var post in await cache.SortedSetRangeByScoreWithScoresAsync(
                               redisKey, 5000, 100000))
{
    Console.WriteLine(post);
}

Per evitare che una classifica cresca indefinitamente, rimuovi le voci meno recenti usando SortedSetRemoveRangeByRankAsync oppure usa chiavi con ambito temporale (ad esempio, classifiche giornaliere o settimanali). È possibile aggiornare i punteggi in modo atomico usando SortedSetIncrementAsync (ZINCRBY).

Memorizzare nella cache lo stato della sessione e l'output HTML

È possibile usare Azure Redis gestito per archiviare lo stato della sessione e i dati della cache di output per le applicazioni ASP.NET Core e ASP.NET. Quando si mantengono i dati della sessione e viene eseguito il rendering dell'output in una cache condivisa basata su Redis, le applicazioni in esecuzione in più istanze, ad esempio in Servizio app di Azure, Servizio Azure Kubernetes (AKS), App contenitore di Azureo i set di scalabilità di macchine virtuali possono mantenere esperienze utente coerenti senza richiedere affinità server.

Tip

Per ottenere prestazioni ottimali, distribuire l'applicazione e l'istanza di Redis gestita di Azure nella stessa area di Azure.

ASP.NET Core

Le applicazioni ASP.NET Core usano l'astrazione IDistributedCache e il middleware di sessione. Azure Managed Redis si integra con IDistributedCache tramite il Microsoft.Extensions.Caching.StackExchangeRedis pacchetto .

builder.Services.AddStackExchangeRedisCache(options =>
{
    options.Configuration = "<your-cache-name>.<region>.redis.azure.net:10000";
    options.InstanceName = "app-cache:";
});

builder.Services.AddSession();

Il middleware di memorizzazione nella cache dell'output di ASP.NET Core può anche usare Redis come archivio back-end distribuito, consentendo alle applicazioni di condividere frammenti o pagine già sottoposti a rendering tra tutte le istanze. Per altre informazioni, vedere provider di cache di output di ASP.NET Core per Redis.

integrazione di .NET Aspire

Le applicazioni .NET Aspire possono usare il Aspire.Hosting.Azure.Redis pacchetto per dichiarare una risorsa Redis gestita di Azure nell'host dell'app. I progetti che utilizzano ricevono automaticamente la configurazione della connessione tramite l'inserimento delle dipendenze, eliminando la gestione manuale delle stringhe di connessione tra i servizi.

// App host: declare the Azure Managed Redis resource
var cache = builder.AddAzureManagedRedis("cache");

builder.AddProject<Projects.ProductService>()
    .WithReference(cache);

I servizi che utilizzano registrano la cache distribuita nello stesso modo di qualsiasi altro provider IDistributedCache. Per altre informazioni, vedere Introduzione all'integrazione di Redis.

Disponibilità elevata, scalabilità e partizionamento

Ogni istanza Azure Managed Redis usa la replicazione primaria/secondaria. Il servizio monitora l'integrità dei nodi e promuove automaticamente una replica in caso di errore del database primario. Poiché la replica è asincrona, una piccola quantità di dati scritti di recente può essere persa durante un failover imprevisto. Per le strategie generali alla base della replica, del failover e della memorizzazione nella cache a più livelli, vedere Implementare disponibilità elevata e scalabilità e migliorare le prestazioni in precedenza in questo articolo.

È possibile combinare una cache in memoria locale con Redis gestita di Azure per ridurre la latenza e fornire un fallback se la cache condivisa è temporaneamente non raggiungibile. Il modelloCircuit-Breaker e il modello diCache-Aside consentono di gestire questo approccio a più livelli.

Per i carichi di lavoro che superano la capacità di un singolo nodo, Azure Managed Redis supporta il partizionamento (partizionamento orizzontale) dei dati tra più nodi Redis. Con entrambi i criteri di clustering, i dati vengono partizionati automaticamente tra i nodi usando l'hash da chiave a partizione, il failover automatico e la risincronizzazione e il partizionamento orizzontale online (scale-out e scale-in). Redis gestito di Azure supporta due criteri di clustering:

  • Criteri di clustering oss (impostazione predefinita): I client comunicano direttamente con la partizione appropriata e seguono la semantica del cluster Redis OSS, inclusi i reindirizzamenti MOVED e ASK. I client con riconoscimento del cluster, ad esempio StackExchange.Redis, gestiscono automaticamente questi reindirizzamenti. Questo criterio offre il sovraccarico di routing più basso.

  • Criteri di Clustering aziendale Redis: Un proxy fornisce un routing trasparente tramite un singolo endpoint. I client non devono implementare la logica compatibile con il cluster o gestire le risposte MOVED/ASK. Questo criterio offre un'integrazione client più semplice, ma introduce una piccola quantità di sovraccarico di routing.

Azure Managed Redis supporta anche la modalità non cluster, che utilizza una singola coppia primaria/replica senza partizionamento orizzontale. Questa modalità è adatta per carichi di lavoro più piccoli che non richiedono scalabilità orizzontale.

Note

I modelli di partizionamento personalizzati, ad esempio l'hashing sul lato client o i proxy non Microsoft, sono in genere necessari solo nelle distribuzioni Redis autogestiti in macchine virtuali o Kubernetes. Il clustering gestito di Azure per Redis gestisce automaticamente il routing, il failover e il partizionamento orizzontale.

Replica geografica attiva

Per la disponibilità di più aree, Azure Redis gestito supporta la replica geografica attiva, che collega le istanze tra le aree Azure in un singolo gruppo di replica. Ogni istanza può gestire le letture e le scritture e le modifiche vengono sincronizzate automaticamente. Un'applicazione è responsabile del reindirizzamento del traffico a un'istanza operativa durante un guasto regionale. Per altre informazioni, vedere Configurare la replica geografica attiva.

Salvataggio permanente dei dati

Per impostazione predefinita, i dati memorizzati nella cache in Redis gestiti di Azure vengono mantenuti in memoria e possono essere persi se un nodo viene riavviato o viene eseguito il failover. Per i carichi di lavoro in cui la ricompilazione della cache dall'archivio dati di origine sarebbe lenta o costosa, Azure Managed Redis supporta la persistenza facoltativa dei dati:

  • Le snapshot RDB (database Redis) creano periodicamente snapshot puntuali salvate in un disco gestito. RDB ha un effetto minimo sulle prestazioni durante le normali operazioni, ma i dati scritti dopo l'ultimo snapshot possono essere persi.

  • AOF (Append-Only File) registra ogni operazione di scrittura su disco. AOF riduce la potenziale perdita di dati a circa un secondo di scrittura, ma produce file di dimensioni maggiori e può ridurre la velocità effettiva di scrittura.

È possibile usare sia RDB che AOF insieme. Redis carica lo snapshot RDB all'avvio e quindi riproduce il log AOF per il ripristino quasi completo.

Important

La persistenza migliora la durabilità rispetto agli errori dei nodi, ma non è un meccanismo di backup o ripristino di emergenza. Per i dati critici, mantenere sempre la copia autorevole nell'archivio dati di origine e usare il modello diCache-Aside per ripopolare la cache.

Per informazioni dettagliate sulla configurazione, vedere Configurare la persistenza dei dati.

Proteggere i dati memorizzati nella cache in Redis gestito di Azure

Le linee guida in Proteggere i dati memorizzati nella cache descrivono i problemi di controllo di accesso e di transito dei dati. Azure Redis gestito consente di risolvere questi problemi nei modi seguenti:

  • Usare l'autenticazione di Microsoft Entra ID come meccanismo di controllo di accesso primario e seguire il principio dei privilegi minimi quando si concede l'accesso.

  • Usare gli endpoint privati per limitare l'accesso alla rete in modo che il traffico non attraversi la rete Internet pubblica.

  • Azure Managed Redis crittografa i dati in transito tramite TLS e i dati archiviati.

Considerazioni sulla serializzazione

Quando si archiviano oggetti .NET in Redis come valori stringa, è necessario serializzarli. Quando si sceglie un formato di serializzazione, valutare i compromessi tra prestazioni, interoperabilità, controllo delle versioni e dimensioni del payload. Non esiste un singolo serializzatore più veloce per tutti gli scenari. I benchmark dipendono fortemente dal contesto e potrebbero non riflettere il carico di lavoro effettivo.

Se il livello Redis gestito di Azure supporta RedisJSON, è possibile archiviare gli oggetti come documenti JSON nativi ed eseguire query su singoli campi senza deserializzare l'intero valore:

public static class RedisJsonExtensions
{
    public static async Task<T?> GetAsync<T>(
        this IDatabase cache,
        string key,
        string path = "$")
    {
        var result = await cache.ExecuteAsync("JSON.GET", key, path);

        if (result.IsNull)
            return default;

        return JsonSerializer.Deserialize<T>(result!);
    }

    public static async Task SetAsync<T>(
        this IDatabase cache,
        string key,
        T value,
        TimeSpan? expiry = null,
        string path = "$")
    {
        var json = JsonSerializer.Serialize(value);

        // Store JSON document
        await cache.ExecuteAsync("JSON.SET", key, path, json);

        // Apply TTL if provided
        if (expiry.HasValue)
        {
            await cache.KeyExpireAsync(key, expiry);
        }
    }

    public static async Task<bool> ExpireAsync(
        this IDatabase cache,
        string key,
        TimeSpan expiry)
    {
        return await cache.KeyExpireAsync(key, expiry);
    }
}

Quando si serializzano i valori come stringhe Redis, invece, le opzioni di formato comuni includono:

  • JSON : formato leggibile con ampio supporto multipiattaforma. Non il formato più compatto, ma una buona scelta quando gli elementi memorizzati nella cache vengono restituiti direttamente ai client HTTP perché evita un passaggio di deserializzazione e reinirializzazione aggiuntivo.

  • MessagePack : formato binario compatto senza requisiti di schema. Produce payload più piccoli rispetto a JSON con un sovraccarico di serializzazione inferiore.

  • Protocol Buffers (protobuf): formato binario basato su uno schema che produce payload compatti. Richiede .proto file di definizione e un passaggio di compilazione per generare codice specifico del linguaggio.

  • BSON : formato binario che estende JSON con più tipi, ad esempio date e dati binari non elaborati. I payload sono paragonabili alle dimensioni di JSON. Una scelta pratica quando l'applicazione usa già BSON altrove, ad esempio con MongoDB.

I modelli seguenti potrebbero essere rilevanti quando si implementa la memorizzazione nella cache nelle applicazioni:

  • Cache-Aside modello: questo modello descrive come caricare dati su richiesta in una cache da un archivio dati. Consente inoltre di mantenere la coerenza tra i dati nella cache e i dati nell'archivio dati originale.

  • Modello di partizionamento orizzontale: questo modello fornisce informazioni sull'implementazione del partizionamento orizzontale per migliorare la scalabilità durante l'archiviazione e l'accesso a grandi volumi di dati.