Looking for help?
< All Topics
Stampa

Redis vs. Memcached: sistemi di archiviazione dati in memoria

Redis e Memcached sono entrambi sistemi di archiviazione dati in memoria. Memcached è un servizio di cache di memoria distribuita ad alte prestazioni e Redis è un archivio chiave-valore open source. Simile a Memcached, Redis archivia la maggior parte dei dati in memoria. Supporta operazioni su vari tipi di dati, tra cui stringhe, tabelle hash ed elenchi collegati. La famiglia InfraWP supporta questi due popolari sistemi di archiviazione dati. In questo articolo esamineremo la differenza tra Redis e Memcached.

Confronto delle caratteristiche

Salvatore Sanfilippo, l’autore di Redis, ha condiviso i seguenti punti di confronto tra Redis e Memcached:

Operazioni sui dati dell’estremità del server

Redis supporta operazioni sui dati del server, possiede più strutture di dati e supporta operazioni sui dati più ricche rispetto a Memcached. In Memcached, di solito è necessario copiare i dati sul lato client per modifiche simili e quindi reimpostare i dati. Il risultato è che questo aumenta notevolmente il numero di I/O di rete e le dimensioni dei dati. In Redis, queste operazioni complicate sono efficienti quanto le operazioni generali GET/SET. Pertanto, se hai bisogno della cache per supportare strutture e operazioni più complicate, Redis è una buona scelta.

Confronto dell’efficienza dell’uso della memoria

Memcached ha un tasso di utilizzo della memoria più elevato per l’archiviazione semplice di valori-chiave. Ma se Redis adotta la struttura hash, avrà un tasso di utilizzo della memoria più elevato rispetto a Memcached grazie alla sua modalità di compressione combinata.

Confronto delle prestazioni

Redis utilizza solo core singoli mentre Memcached utilizza core multipli. Quindi, in media, Redis vanta prestazioni superiori rispetto a Memcached nello storage di dati di piccole dimensioni se misurato in termini di core. Memcached supera Redis per l’archiviazione di dati di 100k o superiore. Sebbene Redis abbia anche apportato alcune ottimizzazioni per l’archiviazione di big data, è ancora inferiore a Memcached.

Discutiamo ora alcuni punti a sostegno dei confronti di cui sopra.

Diversi tipi di dati supportati

A differenza di Memcached che supporta solo i record di dati della semplice struttura chiave-valore, Redis supporta tipi di dati molto più ricchi, inclusi String, Hash, List, Set e Sorted Set. Redis utilizza internamente un redisObject per rappresentare tutte le chiavi e i valori. Le informazioni primarie del redisObject sono come mostrato di seguito:

Type rappresenta il tipo di dati di un oggetto valore. La codifica indica il metodo di memorizzazione di diversi tipi di dati in Redis, ad esempio type=string rappresenta che il valore memorizza una stringa generale e la codifica corrispondente può essere raw o int. Se è int, Redis archivia e rappresenta la stringa associata come tipo di valore. Ovviamente la premessa è che sia possibile rappresentare la stringa con un valore, come stringhe di “123″ e “456”. Solo dopo aver abilitato la funzione di memoria virtuale Redis, allocherà i campi vm con la memoria. Questa funzione è disattivata per impostazione predefinita. Ora parliamo di alcuni tipi di dati.

Corda

Comandi usati di frequente: set/get/decr/incr/mget e così via.

Scenari applicativi: la stringa è il tipo di dati più comune e la chiave/valore generale appartiene a questa categoria.

Metodo di implementazione: String è una stringa di caratteri per impostazione predefinita in Redis a cui fa riferimento redisObject. Quando viene chiamato per le operazioni INCR o DECR, il sistema lo convertirà nel tipo di valore per il calcolo. A questo punto, il campo di codifica di redisObject è int.

Hash

Comandi usati di frequente: hget/hset/hgetall e così via.

Scenari applicativi: memorizzazione dei dati dell’oggetto informazioni utente, inclusi ID utente, nome utente, età e compleanno; recuperare il nome utente, l’età o la data di nascita tramite l’ID utente.

Metodo di implementazione: Hash in Redis è una HashMap del valore memorizzato internamente e fornisce l’interfaccia per l’accesso diretto a questo membro della mappa. Come mostrato in figura, la Chiave è l’ID utente e il Valore è una mappa. La chiave di questa mappa è il nome dell’attributo del membro e il valore è il valore dell’attributo. In questo modo è possibile eseguire direttamente le modifiche e accedere ai dati tramite la chiave della mappa interna (In Redis la chiave della mappa interna è chiamata campo), ovvero tramite la chiave (ID utente) + campo (tag attributo) per eseguire operazioni sui dati di attributo corrispondenti.

