Gli hash Redis sono (abbastanza intuitivamente!) hash che associano i nomi delle stringhe ai valori delle stringhe. Sono essenzialmente contenitori denominati di campi univoci e relativi valori. Sono il modo perfetto per rappresentare un oggetto come una struttura dati Redis. Come previsto, forniscono operazioni di base a tempo costante come ottenere, impostare, esiste ecc. Vengono fornite anche una serie di operazioni avanzate. L'elenco completo dei comandi hash è qui.
Facciamo un giro dal redis-cli .
# hmset key field value [field value ...] : Insert elements in a hash. O(N), N is # of field being set 127.0.0.1:6379> hmset std:101 name "John Smith" dob "01-01-2000" gender M active 0 cgpa 2.9 OK # hgetall key : key all keys and values in the hash. O(N), N is size of hash 127.0.0.1:6379> hgetall std:101 1) "name" 2) "John Smith" 3) "dob" 4) "01-01-2000" 5) "gender" 6) "M" 7) "active" 8) "0" 9) "cgpa" 10) "2.9" 127.0.0.1:6379> hmset std:102 name "Jane" name "Ann" OK # If duplicates are found, only the last set is valid 127.0.0.1:6379> hgetall std:102 1) "name" 2) "Ann" # hexists key field: does field exist in the hash with key. O(1) 127.0.0.1:6379> hexists std:102 cgpa (integer) 0 # hincrby key field increment: Increment the integer field by increment. O(1) 127.0.0.1:6379> hincrby std:101 active 1 (integer) 1 # hget key field : the value for field in the hash stored at key. O(1) 127.0.0.1:6379> hget std:101 active 1) "1" # If field doesn't exist, hincrby sets it to 0 and then applies increment 127.0.0.1:6379> hincrby std:102 active 2 (integer) 2 # hmget key field [field ...]: the values of the fields requested for the hash with key. O(N), N is # of keys requested 127.0.0.1:6379> hmget std:102 active 1) "2" # hincrbyfloat key field increment: Increment the float value in field by increment. O(1) 127.0.0.1:6379> HINCRBYFLOAT std:101 cgpa 1.0 "3.9" # HSETNX key field value: set field to value if not alread set. O(1) 127.0.0.1:6379> hsetnx std:102 cgpa 4.0 (integer) 1 127.0.0.1:6379> hget std:102 cgpa "4.0" # hlen key: number of fields in the hash. O(1) 127.0.0.1:6379> hlen std:101 (integer) 5 # hkeys key : all fields in the hash. O(N), N is size of hash 127.0.0.1:6379> hkeys std:101 1) "name" 2) "dob" 3) "gender" 4) "active" 5) "cgpa"
Come ci si aspetta dal nostro hosting per Redis™* come server di struttura dati, vediamo che Redis fornisce operazioni abbastanza utili e avanzate sugli hash.
Interni
Come Redis Sets, anche gli hash Redis sono implementati come dizionari. I dizionari in Redis sono implementati come tabelle hash che utilizzano la funzione hash MurmurHash2 e crescono tramite il ridimensionamento incrementale. Le collisioni hash vengono gestite mediante concatenamento. Maggiori dettagli possono essere trovati nell'implementazione Redis del dizionario su dict.c.
Come con Sets, c'è un'ottimizzazione dello storage realizzata per hash più piccoli. Questa struttura di dati è chiamata ziplist (gli hash erano ottimizzati utilizzando una struttura di dati diversa chiamata zipmap prima di Redis 2.6) nell'implementazione di Redis. Si tratta essenzialmente di un elenco doppiamente collegato appositamente codificato che è ottimizzato per il risparmio di memoria. I dati, così come i puntatori, vengono memorizzati in linea. Ziplist viene utilizzato anche per ottimizzare l'archiviazione di insiemi ed elenchi ordinati più piccoli. Un hash quando viene appiattito in un elenco di questo tipo assomiglia a [key1, value1, key2, value2, ...]. In che modo è più efficiente delle chiavi semplici? Gli hash con poche chiavi possono essere impacchettati in modo intelligente in questa struttura ad array lineare (cioè la ziplist) pur garantendo prestazioni O(1) ammortizzate per get e set. Ovviamente questo non può tenere il passo con l'aumento dei campi hash. Man mano che l'hash cresce, viene convertito nella struttura del dizionario standard per mantenere le prestazioni O(1) e il risparmio di spazio viene perso. I parametri di configurazione Redis che controllano questa trasformazione sono:
- list-max-ziplist-entries default (512):passa alla rappresentazione standard se l'hash supera questo limite.
- list-max-ziplist-value default (64):passa alla rappresentazione standard se l'elemento più grande nell'hash diventa maggiore di questo limite.
Ulteriori dettagli possono essere compresi dal codice e dai commenti nell'implementazione che si trova qui. Il risparmio di memoria derivante dall'utilizzo di questa speciale ottimizzazione è significativo. Ne parleremo più in dettaglio nella prossima sezione.
Ottimizzazione della memoria
Uno dei ben noti consigli per il risparmio di memoria durante l'utilizzo di Redis consiste nell'usare hash invece di semplici stringhe. Questo è un caso d'uso importante per utilizzare la potenza degli hash Redis nelle applicazioni del mondo reale. Dalla documentazione ufficiale Redis sull'ottimizzazione della memoria:
Utilizza gli hash quando possibile
Gli hash piccoli sono codificati in uno spazio molto piccolo, quindi dovresti provare a rappresentare i tuoi dati usando gli hash ogni volta che è possibile. Ad esempio se hai oggetti che rappresentano utenti in un'applicazione web, invece di usare chiavi diverse per nome, cognome, email, password, usa un unico hash con tutti i campi obbligatori.
Quel post prosegue proponendo un modo per mappare un intervallo di oggetti in un insieme di hash per sfruttare il risparmio di memoria. Instagram, in un post sul blog molto popolare, descrive l'utilizzo di una tecnica simile che li ha aiutati a raggiungere ordini di grandezza di potenziali risparmi. Un altro blog che tenta di misurare i vantaggi dell'ottimizzazione è questo.
Applicazioni
- Gli hash Redis sono naturalmente adatti per memorizzare oggetti:sessioni, utenti, visitatori ecc. Questo lo rende una delle strutture dati chiave fornite da Redis.
- Nella sua forma ottimizzata per la memoria, è una scelta eccellente per memorizzare nella cache grandi quantità di dati.
Un archivio di indirizzi di oggetti
Poiché l'ottimizzazione della memoria è un caso d'uso importante per gli hash, discutiamo un esempio simile alla distribuzione di Instagram per mostrare come utilizzare le funzionalità di salvataggio della memoria degli hash Redis. Diciamo che abbiamo un'enorme distribuzione CAS (Content-addressable Storage) con centinaia di milioni di oggetti archiviati. La posizione di ogni oggetto è una stringa hash. Intendiamo sviluppare un sistema di ricerca per scoprire la posizione dell'oggetto dato il suo ID. Il modo tipico per farlo in Redis sarà usare una stringa.
set object:14590860 "007f80f0a62408..."
set object:11678 "009f80abcd0a60..."
...
Questo approccio funziona bene. Tuttavia, poiché il numero di oggetti che abbiamo è enorme, finiremo per aver bisogno di molta memoria per Redis. Vogliamo fare meglio. Prendiamo l'approccio hash ottimizzato per la memoria a questo problema. Dovremo scegliere i valori giusti per list-max-ziplist-entries e list-max-ziplist-value . Il valore corretto per list-max-ziplist-value è qualunque sia la lunghezza massima della stringa hash dell'indirizzo di archiviazione. Il valore di list-max-ziplist-entries deve essere mantenuto sufficientemente basso e dipenderà dal numero di secchi di hash totali che desideriamo creare. Sarà meglio capito empiricamente. Per es. per 100 milioni di oggetti potremmo scegliere di utilizzare 100.000 hash. Le voci massime in quel caso saranno 100 m / 100 k =1000. La logica dell'applicazione per decidere in quale hash va l'indirizzo di archiviazione di un oggetto può essere:dividere l'ID oggetto per 100 k e scartare il resto. Quindi l'ID oggetto 14590860 andrà in hash (14590860/100k) =145 cioè
hset object:145 14590860 "007f80f0a62408..."
hget object:145 14590860
> "007f80f0a62408..."
Questa implementazione non solo sarà molto più leggera in termini di memoria, ma dovrebbe anche fornire una buona posizione della cache.
Ecco i nostri altri post nella serie di strutture dati Redis.
- Set Redis
- Bitmap Redis
- Insiemi ordinati Redis