Cet article étudie principalement le contenu pertinent du débit élevé et du cache LRU en filetage, comme suit.
Il y a quelques années, j'ai implémenté un cache LRU pour trouver son ID pour les mots clés. La structure des données est très intéressante car le débit requis est suffisamment grand pour éliminer les problèmes de performances causés par le grand nombre de locks et les mots clés synchronized . L'application est implémentée en Java.
Je pensais qu'une série d'allocations de référence atomique maintiendrait l'ordre des LRU et du LRU dans ConcurrenthashMap. Au début, j'ai enveloppé la valeur dans l'entrée. L'entrée a un nœud dans la chaîne LRU de la liste à double liaison. La queue de la chaîne maintient l'entrée récemment utilisée et le nœud de tête stocke l'entrée qui peut être effacée lorsque le cache atteint une certaine taille. Chaque nœud pointe vers l'entrée utilisée pour trouver.
Lorsque vous recherchez la valeur via la clé, le cache doit d'abord rechercher la carte pour voir si cette valeur existe. S'il n'existe pas, il dépendra du chargeur pour lire la valeur à partir de la source de données de manière à lire et l'ajouter dans la carte dans le "Ajouter en cas de manque". Le défi d'assurer un débit élevé est de maintenir efficacement la chaîne LRU. Cette carte de hachage simultanée est segmentée et le niveau de thread est à un certain niveau (vous pouvez spécifier le niveau de concurrence lorsque vous construisez la carte) ne connaîtra pas trop de concours de threads. Mais la chaîne LRU ne peut-elle pas être divisée de la même manière? Pour résoudre ce problème, j'ai introduit une file d'attente auxiliaire pour la compensation des opérations.
Il existe six méthodes de base en cache. Pour les coups de cache, la recherche comprend deux opérations de base: Get and Offre, et pour une perte grossière, il existe quatre méthodes de base: obtenir, charger, mettre et offrir. Dans la méthode de put, nous devrons peut-être suivre l'opération claire. Lorsque le cache frappe, nous effectuons passivement un peu de compensation sur la chaîne LRU appelée opération de purification.
Obtenez: Entrée de recherche dans la carte par clé
Charge: valeur de chargement à partir d'une source de données
Put: créez l'entrée et mappez-la sur la clé
Offre: Ajoutez un nœud à la queue de la liste LRU qui fait référence à une entrée récemment accessible
Expice: supprimez les nœuds à la tête de la liste et les entrées associées de la carte (après que le cache atteint une certaine taille)
Purge: supprimez les nœuds inutilisés dans la liste LRU - nous appelons ces nœuds comme des trous, et la file d'attente de nettoyage garde la trace de ces
L'opération de compensation et le fonctionnement de purification sont tous deux de grands lots de données de traitement. Jetons un coup d'œil aux détails de chaque opération.
L'opération GET fonctionne comme suit:
get (k) -> V Entrée de recherche par clé k Si le cache frappe, nous avons une entrée e Offre Entrée E essayez de purger certains trous d'autre valeur de chargement V pour la clé k Création de l'entrée E <- (k, v) Essayez de mettre l'entrée End Valeur de retour EV
Si la clé existe, nous fournissons un nouveau nœud à la queue de la chaîne LRU pour indiquer qu'il s'agit d'une valeur récemment utilisée. L'exécution de Get and Offre n'est pas une opération atomique (pas de verrouillage ici), nous ne pouvons donc pas dire que ce nœud offert pointe vers l'entité la plus récemment utilisée, mais c'est certainement l'entité la plus récemment utilisée obtenue lorsque nous exécutons simultanément. Nous ne forcez pas à obtenir et à offrir d'exécuter l'ordre entre les threads, car cela peut limiter le débit. Après avoir offert un nœud, nous essayons de faire des opérations pour effacer et retourner la valeur. Jetons un coup d'œil à l'offre et aux opérations de purge en détail ci-dessous.
Si la perte de cache se produit, nous appellerons le chargeur pour charger la valeur de cette clé, créez une nouvelle entité et la mettrons dans la carte, et l'opération de put est la suivante:
put (e) -> e entrée existante ex <- map.putifabsen (ek, e) Si l'offre absente est entrée e; Si la taille atteint les expulsions d'expulsion des exvictions certaines entrées finissent l'entrée de retour e autrement, nous avons une entrée existante ex retour
Comme vous pouvez le voir, il peut y avoir une concurrence lorsque deux fils ou plus mettent une entité dans la carte, mais un seul succès est autorisé et l'offre sera appelée. Après avoir fourni un nœud à la queue de la chaîne LRU, nous devons vérifier si le cache a atteint son seuil, qui est l'identifiant que nous utilisons pour démarrer l'opération claire par lots. Dans ce scénario d'application spécifique, le réglage du seuil est inférieur à la capacité. L'opération de compensation se produit dans un petit lot plutôt que lorsque chaque entité est ajoutée. Plusieurs threads peuvent participer à l'opération de compensation jusqu'à ce que la capacité de cache atteigne sa capacité. Le verrouillage est facile mais les fils peuvent être sûrs. Le nettoyage du nœud de tête de la chaîne LRU nécessite un retrait, ce qui nécessite des opérations atomiques minutieuses pour éviter les opérations d'élimination multi-thread dans la carte.
Cette opération d'offre est très intéressante. Il essaie toujours de créer un nœud mais n'essaie pas de supprimer et de supprimer les nœuds qui ne sont plus utilisés immédiatement dans le LRU.
Offre (e) Si le nœud de queue ne fait pas référence à l'entrée E Attribuez le nœud actuel C <- EN Créez un nouveau nœud N (E), Nouveaux références de nœud à l'entrée E Si le nœud de comparaison et de l'ensemble atomique, attendez C, attribuez N Ajouter le nœud N à la queue de la liste LRU Si le nœud C Not Null Set Entry Ce
Tout d'abord, il vérifie que le nœud à la queue de la chaîne ne pointe pas vers l'entité accessible, ce qui n'est pas différent à moins que tous les threads n'accèdent fréquemment à la même paire de valeurs de clé. Il créera un nouveau nœud à la queue de la chaîne. Lorsque cette entité est différente, avant de fournir un nouveau nœud, il essaie de faire une comparaison et de régler une opération pour l'entité, ce qui empêchera plusieurs threads de faire la même chose.
Le fil qui alloue avec succès les nœuds fournit un nouveau nœud à la fin de la chaîne LRU. Cette opération est la même que la découverte dans ConcurrentLinkEdQueue. L'algorithme de dépendance est décrit dans l'article suivant. Les algorithmes de file d'attente simultanés simples, rapides et pratiques et de blocage. Le thread vérifiera alors si l'entité est liée à d'autres nœuds auparavant. Si c'est le cas, l'ancien nœud ne sera pas supprimé immédiatement, mais sera marqué comme un trou (la référence à son entité sera définie sur vide)
Ce qui précède est toute l'explication détaillée de cet article sur le cache LRU à haut débit et à filetage, et j'espère que cela sera utile à tout le monde. Les amis intéressés peuvent continuer à se référer à d'autres sujets connexes sur ce site. S'il y a des lacunes, veuillez laisser un message pour le signaler. Merci vos amis pour votre soutien pour ce site!