Ci sono due modi di implementazione per l’attuale HashMap: quando ci sono solo pochi membri nell’HashMap, Redis opta per array unidimensionali per l’archiviazione compatta per risparmiare memoria, invece della struttura HashMap nel vero senso della parola. A questo punto, la codifica del valore corrispondente redisObject è zipmap. Quando il numero dei membri aumenterà, Redis li convertirà nella HashMap nel vero senso della parola e la codifica in questo momento sarà ht.

Elenco

Comandi usati di frequente: lpush/rpush/lpop/rpop/lrange.

Scenari applicativi: l’elenco Redis è la struttura dati più importante in Redis. Infatti, è possibile implementare il seguente elenco di Twitter e l’elenco dei fan utilizzando la struttura dell’elenco di Redis.

Metodo di implementazione: tramite un elenco collegato a due vie che supporta le ricerche inverse e l’attraversamento per facilitare le operazioni. Ma comporta anche un sovraccarico di memoria aggiuntivo. Molte implementazioni in Redis, incluso l’invio di code di buffering, adottano anche questa struttura di dati.

Set

Comandi usati di frequente: sadd/spop/smembers/sunion e così via.

Scenari applicativi: il set Redis fornisce una funzione di elenco esterna simile a quella di elenco. È speciale in quanto il set può rimuovere automaticamente i duplicati. Quando è necessario archiviare un elenco di dati senza alcuna duplicazione, il set è una buona opzione. Inoltre, il set fornisce un’interfaccia importante per giudicare se un membro è all’interno di un set, una funzionalità non fornita dall’elenco.

Metodo di implementazione: l’implementazione interna di set è una HashMap il cui valore è sempre nullo. In realtà, rimuove rapidamente i duplicati calcolando i valori hash. In effetti, questo è anche il motivo per cui il set può giudicare se un membro è all’interno del set.

Set ordinato

Comandi usati di frequente: zadd/zrange/zrem/zcard e così via.

Scenari applicativi: Gli scenari applicativi del set ordinato Redis sono simili a quelli del set. La differenza è che mentre set non ordina automaticamente i dati, il set ordinato può ordinare i membri tramite un parametro di priorità (punteggio) fornito dall’utente. Inoltre, quest’ultimo ordina automaticamente anche i dati inseriti. Puoi scegliere la struttura dei dati del set ordinato quando hai bisogno di un elenco di set ordinato senza dati duplicati, come la timeline pubblica di Twitter, che può richiedere il tempo di pubblicazione come punteggio per l’archiviazione con l’ordinamento automatico dei dati ottenuti in base al tempo.

Metodo di implementazione: il set ordinato di Redis utilizza HashMap e SkipList internamente per garantire l’archiviazione e l’ordine efficienti dei dati. HashMap memorizza le mappature tra il membro e il punteggio; mentre la SkipList memorizza tutti i membri. L’ordinamento si basa sul punteggio memorizzato nella HashMap. L’utilizzo della struttura SkipList può migliorare l’efficienza della ricerca e semplificare l’implementazione.

Schema di gestione della memoria diverso

In Redis, non tutta la memorizzazione dei dati avviene in memoria. Questa è una grande differenza tra Redis e Memcached. Quando la memoria fisica è piena, Redis può scambiare i valori non utilizzati per lungo tempo sul disco. Redis memorizza nella cache solo tutte le informazioni chiave. Se rileva che l’utilizzo della memoria supera il valore di soglia, attiverà l’operazione di scambio. Redis calcola i valori per le chiavi da scambiare sul disco in base a “swappability = age*log(size_in_memory)”. Quindi rende questi valori per le chiavi persistenti nel disco e li cancella dalla memoria. Questa funzione consente a Redis di mantenere dati di dimensioni maggiori della capacità di memoria della macchina. La memoria della macchina deve conservare tutte le chiavi e non scambierà tutti i dati.

Allo stesso tempo, quando Redis scambia i dati in memoria sul disco, il thread principale che fornisce i servizi e il thread figlio per l’operazione di scambio condivideranno questa parte di memoria. Quindi, se aggiorni i dati che intendi scambiare, Redis bloccherà questa operazione, impedendo l’esecuzione di tale modifica fino a quando il thread figlio non avrà completato l’operazione di scambio. Quando si leggono dati da Redis, se il valore della chiave di lettura non è in memoria, Redis deve caricare i dati corrispondenti dal file di scambio e poi restituirli al richiedente. Qui, c’è un problema del pool di thread di I/O. Per impostazione predefinita, Redis incontrerà una congestione, ovvero risponderà solo dopo aver caricato correttamente tutti i file di scambio. Questa politica è adatta per le operazioni batch quando è presente un numero limitato di client. Ma se si applica Redis in un grande programma di siti Web, non è in grado di soddisfare le elevate esigenze di concorrenza. Tuttavia, è possibile impostare la dimensione del pool di thread di I/O per l’esecuzione di Redis ed eseguire operazioni simultanee per leggere le richieste di caricamento dei dati corrispondenti nel file di scambio per ridurre il tempo di congestione.

