
Quando si entra in un prodotto che si è evoluto per più di dieci anni, non sorprende affatto imbattersi in tecnologie obsolete. Ma cosa succede se tra sei mesi devi gestire un carico dieci volte superiore e il costo dei malfunzionamenti aumenta di centinaia di volte? In questo caso, hai bisogno di un ottimo Highload Engineer. Tuttavia, non avendo a disposizione uno, hanno deciso di affidare a me la risoluzione del problema. Nella prima parte dell'articolo parlerò di come siamo passati da Redis a Redis-cluster, mentre nella seconda parte darò consigli su come iniziare a utilizzare il cluster e su cosa fare attenzione durante l'operatività.
Scelta della tecnologia
È così male Redis separato (standalone redis) nella configurazione 1 master e N slave? Perché lo considero una tecnologia obsoleta?
No, Redis non è così male… Tuttavia, ci sono alcune imperfezioni che non si possono ignorare.
In primo luogo, Redis non supporta meccanismi di ripristino di emergenza dopo un guasto del master. Per risolvere questo problema, abbiamo utilizzato una configurazione che prevede il failover automatico degli indirizzi VIP a un nuovo master, cambiando il ruolo di uno degli slave e commutando gli altri. Questo meccanismo funzionava, ma non si può etichettarlo come una soluzione affidabile. Prima di tutto, si verificavano falsi allarmi, e in secondo luogo, era una soluzione una tantum, e dopo che scattava era necessario un intervento manuale per riarmare la molla.
In secondo luogo, avere solo un master portava a problemi di sharding. Era necessario creare più cluster indipendenti "1 master e N slave", poi distribuire manualmente i database su queste macchine e sperare che domani uno dei database non si espandesse così tanto da dover essere spostato su un'istanza separata.
Quali sono le opzioni?
- La soluzione più costosa e ricca è Redis-Enterprise. Questa è una soluzione confezionata con supporto tecnico completo. Anche se appare ideale dal punto di vista tecnico, non era adatta per motivi ideologici.
- Redis-cluster. Di serie offre il supporto per il failover del master e lo sharding. L'interfaccia è quasi identica a quella della versione normale. Sembra promettente, discuteremo dei potenziali problemi in seguito.
- Tarantool, Memcache, Aerospike e altri. Tutti questi strumenti svolgono fondamentalmente lo stesso compito. Ma ognuno ha i propri svantaggi. Abbiamo deciso di non mettere tutte le uova in un solo paniere. Memcache e Tarantool li utilizziamo per altri compiti e, anticipando, posso dire che nella nostra esperienza abbiamo avuto più problemi con loro.
Specificità dell'uso
Diamo un'occhiata a quali problemi abbiamo storicamente risolto con Redis e quale funzionalità abbiamo utilizzato:
- Cache prima delle richieste a servizi remoti come 2GIS | Golang
GET SET MGET MSET "SELECT DB"
- Cache prima di MYSQL | PHP
GET SET MGET MSET SCAN "KEY BY PATTERN" "SELECT DB"
- Archivio principale per il servizio di gestione delle sessioni e delle coordinate degli autisti | Golang
GET SET MGET MSET "SELECT DB" "ADD GEO KEY" "GET GEO KEY" SCAN
Come potete vedere, nessuna matematica avanzata. Qual è allora la complessità? Analizziamo ogni metodo separatamente.
Sanitizer.replaceElementWithChildren()
Descrizione
Caratteristiche di Redis-cluster
Soluzione
GET SET
Scrivere/leggere una chiave
MGET MSET
Scrivere/leggere più chiavi
Le chiavi saranno distribuite su nodi diversi. Le librerie pronte possono eseguire operazioni multiple solo all'interno di un unico nodo
Sostituire MGET con pipeline di N operazioni GET
SELECT DB
Scegliere il database con cui lavoreremo
Non supporta più database
Consolidare tutto in un unico database. Aggiungere prefissi alle chiavi
SCAN
Scorrere tutte le chiavi nel database
Poiché abbiamo un solo database, scorrere tutte le chiavi nel cluster è troppo costoso
Mantenere un'invarianza all'interno di una singola chiave ed eseguire HSCAN su quella chiave. Oppure rinunciare completamente
GEO
Operazioni con chiavi geografiche
La chiave geografica non viene shardata
KEY BY PATTERN
Ricerca di una chiave per pattern
Poiché abbiamo un solo database, cercheremo tra tutte le chiavi nel cluster. Troppo costoso
Rinunciare o mantenere invarianti, come nel caso di SCAN
Redis vs Redis-cluster
Cosa perdiamo e cosa guadagniamo passando al cluster?
- Svantaggi: perdiamo la funzionalità di più database.
- Se vogliamo memorizzare in un unico cluster dati logicamente non correlati, dovremo fare dei compromessi sotto forma di prefissi.
- Perdiamo tutte le operazioni 'sul database', come SCAN, DBSIZE, CLEAR DB e simili.
- Le operazioni multiple sono diventate significativamente più complesse da implementare, poiché potrebbe essere necessario accedere a più nodi.
- Vantaggi:
- Resilienza con failover automatico del master.
- Sharding lato Redis.
- Trasferimento atomico e senza interruzioni dei dati tra i nodi.
- Aggiunta e ridistribuzione delle risorse e dei carichi senza interruzioni.
Direi che se non è necessario garantire un alto livello di disponibilità, non vale la pena migrare a un cluster, poiché potrebbe essere un compito non banale. Ma se inizialmente si deve scegliere tra una versione separata e quella cluster, è consigliabile optare per il cluster, in quanto non è peggiore e, inoltre, allevierà parte della vostra fatica.
Preparazione alla migrazione
Iniziamo con i requisiti per la migrazione:
- Deve essere senza soluzione di continuità. Un'interruzione totale del servizio di 5 minuti non è accettabile.
- Deve essere il più sicuro e graduale possibile. Vogliamo avere un certo controllo sulla situazione. Non vogliamo trasferire tutto all'improvviso e pregare per un pulsante di ripristino.
- Minime perdite di dati durante la migrazione. Comprendiamo che sarà molto difficile migrare in modo atomico, quindi accettiamo una certa desincronizzazione tra i dati nel Redis normale e in quello cluster.
Manutenzione del cluster
Poco prima della migrazione, è importante pensare se possiamo sostenere il cluster:
- Grafici. Utilizziamo Prometheus e Grafana per i grafici di utilizzo della CPU, della memoria occupata, del numero di client, delle operazioni GET, SET, AUTH, ecc.
- Esperienza. Immaginate che domani sotto la vostra responsabilità ci sia un enorme cluster. Se si guasta, nessuno tranne voi sarà in grado di ripararlo. Se inizia a rallentare, tutti verranno da voi. Se serve aggiungere risorse o ridistribuire il carico, di nuovo a voi. Per non diventare grigi a 25 anni, è fondamentale prevedere questi casi e testare in anticipo come la tecnologia si comporterà in determinate azioni. Ne parleremo in dettaglio nella sezione 'Esperienza'.
- Monitoraggio e avvisi. Quando il cluster si rompe, vogliamo essere i primi a saperlo. Qui ci siamo limitati ad avvisare che tutti i nodi restituiscono le stesse informazioni sullo stato del cluster (sì, ci sono anche altre possibilità). È più facile notare altri problemi tramite le notifiche dei servizi client Redis.
Trasloco
Come procederemo con la migrazione:
- In primo luogo, è necessario preparare la libreria per lavorare con il cluster. Come base per la versione in Go abbiamo preso go-redis e l'abbiamo leggermente modificata. Abbiamo implementato i metodi Multi tramite pipeline e abbiamo anche corretto un po' le regole di ripetizione delle richieste. Con la versione per PHP sono sorti più problemi, ma alla fine abbiamo scelto php-redis. Recentemente hanno introdotto il supporto per il cluster, e a nostro avviso sembra funzionare bene.
- Successivamente, è necessario configurare il cluster stesso. Questo si fa letteralmente con due comandi basati sul file di configurazione. Discuteremo ulteriormente la configurazione qui sotto.
- Per una transizione graduale utilizziamo la modalità di simulazione. Poiché abbiamo due versioni della libreria con la stessa interfaccia (una per la versione normale, l'altra per il cluster), non è difficile creare un wrapper che funzioni con la versione separata e nel contempo duplichi tutte le richieste al cluster, confronti le risposte e registri le discrepanze nei log (nel nostro caso in NewRelic). In questo modo, anche se durante il rilascio la versione cluster si rompe, il nostro ambiente di produzione non ne sarà influenzato.
- Lanciando il cluster in modalità di simulazione, possiamo osservare tranquillamente il grafico delle discrepanze delle risposte. Se la percentuale di errori si sta lentamente ma inesorabilmente avvicinando a una piccola costante, significa che va tutto bene. Perché ci sono comunque delle discrepanze? Perché la scrittura nella versione separata avviene leggermente prima rispetto al cluster, e a causa di micro-latency i dati possono divergere. Resta solo da controllare i log delle discrepanze, e se tutte sono spiegabili dalla non atomicità della scrittura, possiamo procedere.
- Ora possiamo passare dalla modalità di simulazione. Scriveremo e leggeremo dal cluster, mentre duplichiamo nella versione separata. Perché? Nel corso della prossima settimana vogliamo osservare il funzionamento del cluster. Se dovessimo scoprire che ci sono problemi durante i picchi di carico, o se abbiamo trascurato qualcosa, abbiamo sempre un rollback di emergenza al vecchio codice e dati attuali grazie alla modalità di simulazione.
- Bisogna spegnere la modalità di simulazione e smontare la versione separata.
Esperienza
Per prima cosa, un breve riassunto della struttura del cluster.
In primo luogo, Redis è un archivio key-value. Come chiave si utilizzano stringhe arbitrarie. Come valori possono essere utilizzati numeri, stringhe e intere strutture. Ce ne sono moltissimi, ma per capire la struttura complessiva non è importante.
Il livello di astrazione successivo alle chiavi è rappresentato dagli slot (SLOTS). Ogni chiave appartiene a uno dei 16.383 slot. All'interno di ciascuno slot può esserci un numero qualsiasi di chiavi. In questo modo, tutte le chiavi si suddividono in 16.383 insiemi non sovrapposti.

Inoltre, nel cluster devono essere presenti N master node. Ogni nodo può essere rappresentato come un'istanza separata di Redis, che conosce tutto sulle altre nodi all'interno del cluster. Ogni master node contiene un certo numero di slot. Ogni slot appartiene solo a un master node. Tutti gli slot devono essere distribuiti tra i nodi. Se alcuni slot non sono distribuiti, le chiavi in essi contenute non saranno accessibili. Ha senso eseguire ogni master node su una macchina logica o fisica separata. Vale anche la pena ricordare che ogni nodo opera solo su un core, e se si desidera eseguire più istanze di Redis su una singola macchina logica, assicurarsi che operino su core diversi (non abbiamo provato a farlo, ma in teoria dovrebbe funzionare). In sostanza, i master node garantiscono una normale sharding, e un numero maggiore di master node consente di scalare le richieste di scrittura e lettura.
Dopo che tutte le chiavi sono state distribuite su slot e gli slot sono stati distribuiti tra i master node, è possibile aggiungere un numero arbitrario di slave node a ciascun master node. All'interno di ciascuna di queste coppie "master-slave" opererà una normale replica. Gli slave sono necessari per scalare le richieste di lettura e per il failover in caso di guasto del master.

Adesso parliamo delle operazioni che sarebbe utile saper eseguire.
Per accedere al sistema utilizzeremo Redis-CLI. Poiché Redis non ha un unico punto di ingresso, è possibile eseguire le seguenti operazioni su qualsiasi nodo. In ogni punto evidenzio separatamente la possibilità di eseguire l'operazione sotto carico.
- La prima e più importante operazione di cui avremo bisogno è: cluster nodes. Essa restituisce lo stato del cluster, mostrando l'elenco dei nodi, i loro ruoli, la distribuzione degli slot, ecc. Ulteriori informazioni possono essere ottenute tramite cluster info e cluster slots.
- Sarebbe utile poter aggiungere e rimuovere nodi. A questo scopo ci sono le operazioni cluster meet e cluster forget. Si prega di notare che cluster forget deve essere applicato a OGNI nodo, sia ai master che alle repliche. Mentre per cluster meet è sufficiente chiamarlo su un solo nodo. Tale distinzione può essere disorientante, quindi è meglio conoscerla prima di mettere in produzione il cluster. L'aggiunta di un nodo avviene in modo sicuro durante il funzionamento e non influisce sul lavoro del cluster (il che è logico). Tuttavia, se si intende rimuovere un nodo dal cluster, è necessario assicurarsi che non siano rimasti slot su di esso (altrimenti si rischia di perdere l'accesso a tutte le chiavi su quel nodo). Non rimuovere il master che ha slave, altrimenti verrà eseguita una votazione non necessaria per un nuovo master. Se sui nodi non ci sono più slot, è un piccolo problema, ma perché complicarsi la vita se si può prima rimuovere gli slave.
- Se è necessario forzare il cambio di posto tra master e slave, si può usare il comando cluster failover. Invocandolo durante il normale funzionamento, bisogna tenere presente che durante l'esecuzione dell'operazione il master non sarà disponibile. Solitamente, il passaggio avviene in meno di un secondo, ma non è atomico. Puoi aspettarti che parte delle richieste al master in quel momento si concludano con un errore.
- Prima di rimuovere un nodo dal cluster, non devono rimanere slot. È meglio ridistribuirli utilizzando il comando cluster reshard. Gli slot saranno trasferiti da un master a un altro. L'intera operazione può richiedere alcuni minuti, a seconda del volume dei dati trasferiti; tuttavia, il processo di trasferimento è sicuro e non influisce sul funzionamento del cluster. In questo modo, tutti i dati possono essere trasferiti da un nodo a un altro sotto carico, senza preoccuparsi della loro disponibilità. Tuttavia, ci sono alcune sottigliezze. Prima di tutto, il trasferimento dei dati implica un certo carico sul nodo ricevente e sul nodo mittente. Se il nodo ricevente è già molto carico dal punto di vista della CPU, non dovreste sovraccaricarlo ulteriormente con l'accettazione di nuovi dati. In secondo luogo, appena il master mittente non avrà più slot, tutti i suoi slave passeranno immediatamente al master a cui questi slot sono stati trasferiti. E il problema è che tutti questi slave vorranno sincronizzare i dati contemporaneamente. E sarete fortunati se si tratterà di una sincronizzazione parziale e non completa. Tenete conto di questo e abbinare le operazioni di trasferimento degli slot e di disattivazione/ trasferimento degli slave. Oppure sperate che abbiate un'adeguata riserva di capacità.
- Cosa fare se, durante il trasferimento, scoprite di aver perso alcuni slot? Spero che questo problema non vi tocchi, ma in caso contrario, c'è l'operazione cluster fix. Essa distribuirà gli slot sui nodi in modo casuale, anche se con qualche difficoltà. Consiglio di testarne il funzionamento, rimuovendo preventivamente dal cluster un nodo con slot distribuiti. Poiché i dati negli slot non distribuiti sono già inaccessibili, è troppo tardi per preoccuparsi di eventuali problemi di accessibilità di questi slot. D'altra parte, l'operazione non influenzerà gli slot distribuiti.
- Un'altra operazione utile è monitor. Essa consente di vedere in tempo reale l'intero elenco delle richieste che arrivano al nodo. Inoltre, è possibile fare grep per verificare se è presente il traffico desiderato.
Vale la pena menzionare anche la procedura di failover del master. In breve, esiste ed, a mio avviso, funziona molto bene. Tuttavia, non bisogna pensare che staccando il cavo dalla presa su una macchina con nodo master, Redis si commuti immediatamente e i client non notino la perdita. Nella mia esperienza, il passaggio richiede alcuni secondi. Durante questo tempo, una parte dei dati sarà inaccessibile: viene rilevata l'inaccessibilità del master, i nodi votano per un nuovo master, i slave si commutano e i dati vengono sincronizzati. Il modo migliore per verificare autonomamente che lo schema funzioni è effettuare delle prove locali. Avvia il cluster sul tuo laptop, applica un carico minimo, simula un crash (ad esempio, bloccando le porte), valuta la velocità di commutazione. A mio avviso, solo giocando in questo modo per un giorno o due, si può essere certi che la tecnologia funzioni. Oppure, si può sperare che il software utilizzato dalla metà di Internet funzioni sicuramente.
Configurazione
Spesso, la configurazione è la prima cosa necessaria per iniziare a lavorare con lo strumento. E quando tutto è in funzione, non si ha voglia di toccare la configurazione. Richiede uno sforzo particolare forzarsi a tornare alle impostazioni e controllarle attentamente. A quanto ricordo, abbiamo avuto almeno due seri problemi a causa della scarsa attenzione alla configurazione. Presta particolare attenzione ai seguenti punti:
- timeout 0
Tempo dopo il quale si chiudono le connessioni inattive (in secondi). 0 — non vengono chiuse
Non tutte le nostre librerie erano in grado di chiudere correttamente le connessioni. Disattivando questa impostazione, rischiamo di arrivare al limite massimo di clienti. D'altra parte, se esiste un problema del genere, la disconnessione automatica delle connessioni perse la maschererà, e potremmo non accorgercene. Inoltre, non bisogna attivare questa impostazione quando si usano connessioni persistenti. - Save x y & appendonly yes
Salvataggio di uno snapshot RDB.
Discuteremo in dettaglio i problemi RDB/AOF qui sotto. - stop-writes-on-bgsave-error no & slave-serve-stale-data yes
Se attivato, il master smetterà di accettare richieste di modifica in caso di errore nello snapshot RDB. Se la connessione con il master viene persa, lo slave può continuare a rispondere alle richieste (yes). Oppure smetterà di rispondere (no)
Non ci piace la situazione in cui Redis si trasforma in una zucca. - repl-ping-slave-period 5
Trascorso questo intervallo di tempo, inizieremo a preoccuparci che il master si sia rotto e sia tempo di eseguire la procedura di failover.
Dovremo trovare manualmente un equilibrio tra falsi allarmi e attivazione del failover. Nella nostra pratica, questo è di 5 secondi. - repl-backlog-size 1024mb & epl-backlog-ttl 0
Esattamente quanti dati possiamo mantenere nel buffer per una replica disconnessa. Se il buffer si esaurisce, sarà necessario sincronizzarsi completamente.
La pratica suggerisce che è meglio impostare un valore più alto. Ci sono molte ragioni per cui una replica potrebbe iniziare a ritardare. Se sta rallentando, probabilmente il vostro master sta già facendo fatica e una sincronizzazione completa sarà l'ultima goccia. - maxclients 10000
Numero massimo di clienti simultanei.
Dalla nostra esperienza, è meglio impostare un valore più alto. Redis gestisce perfettamente 10.000 connessioni. Assicurati solo che ci siano abbastanza socket nel sistema. - maxmemory-policy volatile-ttl
Regola secondo cui le chiavi vengono rimosse al raggiungimento del limite di memoria disponibile.
Qui ciò che conta non è tanto la regola, quanto la comprensione di come avverrà. Redis merita un elogio per la sua capacità di lavorare normalmente al raggiungimento del limite di memoria.
Problemi RDB e AOF
Anche se Redis memorizza tutte le informazioni nella memoria volatile, esiste anche un meccanismo per salvare i dati su disco. Anzi, tre meccanismi:
- RDB-snapshot — una copia completa di tutti i dati. Viene impostata tramite la configurazione SAVE X Y e letta come «Salvare un'istantanea completa di tutti i dati ogni X secondi, se è stato modificato almeno Y chiavi».
- Append-only file — lista delle operazioni nell'ordine in cui vengono eseguite. Aggiunge nuove operazioni arrivate nel file ogni X secondi o ogni Y operazioni.
- RDB e AOF — combinazione dei due precedenti.
Tutti i metodi hanno i loro pregi e difetti, non elencherò tutti, ma richiamo l'attenzione su aspetti che non sono così evidenti, secondo me.
Per prima cosa, per salvare un'istantanea RDB è necessario chiamare FORK. Se ci sono molti dati, questo può mettere in timeout l'intero Redis per un periodo che va da alcuni millisecondi a un secondo. Inoltre, il sistema deve allocare memoria per tale istantanea, il che porta alla necessità di mantenere sulla macchina logica un doppio margine di memoria: se a Redis sono stati dedicati 8 GB, sulla virtuale con esso devono essere disponibili 16.
In secondo luogo, ci sono problemi con la sincronizzazione parziale. In modalità AOF, durante il ripristino della connessione con lo slave, può essere eseguita una sincronizzazione completa invece di quella parziale. Non ho capito perché accada. Ma vale la pena ricordarlo.
Questi due punti già ci fanno riflettere su quanto siano necessari questi dati su disco, se tutto viene comunque duplicato dagli slave. I dati possono andare persi solo se tutti gli slave si guastano, e questo è un problema di livello "incendio nel centro dati". Come compromesso, si potrebbe considerare di conservare i dati solo sugli slave, ma in tal caso è necessario assicurarsi che questi slave non diventino mai master durante un ripristino d'emergenza (per questo esiste una configurazione della priorità degli slave nel loro file di configurazione). Noi, caso per caso, riflettiamo su se sia necessario salvare i dati su disco, e più spesso rispondiamo "no".
Conclusione
In conclusione, spero di aver fornito una panoramica sull'operato di redis-cluster a chi non ne ha mai sentito parlare, e di aver messo in evidenza alcuni aspetti non evidenti per coloro che lo utilizzano da tempo.
Grazie per il tempo dedicato e, come al solito, i commenti sull'argomento sono benvenuti.
Fonte: habr.com
