Top 5 Colli di Bottiglia delle Prestazioni di Redis e Come Risolverli
Sblocca le massime prestazioni dalle tue implementazioni Redis con questa guida essenziale ai colli di bottiglia comuni. Impara a identificare e risolvere problemi come comandi O(N) lenti, eccessivi round trip di rete, pressione della memoria e politiche di espulsione inefficienti, overhead di persistenza e operazioni vincolate dalla CPU. Questo articolo fornisce passaggi attuabili, esempi pratici e best practice, dall'uso di pipelining e `SCAN` all'ottimizzazione delle strutture dati e della persistenza, garantendo che la tua istanza Redis rimanga veloce e affidabile per tutte le tue esigenze di caching, messaggistica e archiviazione dati.
Top 5 Colli di Bottiglia delle Prestazioni di Redis e Come Risolverli
I problemi di prestazioni di Redis di solito sembrano misteriosi finché non ricordi una cosa: Redis è veloce, ma non è esente dal lavoro. Un comando che esamina un milione di chiavi esamina comunque un milione di chiavi. Un client che invia un comando per round trip di rete paga comunque per ogni round trip. Un server che esaurisce la memoria deve comunque espellere, scambiare, rifiutare scritture o cadere a seconda della configurazione.
Quando Redis rallenta, non iniziare modificando impostazioni a caso. Inizia con le prove:
redis-cli INFO
redis-cli SLOWLOG GET 20
redis-cli LATENCY DOCTOR
redis-cli INFO commandstats
redis-cli INFO memory
Quei comandi di solito puntano verso uno dei cinque colli di bottiglia: comandi lenti, round trip di rete, pressione della memoria, overhead di persistenza o saturazione della CPU.
1. Comandi lenti su grandi dati
Redis ha molte piccole operazioni a tempo costante, ma non tutti i comandi sono piccoli. Comandi come KEYS, LRANGE grande, SMEMBERS, HGETALL, ZRANGE su intervalli enormi, SORT e script Lua lunghi possono bloccare altri client mentre vengono eseguiti.
L'incidente classico inizia con un comando di pulizia o debug:
KEYS *
Su una piccola istanza di sviluppo, restituisce immediatamente. Su un keyspace di produzione con milioni di chiavi, può bloccare il server abbastanza a lungo da far accumulare le richieste dell'applicazione. Lo stesso schema si verifica con un hash che è iniziato come "pochi campi per utente" e silenziosamente è diventato un oggetto gigante.
Trova le prove:
redis-cli SLOWLOG GET 20
redis-cli INFO commandstats
redis-cli LATENCY LATEST
SLOWLOG registra i comandi che hanno superato la soglia configurata. INFO commandstats mostra i conteggi delle chiamate per comando e il tempo cumulativo. Se un comando domina il tempo, inizia da lì.
Correggi il modello di accesso:
redis-cli --scan --pattern 'user:*'
Usa SCAN invece di KEYS per l'iterazione del keyspace. Usa HSCAN, SSCAN e ZSCAN per hash, set e sorted set grandi. Recupera pagine o intervalli invece di intere strutture:
LRANGE feed:user:42 0 49
ZRANGE leaderboard 0 99 WITHSCORES
Se un oggetto è diventato troppo grande, suddividilo in base a come l'applicazione lo legge. Un singolo hash user:42 con migliaia di campi non correlati può essere comodo per le scritture ma doloroso per le letture che necessitano solo delle impostazioni del profilo. Chiavi separate come user:42:profile, user:42:prefs e user:42:counters possono ridurre la quantità di dati toccati per richiesta.
Per l'eliminazione, preferisci UNLINK quando i valori possono essere grandi:
UNLINK old:large:set
UNLINK rimuove la chiave dal keyspace e libera memoria in modo asincrono. È più sicuro di DEL per valori grandi, anche se la pulizia in blocco necessita ancora di limitazione.
2. Troppi round trip di rete
Redis può elaborare un comando in microsecondi mentre la tua applicazione spende millisecondi in attesa sulla rete. Se un percorso di richiesta invia 50 comandi Redis sequenziali, la rete può dominare il tempo totale anche quando Redis stesso è sano.
Questo è comune in codice come:
for user_id in user_ids:
profile = redis.get(f"user:{user_id}:profile")
Ogni GET attende la propria risposta prima che inizi il successivo. Attraverso una rete, questo è costoso.
Usa il pipelining:
pipe = redis.pipeline(transaction=False)
for user_id in user_ids:
pipe.get(f"user:{user_id}:profile")
profiles = pipe.execute()
Il pipelining invia più comandi senza attendere ogni risposta individualmente. Redis esegue ancora i comandi in ordine, ma il client evita di pagare un round trip per ogni comando.
Usa comandi multi-chiave dove si adattano:
MGET user:1:profile user:2:profile user:3:profile
Non trasformare ogni richiesta in un'enorme pipeline. Grandi pipeline possono aumentare l'uso della memoria e creare picchi di risposta. Raggruppa abbastanza per rimuovere lo spreco evidente di round trip, poi misura.
Per percorsi ad alta lettura, controlla anche se la tua applicazione chiede ripetutamente a Redis valori che potrebbero essere recuperati una volta e riutilizzati per la durata di una richiesta. Una piccola cache di richieste locale può rimuovere letture duplicate accidentali senza modificare Redis.
3. Pressione della memoria e agitazione dell'espulsione
Redis è incentrato sulla memoria. Una volta che la memoria è limitata, le prestazioni peggiorano in diversi modi: l'espulsione costa CPU, le scritture possono fallire sotto noeviction, i fork di persistenza possono diventare più difficili, le repliche possono rimanere indietro e il sistema operativo può scambiare se l'host è configurato male o sovraccarico.
Controlla la memoria:
redis-cli INFO memory
redis-cli INFO stats | grep evicted_keys
redis-cli CONFIG GET maxmemory
redis-cli CONFIG GET maxmemory-policy
Segni importanti:
used_memoryè vicino amaxmemory.evicted_keyssta aumentando rapidamente.- L'host sta scambiando.
- Le chiavi grandi consumano più memoria del previsto.
- Le chiavi della cache in scadenza non hanno effettivamente TTL.
Trova le chiavi grandi con attenzione. Non eseguire comandi ampi e costosi durante il traffico di punta. Il campionamento con --bigkeys può aiutare:
redis-cli --bigkeys
Per le cache, imposta un limite di memoria e una politica di espulsione che corrisponda ai dati:
maxmemory 4gb
maxmemory-policy allkeys-lru
allkeys-lru o allkeys-lfu possono avere senso quando tutte le chiavi sono voci della cache. volatile-lru espelle solo le chiavi con TTL, utile quando le chiavi persistenti condividono l'istanza con le chiavi della cache. noeviction è spesso giusto per Redis usato come archivio dati primario, perché espellere silenziosamente dati dall'aspetto durevole sarebbe peggio che restituire un errore.
Imposta TTL al momento della scrittura:
SET cache:product:123 "$json" EX 300
Per i session store, sii deliberato. Una chiave di sessione senza scadenza è di solito un bug. Per i rate limiter, i contatori dovrebbero scadere con la finestra. Per gli Stream, tronca le voci vecchie. Le perdite di memoria in Redis sono spesso dati dell'applicazione che non hanno mai ricevuto un ciclo di vita.
Riduci anche l'overhead di chiavi e valori dove conta. Migliaia di piccole chiavi possono costare più metadati del previsto. A volte un hash compatto è meglio di molte chiavi individuali; a volte è vero il contrario perché le letture necessitano solo di un campo. Misura con modelli di accesso reali invece di assumere che una forma sia sempre la migliore.
4. Stalli di persistenza e I/O del disco
La persistenza protegge i dati, ma introduce comportamenti di fork e disco che devi capire. Gli snapshot RDB e le riscritture AOF sono normalmente operazioni in background, ma possono comunque causare latenza attraverso il tempo di fork, la pressione della memoria copy-on-write e l'I/O del disco.
Controlla lo stato della persistenza:
redis-cli INFO persistence
redis-cli LATENCY LATEST
iostat -xz 1
Cerca salvataggi in background falliti, tempi di fork lunghi, attività di riscrittura AOF e saturazione del disco. Se i picchi di latenza coincidono con BGSAVE o BGREWRITEAOF, la messa a punto della persistenza è nella lista delle priorità.
Per AOF, l'impostazione principale di durabilità/prestazioni è:
appendfsync everysec
everysec è la scelta bilanciata usuale. always sincronizza ogni scrittura e può essere molto lento. no lascia la sincronizzazione al sistema operativo e accetta più rischio di perdita di dati in caso di crash.
Per RDB, evita regole di snapshot che si attivano costantemente su un carico di lavoro di scrittura intenso a meno che non sia intenzionale:
save 900 1
save 300 10
save 60 10000
Questi valori predefiniti di esempio non sono automaticamente giusti per ogni carico di lavoro. Un Redis ad alta scrittura usato come cache usa e getta potrebbe non aver bisogno di persistenza. Un'istanza Redis usata come coda di lavoro o session store probabilmente sì, ma la finestra di perdita accettabile deve essere chiara.
Se la persistenza compete con il traffico dell'applicazione, considera:
- Storage SSD locale più veloce.
- Separare la persistenza Redis da altri servizi pesanti sul disco.
- Eseguire la persistenza su una replica quando il primario può tollerare quel progetto.
- Mantenere la dimensione del dataset al di sotto di ciò che l'host può forkare comodamente.
- Impostare Linux
vm.overcommit_memory=1dove Redis lo raccomanda per i salvataggi in background.
Non disabilitare la persistenza alla cieca per "risolvere le prestazioni" a meno che i dati non siano veramente usa e getta. Potrebbe far sembrare migliore il grafico mentre trasforma un riavvio in una perdita di dati.
5. Saturazione della CPU ed esecuzione di comandi a thread singolo
L'esecuzione dei comandi Redis è in gran parte a thread singolo, anche se il Redis moderno utilizza thread aggiuntivi per alcuni I/O e lavoro in background. Se un core è monopolizzato da Redis, aggiungere più core inattivi sulla stessa istanza potrebbe non aiutare il percorso dei comandi caldi.
Controlla l'host e il mix di comandi Redis:
top -H -p $(pgrep redis-server)
redis-cli INFO commandstats
redis-cli SLOWLOG GET 20
redis-cli INFO clients
Cause comuni di CPU:
- Operazioni su set, sorted set, liste o hash grandi.
- Script Lua pesanti.
- Overhead di compressione o serializzazione nell'applicazione che causa valori più grandi del previsto.
- Fan-out Pub/Sub molto alto.
- Espulsione costosa sotto pressione della memoria.
- Troppe connessioni che si riconnettono costantemente o emettono piccoli comandi.
Risolvi la CPU riducendo il lavoro, suddividendo il lavoro o distribuendo il lavoro.
Riduci il lavoro cambiando comandi e forme dei dati. Se hai bisogno solo di 50 elementi, non recuperarne 5.000. Se ogni richiesta analizza un blob JSON da 500 KB per leggere un flag, suddividi quel flag in una chiave o campo più piccolo.
Suddividi il lavoro spostando lunghi loop ai client utilizzando scansioni incrementali:
HSCAN big:hash 0 COUNT 100
Distribuisci il lavoro con repliche per le letture o Redis Cluster per lo sharding. Le repliche aiutano il traffico ad alta lettura, ma non rendono le scritture più economiche sul primario. Redis Cluster distribuisce le chiavi tra i primari, il che può aumentare la capacità totale di CPU e memoria, ma aggiunge anche complessità operativa e vincoli di key-slot.
Per Pub/Sub, controlla i buffer di output e il fan-out:
redis-cli PUBSUB NUMSUB events:updates
redis-cli CLIENT LIST
Un abbonato lento può trasformarsi in pressione della memoria. Migliaia di abbonati possono trasformare una pubblicazione in una grande quantità di output di rete. Se Pub/Sub è pesante, considera di isolarlo su un'istanza Redis separata.
Un flusso di lavoro di triage rapido
Quando la latenza di Redis aumenta, esegui questi controlli in ordine:
redis-cli --latency
redis-cli SLOWLOG GET 10
redis-cli LATENCY DOCTOR
redis-cli INFO memory
redis-cli INFO clients
redis-cli INFO persistence
redis-cli INFO commandstats
Poi chiedi:
- È apparso un comando lento?
- La memoria ha raggiunto
maxmemoryo ha iniziato a espellere? - La persistenza ha avviato un salvataggio o una riscrittura?
- I client connessi o bloccati sono aumentati?
- Il conteggio delle chiamate o il tempo di un comando è esploso?
- Le implementazioni dell'applicazione hanno cambiato i modelli di accesso a Redis?
La maggior parte dei colli di bottiglia di Redis non vengono risolti da una singola impostazione magica. Vengono risolti rendendo il carico di lavoro più piccolo, più incrementale, più raggruppato o meglio isolato. Le migliori implementazioni Redis sono noiose: le chiavi scadono quando dovrebbero, le grandi operazioni sono paginate, i client fanno pipelining saggiamente, le impostazioni di persistenza corrispondono al valore dei dati e il monitoraggio coglie la tendenza prima che gli utenti la sentano.
Cosa misurare prima e dopo una correzione
Una correzione delle prestazioni è reale solo se il grafico cambia per il carico di lavoro che conta. Prima di modificare codice o configurazione, cattura una piccola baseline:
redis-cli INFO stats
redis-cli INFO commandstats
redis-cli INFO memory
redis-cli INFO persistence
redis-cli SLOWLOG GET 20
A livello di sistema, cattura CPU, disco, memoria, swap e throughput di rete. Se Redis viene eseguito in un contenitore, controlla sia i limiti del contenitore che la pressione dell'host. Un processo Redis può sembrare a posto all'interno della propria vista della memoria mentre l'host è sotto pressione del disco o della CPU da un altro servizio.
Dopo la modifica, confronta:
- Latenza p50, p95 e p99 dell'applicazione per le richieste supportate da Redis.
- Latenza dei comandi Redis, non solo latenza delle richieste.
- Voci di Slowlog per comando.
- Tasso di espulsione e margine di memoria.
- Client connessi e connessioni rifiutate.
- Tempo di fork della persistenza e stato AOF/RDB.
- Ritardo della replica se le repliche servono letture o proteggono la durabilità.
Sii sospettoso delle correzioni che spostano solo il dolore. Ad esempio, una grande pipeline può ridurre la latenza delle richieste ma aumentare i picchi di memoria. Disabilitare AOF può rimuovere la latenza del disco ma indebolire il recupero. Aumentare maxmemory può ritardare le espulsioni ma affamare l'host se la macchina era già condivisa.
Una pratica utile è scrivere un piccolo test di carico attorno al modello Redis esatto che hai modificato. Se il vecchio codice faceva 40 GET sequenziali, testa GET sequenziali contro MGET o pipelining con dimensioni di payload realistiche. Se il vecchio codice usava HGETALL, testa HGET per i campi effettivamente necessari per la richiesta. La messa a punto di Redis è molto più semplice quando fai benchmark della forma che esegui effettivamente, non un numero generico di "operazioni Redis al secondo".
Infine, mantieni semplice il rollback. Una modifica delle prestazioni di Redis spesso risiede contemporaneamente nel codice dell'applicazione, nelle impostazioni del client e nella configurazione del server. Cambia una cosa quando puoi. Se devi cambiare più cose, annota quale sintomo ogni modifica dovrebbe migliorare. Questo impedisce al prossimo ingegnere di ereditare un mucchio di impostazioni misteriose che nessuno vuole rimuovere.