Per i sistemi di database basati sulla memoria come Redis e Memcached, l’efficienza della gestione della memoria è un fattore chiave che influenza le prestazioni del sistema. Nel linguaggio C tradizionale, le funzioni malloc/free sono il metodo più comune per distribuire e rilasciare la memoria. Tuttavia, questo metodo nasconde un enorme difetto: in primo luogo, per gli sviluppatori, malloc e free senza pari causeranno facilmente perdite di memoria; in secondo luogo, le chiamate frequenti renderanno difficile riciclare e riutilizzare molti frammenti di memoria, riducendo l’utilizzo della memoria; e infine, le chiamate di sistema consumeranno un sovraccarico di sistema molto maggiore rispetto alle chiamate di funzione generali. Pertanto, per migliorare l’efficienza della gestione della memoria, le soluzioni di gestione della memoria non utilizzeranno direttamente le chiamate malloc/free. Sia Redis che Memcached adottano i propri meccanismi di gestione della memoria autoprogettati, ma i metodi di implementazione variano molto. Successivamente introduciamo questi due meccanismi.

Memcached utilizza il meccanismo Slab Allocation per la gestione della memoria per impostazione predefinita. La sua filosofia principale è quella di segmentare la memoria allocata in blocchi di una lunghezza specifica predefinita per memorizzare i record di dati chiave-valore della lunghezza corrispondente per risolvere completamente il problema del frammento di memoria. Idealmente, la progettazione del meccanismo di allocazione della lastra dovrebbe garantire l’archiviazione esterna dei dati, vale a dire facilitare la memorizzazione di tutti i dati chiave-valore nel sistema di allocazione della lastra. Tuttavia, l’applicazione di altre richieste di memoria di Memcached avviene tramite chiamate malloc/gratuite generali. Normalmente, ciò è dovuto al numero e alla frequenza di queste richieste che determinano che non influenzeranno le prestazioni complessive del sistema. Il principio dell’allocazione della lastra è molto semplice. Come mostrato nella figura, si applica prima a una massa di memoria dal sistema operativo e la segmenta in blocchi di varie dimensioni, quindi raggruppa i blocchi della stessa dimensione nella classe Slab. Tra questi, il blocco è l’unità più piccola per la memorizzazione dei dati chiave-valore. È possibile controllare la dimensione di ogni Classe Slab creando un Fattore di Crescita all’avvio di Memcached. Supponiamo che il fattore di crescita nella figura sia 1,25. Se il blocco nel primo gruppo ha una dimensione di 88 byte, il blocco nel secondo gruppo sarà di 112 byte. I pezzi rimanenti seguono la stessa regola.

Quando Memcached riceve i dati inviati dal client, seleziona prima la Classe Slab più appropriata in base alla dimensione dei dati, quindi interroga l’elenco dei blocchi inattivi contenente la Classe Slab in Memcached per individuare un blocco per l’archiviazione dei dati. Quando un dato scade o è obsoleto, e quindi scartato, è possibile riciclare il pezzo originariamente occupato dal record e riportarlo nella lista inattiva.

Dal processo di cui sopra, possiamo vedere che Memcached ha un’efficienza di gestione della memoria molto elevata che non causerà frammenti di memoria. Il suo più grande difetto, tuttavia, è che può causare spreco di spazio. Poiché il sistema alloca ogni blocco nello spazio di memoria di una lunghezza specifica, i dati più lunghi potrebbero non riuscire a utilizzare completamente lo spazio. Come mostrato nella figura, quando mettiamo nella cache dati di 100 byte in un blocco di 128 byte, i 28 byte inutilizzati vanno sprecati.

