J'essaie de répondre à deux questions dans une liste définitive:
J'ai donc lu que les listes Redis sont en réalité mises en œuvre avec des listes chaînées. Mais pour les autres types, je ne suis pas en mesure de creuser aucune information. En outre, si quelqu'un tombait sur cette question sans avoir un résumé détaillé des avantages et inconvénients de la modification ou de l'accès à différentes structures de données, il disposerait d'une liste complète du meilleur moment. utilisez des types spécifiques pour faire référence également.
Plus précisément, je cherche à décrire tous les types: chaîne, liste, ensemble, zset et hachage.
Oh, j'ai déjà lu ces articles, entre autres:
Je vais essayer de répondre à votre question, mais je vais commencer par quelque chose qui peut paraître étrange au début: si vous n'êtes pas intéressé par Redis internals, vous ne devriez pas vous en préoccuper sur la manière dont les types de données sont implémentés en interne. C’est pour une raison simple: pour chaque opération Redis, vous trouverez la complexité temporelle dans la documentation et, si vous avez le jeu d’opérations et la complexité chronologique, la seule autre chose dont vous avez besoin est un indice sur l’utilisation de la mémoire (et parce que nous faisons de nombreuses optimisations qui peuvent varier en fonction des données, le meilleur moyen d’obtenir ces derniers chiffres est de faire quelques tests triviaux dans le monde réel).
Mais puisque vous avez demandé, voici l’implémentation sous-jacente de chaque type de données Redis.
Toutefois, lorsque les listes, les ensembles et les ensembles triés ont un nombre d'éléments et une taille des valeurs les plus élevés réduits, un codage différent, beaucoup plus compact, est utilisé. Cet encodage diffère selon les types, mais présente la particularité d'être un bloc de données compact qui oblige souvent un balayage O(N) à chaque opération. Puisque nous utilisons ce format uniquement pour les petits objets, cela n’est pas un problème; balayer un petit blob O(N) est cache inconscient donc, en pratique, il est très rapide, et quand il y a trop d'éléments, l'encodage passe automatiquement à l'encodage natif (liste chaînée, hash, etc.).
Mais votre question ne portait pas uniquement sur les internes, votre argument était Quel type utiliser pour accomplir quoi?.
C'est le type de base de tous les types. C'est l'un des quatre types, mais c'est également le type de base des types complexes, car une liste est une liste de chaînes, un ensemble est un ensemble de chaînes, etc.
Une chaîne Redis est une bonne idée dans tous les scénarios évidents dans lesquels vous souhaitez stocker une page HTML, mais également dans laquelle vous souhaitez éviter de convertir vos données déjà codées. Ainsi, par exemple, si vous avez JSON ou MessagePack, vous pouvez simplement stocker des objets sous forme de chaînes. Dans Redis 2.6, vous pouvez même manipuler ce type d'objet côté serveur à l'aide de scripts Lua.
Les bitmaps sont une autre utilisation intéressante des chaînes et, en général, des tableaux d'octets à accès aléatoire, car Redis exporte des commandes pour accéder à des plages aléatoires d'octets, voire de bits simples. Par exemple, vérifiez ce bon article de blog: Métriques en temps réel rapides et faciles avec Redis .
Les listes sont bonnes lorsque vous êtes susceptible de ne toucher que les extrêmes de la liste: près de la queue ou près de la tête. Les listes ne sont pas très bonnes pour paginer des choses, car l'accès aléatoire est lent, O (N). Les listes de piles et piles en clair, ou le traitement d'éléments dans une boucle à l'aide de RPOPLPUSH avec la même source et la même destination pour "faire pivoter" un anneau d'éléments, sont donc les bonnes utilisations des listes.
Les listes sont également utiles lorsque nous voulons simplement créer une collection limitée de N éléments où généralement nous n’avons accès qu’aux éléments du haut ou du bas, ou lorsque N est petit.
Les ensembles constituent une collection de données non ordonnée. Ils sont donc utiles chaque fois que vous avez une collection d'éléments. Il est donc très important de vérifier très rapidement l'existence ou la taille de la collection. Une autre chose intéressante à propos des ensembles est la prise en charge des éléments aléatoires furtifs ou sautants (commandes SRANDMEMBER et SPOP).
Les ensembles sont également utiles pour représenter des relations, par exemple "Quels sont les amis de l'utilisateur X?" et ainsi de suite. Mais d'autres bonnes structures de données pour ce genre de choses sont des ensembles triés comme nous le verrons.
Les ensembles supportent des opérations complexes comme les intersections, les unions, etc. C'est donc une bonne structure de données pour utiliser Redis de manière "informatique", lorsque vous avez des données et que vous souhaitez effectuer des transformations sur ces données pour obtenir une sortie.
Les petits ensembles sont encodés de manière très efficace.
Les hachages constituent la structure de données idéale pour représenter des objets, composés de champs et de valeurs. Les champs de hachage peuvent également être incrémentés de manière atomique à l'aide de HINCRBY. Lorsque vous avez des objets tels que des utilisateurs, des articles de blog ou un autre type de item, les hachages sont probablement la voie à suivre si vous ne souhaitez pas utiliser votre propre encodage tel que JSON ou similaire.
Cependant, gardez à l’esprit que Redis encode très efficacement les petits hachages et vous pouvez lui demander d’atomographier GET, SET ou d’incrémenter les champs individuels très rapidement.
Les hachages peuvent également être utilisés pour représenter des structures de données liées, à l'aide de références. Par exemple, vérifiez la mise en œuvre des commentaires par lamernews.com.
Les ensembles triés sont les seules les autres structures de données, à part les listes, permettant de conserver les éléments ordonnés. Vous pouvez faire un certain nombre de choses intéressantes avec des ensembles triés. Par exemple, vous pouvez avoir toutes sortes de listes en haut de quelque chose dans votre application Web. Meilleurs utilisateurs par score, meilleurs messages par pages vues, peu importe, mais une seule instance Redis prendra en charge des tonnes d'opérations d'insertion et d'objectifs par seconde.
Les ensembles triés, comme les ensembles normaux, peuvent être utilisés pour décrire des relations, mais ils vous permettent également de paginer la liste des éléments et de mémoriser l'ordre. Par exemple, si je me souviens des amis de l'utilisateur X avec un ensemble trié, je peux facilement les retenir par ordre d'amitié acceptée.
Les ensembles triés conviennent aux files d'attente prioritaires.
Les ensembles triés sont comme des listes plus puissantes, dans lesquelles l'insertion, la suppression ou l'extraction de plages au milieu de la liste est toujours rapide. Mais ils utilisent plus de mémoire et sont des structures de données O(log(N)).
J'espère que j'ai fourni quelques informations dans ce post, mais il est de loin préférable de télécharger le code source de lamernews à partir de http://github.com/antirez/lamernews et de comprendre comment cela fonctionne. De nombreuses structures de données de Redis sont utilisées dans Lamer News et de nombreux indices permettent de savoir quoi utiliser pour résoudre une tâche donnée.
Désolé pour les fautes de grammaire, il est minuit ici et trop fatigué pour relire l'article;)
La plupart du temps, vous n'avez pas besoin de comprendre les structures de données sous-jacentes utilisées par Redis. Mais un peu de connaissance vous aide à faire des compromis entre mémoire et processeur. Il vous aide également à modéliser vos données de manière efficace.
En interne, Redis utilise les structures de données suivantes:
Pour trouver le codage utilisé par une clé particulière, utilisez la commande object encoding <key>
.
Dans Redis, les chaînes sont appelées chaînes dynamiques simples, ou SDS . C'est une petite enveloppe sur un char *
qui vous permet de stocker la longueur de la chaîne et le nombre d'octets libres comme préfixe.
Comme la longueur de la chaîne est stockée, strlen est une opération O(1). De plus, comme la longueur est connue, les chaînes Redis sont sécurisées pour les binaires. parfaitement légal pour une chaîne de contenir le caractère null .
Les chaînes sont la structure de données la plus polyvalente disponible dans Redis. Une chaîne est tout :
long
pouvant stocker des nombres. Voir INCR , DÉCR , INCRBY et COMMANDES .chars
, ints
, longs
ou de tout autre type de données) pouvant permettre un accès aléatoire efficace. Voir les commandes SETRANGE et GETRANGE .Redis utilise un Dictionary pour les éléments suivants:
Les dictionnaires Redis sont implémentés à l'aide de Tables de hachage . Au lieu d’expliquer la mise en œuvre, je vais simplement expliquer les spécificités de Redis:
dictType
pour étendre le comportement d'une table de hachage. Cette structure comporte des pointeurs de fonction. Les opérations suivantes sont donc extensibles: a) fonction de hachage, b) comparaison de clé, c) destructeur de clé et d) destructeur de valeur.La structure de données Set
utilise un dictionnaire pour garantir l’absence de doublons. Le Sorted Set
utilise un dictionnaire pour mapper un élément sur sa partition, ce qui explique pourquoi ZSCORE est une opération O(1) .
Le type de données list
est implémenté à l'aide de Listes Doublement-Liées . L'implémentation de Redis est directement issue de l'algorithme. Le seul changement est que Redis stocke la longueur dans la structure de données de la liste. Cela garantit que LLEN a O(1) complexité.
Redis utilise Ignorer les listes comme structure de données sous-jacente pour les ensembles triés. Wikipedia a une bonne introduction. L'article de William Pugh Skip Lists: une alternative probabiliste aux arbres équilibrés contient plus de détails.
Les ensembles triés utilisent à la fois une liste de contrôle et un dictionnaire. Le dictionnaire stocke le score de chaque élément.
L'implémentation de la liste de refus de Redis diffère de l'implémentation standard des manières suivantes:
Une liste zip ressemble à une liste doublement chaînée, sauf qu'elle n'utilise pas de pointeur et stocke les données en ligne.
Chaque nœud d'une liste doublement chaînée a 3 pointeurs: un pointeur avant, un pointeur arrière et un pointeur pour référencer les données stockées sur ce noeud. Les pointeurs ont besoin de mémoire (8 octets sur un système 64 bits). Ainsi, pour les petites listes, une liste doublement chaînée est très inefficace.
Une liste Zip stocke les éléments de manière séquentielle dans une chaîne Redis. Chaque élément a un petit en-tête qui stocke la longueur et le type de données de l'élément, le décalage par rapport à l'élément suivant et le décalage par rapport à l'élément précédent. Ces décalages remplacent les pointeurs avant et arrière. Puisque les données sont stockées en ligne, nous n’avons pas besoin d’un pointeur de données.
La liste Zip est utilisée pour stocker de petites listes, des ensembles triés et des hachages. Les ensembles triés sont aplatis dans une liste comme [element1, score1, element2, score2, element3, score3]
et stocké dans la liste zip. Les hachages sont aplatis dans une liste comme [key1, value1, key2, value2]
etc.
Avec les listes zip, vous avez le pouvoir de faire un compromis entre le processeur et la mémoire. Les listes zip sont efficaces en termes de mémoire, mais elles utilisent plus de ressources processeur qu'une liste chaînée (ou table de hachage/liste de sauts). La recherche d'un élément dans la liste zip est O (n). L'insertion d'un nouvel élément nécessite la réaffectation de mémoire. De ce fait, Redis utilise cet encodage uniquement pour les petites listes, les hachages et les ensembles triés. Vous pouvez modifier ce comportement en modifiant les valeurs de <datatype>-max-ziplist-entries
et <datatype>-max-ziplist-value>
dans redis.conf. Voir Optimisation de la mémoire, section "Codage spécial de petits types de données agrégées" pour plus d'informations.
Les commentaires sur ziplist.c sont excellents et vous pouvez parfaitement comprendre cette structure de données sans avoir à lire le code.
Int Sets est un nom de fantaisie pour "Tried Integer Arrays".
Dans Redis, les ensembles sont généralement implémentés à l'aide de tables de hachage. Pour les petits ensembles, une table de hachage est inefficace en termes de mémoire. Lorsque l'ensemble est composé uniquement d'entiers, un tableau est souvent plus efficace.
Un Int Set est un tableau trié d'entiers. Pour trouver un élément, un algorithme de recherche binaire est utilisé. Cela a une complexité de O (log N). L'ajout de nouveaux entiers à ce tableau peut nécessiter une réallocation de mémoire, ce qui peut coûter cher pour des tableaux d'entiers de grande taille.
Comme autre optimisation de la mémoire, Int Set existe en 3 variantes avec différentes tailles d’entiers: 16 bits, 32 bits et 64 bits. Redis est assez intelligent pour utiliser la bonne variante en fonction de la taille des éléments. Lorsqu'un nouvel élément est ajouté et qu'il dépasse la taille actuelle, Redis le migre automatiquement vers la taille suivante. Si une chaîne est ajoutée, Redis convertit automatiquement l'ensemble Int en un ensemble standard basé sur une table de hachage.
Les ensembles int sont un compromis entre le processeur et la mémoire. Les ensembles Int sont extrêmement efficaces en termes de mémoire et, pour les petits ensembles, ils sont plus rapides qu'une table de hachage. Mais après un certain nombre d’éléments, le temps de récupération de O (log N) et le coût de la réaffectation de mémoire deviennent trop importants. Sur la base d'expériences, le seuil optimal pour passer à une table de hachage normale s'est avéré être 512. Toutefois, vous pouvez augmenter ce seuil (le diminuer n'a pas de sens) en fonction des besoins de votre application. Voir set-max-intset-entries
dans redis.conf.
Les Zip Maps sont des dictionnaires aplatis et stockés dans une liste. Ils ressemblent beaucoup aux listes ZIP.
Les Zip Maps sont obsolètes depuis Redis 2.6 et de petits hachages sont stockés dans des listes Zip. Pour en savoir plus sur cet encodage, référez-vous à commentaires dans zipmap.c .
Redis stocke les clés pointant vers des valeurs. Les clés peuvent avoir toute valeur binaire jusqu’à une taille raisonnable (l’utilisation de chaînes courtes ASCII est recommandée pour des raisons de lisibilité et de débogage). Les valeurs sont l’un des cinq types de données Redis natifs.
1.strings - une séquence d'octets sécurisés binaires jusqu'à 512 Mo
2.hashes - une collection de paires clé-valeur
3.lists - une collection de chaînes dans l'ordre d'insertion
4.sets - une collection de chaînes uniques sans ordre
5. ensembles triés - une collection de chaînes uniques ordonnées par le score défini par l'utilisateur
Chaînes
Une chaîne Redis est une séquence d'octets.
Les chaînes dans Redis sont sécurisées binaires (ce qui signifie qu'elles ont une longueur connue non déterminée par des caractères de fin spéciaux). Vous pouvez donc stocker jusqu'à 512 mégaoctets dans une chaîne.
Les chaînes sont le concept cannonique du "magasin de valeurs clés". Vous avez une clé pointant vers une valeur, clé et valeur étant du texte ou des chaînes binaires.
Pour toutes les opérations possibles sur les chaînes, voir le http://redis.io/commands/#string
Hash
Un hachage Redis est un ensemble de paires de valeurs clés.
Un hachage Redis contient plusieurs paires de valeurs de clé, où chaque clé et valeur est une chaîne. Les hashs Redis ne supportent pas directement les valeurs complexes (cela signifie qu'un champ de hachage ne peut pas avoir la valeur d'une liste, d'un ensemble ou d'un autre hachage), mais vous pouvez utiliser des champs de hachage pour pointer sur d'autres valeurs complexes de niveau supérieur. La seule opération spéciale que vous pouvez effectuer sur les valeurs de champ de hachage est l'incrément/décrément atomique du contenu numérique.
Vous pouvez imaginer un hachage Redis de deux manières: en tant que représentation d'objet directe et en tant que moyen de stocker de manière compacte de nombreuses petites valeurs.
Les représentations d'objet directes sont simples à comprendre. Les objets ont un nom (la clé du hachage) et une collection de clés internes avec des valeurs. Voir l'exemple ci-dessous pour un exemple.
Stocker de nombreuses petites valeurs en utilisant un hachage est une technique intelligente de stockage de données massive Redis. Lorsqu'un hachage comporte un petit nombre de champs (~ 100), Redis optimise l'efficacité de stockage et d'accès de l'ensemble du hachage. L'optimisation du stockage de hachage réduit de Redis suscite un comportement intéressant: il est plus efficace de disposer de 100 hachages avec chacun 100 clés et valeurs internes plutôt que d'avoir 10 000 clés de niveau supérieur pointant vers des valeurs de chaîne. L'utilisation de Redis hashes pour optimiser votre stockage de données nécessite ainsi un temps système supplémentaire de programmation, mais si votre stockage de données repose principalement sur des chaînes, vous pouvez économiser beaucoup de temps système en utilisant ce truc étrange.
Pour toutes les opérations possibles sur les hachages, voir le hash docs
Listes
Les listes Redis agissent comme des listes chaînées.
Vous pouvez insérer, supprimer et parcourir des listes à partir de l'en-tête ou de la fin d'une liste.
Utilisez des listes lorsque vous devez conserver les valeurs dans l'ordre dans lequel elles ont été insérées. (Redis vous donne la possibilité d’insérer dans une position de liste arbitraire si vous en avez besoin, mais vos performances d’insertion se dégraderont si vous insérez loin de votre position de départ.)
Les listes Redis sont souvent utilisées en tant que files d'attente producteurs/consommateurs. Insérer des éléments dans une liste, puis extraire des éléments de la liste. Qu'advient-il si vos consommateurs essaient de sortir d'une liste sans éléments? Vous pouvez demander à Redis d'attendre qu'un élément apparaisse et de vous le retourner immédiatement après son ajout. Cela transforme Redis en une file d'attente de messages/événement/tâche/tâche/système de notification en temps réel.
Vous pouvez supprimer de manière atomique des éléments situés à l'une des extrémités d'une liste, ce qui permet de traiter n'importe quelle liste comme une pile ou une file d'attente.
Vous pouvez également gérer des listes de longueur fixe (collections limitées) en réduisant votre liste à une taille spécifique après chaque insertion.
Pour toutes les opérations possibles sur les listes, voir le listes de documents
Définit
Les ensembles Redis sont, enfin, des ensembles.
Un jeu Redis contient des chaînes Redis uniques non ordonnées dans lesquelles chaque chaîne n'existe qu'une seule fois par jeu. Si vous ajoutez le même élément dix fois à un ensemble, il ne s'affichera qu'une fois. Les ensembles sont parfaits pour s'assurer paresseusement que quelque chose existe au moins une fois sans se soucier des éléments en double accumulant et gaspillant de l'espace. Vous pouvez ajouter la même chaîne autant de fois que vous le souhaitez sans avoir à vérifier si elle existe déjà.
Les ensembles sont rapides pour la vérification, l'insertion et la suppression des membres de l'ensemble.
Les ensembles ont des opérations d’ensemble efficaces, comme on peut s’y attendre. Vous pouvez prendre l'union, l'intersection et la différence de plusieurs ensembles à la fois. Les résultats peuvent être renvoyés à l'appelant ou stockés dans un nouvel ensemble pour une utilisation ultérieure.
Les ensembles ont un accès permanent aux contrôles d’effectif (contrairement aux listes), et Redis permet même de retirer et de retourner des membres aléatoires ("extraire un élément aléatoire de l’ensemble") ou de revenir au hasard sans remplacement ("donnez-moi 30 utilisateurs uniques aléatoires" ") ou avec remplacement (" donnez-moi 7 cartes, mais après chaque sélection, remettez-la en place afin qu’elle puisse être à nouveau échantillonnée ").
Pour toutes les opérations possibles sur les ensembles, voir docs sur les ensembles .
Ensembles triés
Les ensembles triés Redis sont des ensembles avec un ordre défini par l'utilisateur.
Pour plus de simplicité, vous pouvez considérer un ensemble trié comme un arbre binaire avec des éléments uniques. (Les ensembles triés par Redis sont en réalité ignorer les listes .) L'ordre de tri des éléments est défini par le score de chaque élément.
Les ensembles triés sont toujours des ensembles. Les éléments ne peuvent apparaître qu'une fois dans un ensemble. Un élément, à des fins d'unicité, est défini par son contenu de chaîne. L'insertion de l'élément "Apple" avec le score de tri 3, puis l'insertion de l'élément "Apple" avec le score de tri 500 ont pour résultat un élément "Apple" avec le score de tri 500 dans votre ensemble trié. Les ensembles sont uniquement uniques en fonction des données, et non en fonction de paires (score, données).
Assurez-vous que votre modèle de données s'appuie sur le contenu de la chaîne et non sur le score de l'élément pour son caractère unique. Les scores sont autorisés à être répétés (voire à zéro), mais, une dernière fois, les éléments d'ensemble ne peuvent exister qu'une fois par ensemble trié. Par exemple, si vous essayez de stocker l'historique de chaque connexion d'utilisateur sous la forme d'un ensemble trié en faisant du score l'époque de la connexion et la valeur l'identifiant de l'utilisateur, vous ne stockerez que la dernière époque de connexion de tous vos utilisateurs. Votre ensemble atteindra la taille de votre base utilisateur et non la taille souhaitée de vos connexions utilisateur *.
Les éléments sont ajoutés à votre ensemble avec des scores. Vous pouvez mettre à jour la partition de n'importe quel élément à tout moment, il vous suffit d'ajouter à nouveau l'élément avec une nouvelle partition. Les scores sont représentés par des doubles en virgule flottante. Vous pouvez ainsi spécifier la granularité des horodatages de haute précision, si nécessaire. Plusieurs éléments peuvent avoir le même score.
Vous pouvez récupérer des éléments de différentes manières. Puisque tout est trié, vous pouvez demander des éléments commençant par les scores les plus bas. Vous pouvez demander des éléments commençant par les scores les plus élevés ("à l'envers"). Vous pouvez demander des éléments par leur score de tri, soit dans l'ordre naturel, soit dans l'ordre inverse.
Pour toutes les opérations possibles sur les ensembles triés, voir la documentation docs des ensembles triés