L’implementazione della gestione della memoria di Redis procede principalmente attraverso i due file zmalloc.h e zmalloc.c nel codice sorgente. Per facilitare la gestione della memoria, Redis memorizzerà la dimensione della memoria nell’intestazione del blocco di memoria dopo l’allocazione della memoria. Come mostrato nella figura, real_ptr è il puntatore restituito dopo che Redis ha chiamato malloc. Redis memorizza la dimensione del blocco di memoria nell’intestazione e la memoria occupata dalla dimensione è determinabile, ovvero il sistema restituisce la lunghezza del tipo size_t e quindi ret_ptr. Quando si presenta la necessità di rilasciare memoria, il sistema passa ret_ptr al programma di gestione della memoria. Tramite ret_ptr, il programma può facilmente calcolare il valore di real_ptr e quindi passare real_ptr per liberare la memoria.

Redis registra la distribuzione di tutta la memoria definendo un array la cui lunghezza è ZMALLOC_MAX_ALLOC_STAT. Ogni elemento nell’array rappresenta il numero di blocchi di memoria allocati dal programma corrente e la dimensione del blocco di memoria è il pedice dell’elemento. Nel codice sorgente, questo array è zmalloc_allocations. zmalloc_allocations[16] rappresenta il numero di blocchi di memoria allocati con la lunghezza di 16 byte. zmalloc.c contiene una variabile statica di used_memory per registrare la dimensione totale della memoria attualmente allocata. Quindi, in generale, Redis adotta l’incapsulato malloc/free, che è molto più semplice rispetto al meccanismo di gestione della memoria di Memcached.

Supporto della persistenza dei dati

Sebbene sia un archivio basato sulla memoria, Redis supporta la persistenza dei dati in memoria e fornisce due principali criteri di persistenza, snapshot RDB e registro AOF. Memcached non supporta le operazioni di persistenza dei dati.

Istantanea RDB

Redis supporta l’archiviazione dell’istantanea dei dati correnti in un file di dati per la persistenza, ovvero l’istantanea RDB. Ma come possiamo generare lo snapshot per un database con scritture di dati continue? Redis utilizza il meccanismo di copia in scrittura del comando fork. Dopo la creazione di uno snapshot, il processo corrente crea un sottoprocesso che rende ciclici tutti i dati e li scrive nel file RDB. Possiamo configurare i tempi di una generazione di snapshot RDB tramite il comando save di Redis. Ad esempio, se si desidera configurare la generazione di snapshot una volta ogni 10 minuti, è possibile configurare la generazione di snapshot dopo ogni 1.000 scritture. È inoltre possibile configurare più regole per l’implementazione insieme. Le definizioni di queste regole si trovano nei file di configurazione di Redis. È inoltre possibile impostare le regole utilizzando il comando CONFIG SET di Redis durante il runtime di Redis senza riavviare Redis.

Il file RDB di Redis è, in una certa misura, incorruttibile perché esegue le sue operazioni di scrittura in un nuovo processo. Alla generazione di un nuovo file RDB, il sottoprocesso generato da Redis scriverà prima i dati in un file temporaneo, quindi rinominerà il file temporaneo in un file RDB tramite la chiamata di sistema di ridenominazione atomica, in modo che il file RDB sia sempre disponibile ogni volta che Redis subisce un errore. Allo stesso tempo, il file RDB di Redis è anche un collegamento nell’implementazione interna della sincronizzazione master-slave di Redis. Tuttavia, RDB ha la sua carenza in quanto una volta che il database incontra qualche problema, i dati salvati nel file RDB potrebbero non essere aggiornati e i dati vengono persi durante il periodo dall’ultima generazione del file RDB all’errore Redis. Tieni presente che per alcune aziende questo è tollerabile.

Registro AOF

La forma completa del registro AOF è Aggiungi solo file. È un file di registro allegato. A differenza del binlog dei database generali, l’AOF è un testo in chiaro riconoscibile e il suo contenuto sono i comandi standard di Redis. Redis aggiungerà solo i comandi che causeranno modifiche ai dati all’AOF. Ogni comando per la modifica dei dati genererà un registro. Il file AOF diventerà sempre più grande. Redis offre un’altra funzionalità: la riscrittura AOF. La funzione della riscrittura AOF è quella di rigenerare un file AOF. C’è solo un’operazione per ogni record nel nuovo file AOF, invece di più operazioni per lo stesso valore registrato nella vecchia copia. Il processo di generazione è simile allo snapshot RDB, ovvero il fork di un processo, l’attraversamento dei dati e la scrittura dei dati nel nuovo file AOF temporaneo. Quando si scrivono dati nel nuovo file, scriverà tutti i registri delle operazioni di scrittura nel vecchio file AOF e li registrerà contemporaneamente nella zona di buffering della memoria. Al termine dell’operazione, il sistema scriverà tutti i log nella zona di buffering nel file temporaneo contemporaneamente. Successivamente, chiamerà il comando di ridenominazione atomica per sostituire il vecchio file AOF con il nuovo file AOF.

AOF è un’operazione di scrittura su file e mira a scrivere i registri delle operazioni sul disco. Implica anche la procedura di operazione di scrittura di cui abbiamo parlato prima. Dopo che Redis ha chiamato l’operazione di scrittura per AOF, utilizza l’opzione appendfsync per controllare il tempo di scrittura dei dati sul disco chiamando il comando fsync. Le tre opzioni di impostazione nell’appendfsync di seguito hanno un livello di sicurezza da basso a forte.

appendfsync no: quando impostiamo appendfsync su no, Redis non prenderà l’iniziativa di chiamare fsync per sincronizzare i log AOF sul disco. La sincronizzazione sarà completamente dipendente dal debug del sistema operativo. La maggior parte dei sistemi operativi Linux esegue l’operazione fsync una volta ogni 30 secondi per scrivere i dati nella zona di buffering sul disco.

appendfsync everysec: quando impostiamo appendfsync a everysec, Redis chiamerà fsync una volta ogni due secondi per impostazione predefinita per scrivere i dati nella zona di buffering sul disco. Ma quando una chiamata fsync dura più di 1 secondo, Redis adotterà il ritardo fsync per attendere un altro secondo. Cioè, Redis chiamerà fsync dopo due secondi. Eseguirà questo fsync indipendentemente dal tempo necessario per l’esecuzione. A questo punto, poiché il descrittore di file subirà una congestione durante il file fsync, l’operazione di scrittura corrente subirà una congestione simile. La conclusione è che nella stragrande maggioranza dei casi, Redis eseguirà fsync una volta ogni due secondi. Nel peggiore dei casi, eseguirà l’operazione fsync una volta ogni due secondi. La maggior parte dei sistemi di database fa riferimento a questa operazione come commit di gruppo, ovvero combinando i dati di più scritture e scrivendo i log sul disco alla volta.

appednfsync sempre: quando impostiamo appendfsync su sempre, ogni operazione di scrittura chiamerà fsync una volta. In questo momento, i dati sono i più sicuri. Ovviamente, poiché esegue fsync ogni volta, comprometterà le prestazioni.

Per i requisiti aziendali generali, ti consigliamo di utilizzare RDB per la persistenza perché l’overhead di RDB è molto inferiore a quello dei log AOF. Per le applicazioni che non sopportano il rischio di perdita di dati, si consiglia di utilizzare i registri AOF.

Memcached stesso non supporta la modalità distribuita. È possibile ottenere l’archiviazione distribuita di Memcached sul lato client solo tramite algoritmi distribuiti come Consistent Hash. La figura seguente mostra lo schema di implementazione dell’archiviazione distribuita di Memcached. Prima che il lato client invii i dati al cluster Memcached, calcola prima il nodo di destinazione dei dati tramite l’algoritmo distribuito nidificato che a sua volta invia direttamente i dati al nodo per l’archiviazione. Ma quando il lato client interroga i dati, deve anche calcolare il nodo che funge da posizione dei dati interrogati e quindi inviare la richiesta di query direttamente al nodo per ottenere i dati.

Rispetto a Memcached che può ottenere solo lo storage distribuito sul lato client, Redis preferisce creare lo storage distribuito sul lato server. L’ultima versione di Redis supporta l’archiviazione distribuita. Redis Cluster è una versione avanzata di Redis che consente l’archiviazione distribuita e consente SPOF. Non ha un nodo centrale ed è capace di espansione lineare. La figura seguente fornisce l’architettura di archiviazione distribuita di Redis Cluster. La comunicazione tra nodi segue il protocollo binario ma la comunicazione nodo-client segue il protocollo ASCII. Nella politica di posizionamento dei dati, Redis Cluster divide l’intero intervallo numerico di chiavi in ​​4.096 slot hash e consente l’archiviazione di uno o più slot hash su ciascun nodo. Vale a dire, l’attuale cluster Redis supporta un massimo di 4.096 nodi. Anche l’algoritmo distribuito utilizzato da Redis Cluster è semplice: crc16 (chiave) % HASH_SLOTS_NUMBER.

Redis Cluster introduce il nodo master e il nodo slave per garantire la disponibilità dei dati in caso di SPOF. Ogni nodo master in Redis Cluster ha due nodi slave corrispondenti per la ridondanza. Di conseguenza, due nodi guasti nell’intero cluster non comprometteranno la disponibilità dei dati. Quando il nodo master esiste, il cluster sceglierà automaticamente un nodo slave per diventare il nuovo nodo master.

Previous Monit / Healing
Next Sincronizzazione server: introduzione
Table of Contents