Dieser Artikel untersucht hauptsächlich den relevanten Inhalt des hohen Durchsatz- und Thread-sicheren LRU-Cache wie folgt.
Vor einigen Jahren habe ich einen LRU -Cache implementiert, um seine ID für Keywords zu finden. Die Datenstruktur ist sehr interessant, da der erforderliche Durchsatz groß genug ist, um die Leistungsprobleme zu beseitigen, die durch die große Anzahl von locks und synchronized Schlüsselwörtern verursacht werden. Die Anwendung wird in Java implementiert.
Ich dachte, dass eine Reihe von Atomreferenzzuweisungen LRUS und LRU in Concurrenthashmap halten würde. Am Anfang habe ich den Wert in den Eintrag eingewickelt. Der Eintrag enthält einen Knoten in der LRU-Kette der doppelt verknüpften Liste. Der Schwanz der Kette behält den kürzlich verwendeten Eintrag bei, und der Kopfknoten speichert den Eintrag, der gelöscht werden kann, wenn der Cache eine bestimmte Größe erreicht. Jeder Knoten zeigt auf den Eintrag, der verwendet wird, um zu finden.
Wenn Sie den Wert über den Schlüssel nachschlagen, muss der Cache zuerst nach der Karte suchen, um festzustellen, ob dieser Wert vorhanden ist. Wenn es nicht vorhanden ist, hängt es vom Loader ab, den Wert aus der Datenquelle auf durchlesende Weise zu lesen und ihn in der Karte in der "Hinzufügen zu fehlen" hinzuzufügen. Die Herausforderung, einen hohen Durchsatz sicherzustellen, besteht darin, die LRU -Kette effektiv aufrechtzuerhalten. Diese gleichzeitige Hash -Karte ist segmentiert und die Thread -Ebene befindet sich auf einer bestimmten Ebene (Sie können die Parallelitätsstufe angeben, wenn Sie die Karte erstellen), wird nicht zu viel Thread -Wettbewerb erfahren. Aber kann die LRU -Kette nicht auf die gleiche Weise geteilt werden? Um dieses Problem zu lösen, habe ich eine Hilfswarteschlange für den Clearing -Operationen eingeführt.
Es gibt sechs grundlegende Methoden im Cache. Für Cache -Hits umfasst die Suche zwei grundlegende Operationen: Get and Offer, und für grobe Verlust gibt es vier grundlegende Methoden: Get, Last, Put und Angebot. Bei der Put -Methode müssen wir möglicherweise den klaren Betrieb verfolgen. Wenn der Cache trifft, machen wir passiv einige Löschen der LRU -Kette, die als Reinigungsoperation bezeichnet wird.
Get: Sucheintrag in der Karte von Key
Laden: Laden Sie den Wert aus einer Datenquelle
Put: Erstellen Sie die Eingabe und ordnen Sie ihn dem Schlüssel ab
Angebot: Gehen Sie einen Knoten am Schwanz der LRU -Liste an, der sich auf einen kürzlich aufgerufenen Eintrag bezieht
REVICT: Entfernen Sie Knoten am Kopf der Liste und die zugehörigen Einträge von der Karte (nachdem der Cache eine bestimmte Größe erreicht hat)
Spülung: Löschen Sie ungenutzte Knoten in der LRU -Liste - Wir bezeichnen diese Knoten als Löcher, und die Aufräumwarteschlange verfolgt diese
Der Clearing -Betrieb und der Reinigungsvorgang sind beide große Chargen von Verarbeitungsdaten. Schauen wir uns die Details jeder Operation an.
Die Get -Operation funktioniert wie folgt:
GET (K) -> V SOKOUP -Eintrag von Key K Wenn der Cache -Hit einen Eintrag E -Eintrag anbieten. Versuchen Sie es mit einigen Löchern. Andernfalls Ladewert V für die Schlüssel K erstellen Ein Eintrag E < - (k, v) Versuchen
Wenn der Schlüssel vorhanden ist, stellen wir einen neuen Knoten am Schwanz der LRU -Kette zur Verfügung, um anzuzeigen, dass dies ein kürzlich verwendeter Wert ist. Die Ausführung von GET und Angebot ist keine Atomoperation (keine Sperre hier), daher können wir nicht sagen, dass dieser Knoten für die zuletzt verwendete Entität angeboten wird, aber es ist definitiv die zuletzt verwendete Entität, die bei gleichzeitiger Ausführung erhalten wurde. Wir erzwingen und bieten nicht an, Reihenfolge zwischen Threads auszuführen, da dies den Durchsatz einschränken kann. Nachdem wir einen Knoten angeboten haben, versuchen wir, einige Operationen auszuführen, um den Wert zu löschen und zurückzugeben. Schauen wir uns das Angebot und die Säuberungsvorgänge im Detail im Folgenden an.
Wenn der Cache -Verlust auftritt, rufen wir den Loader auf, den Wert für diesen Schlüssel zu laden, eine neue Entität zu erstellen und in die Karte zu stecken, und der Put -Betrieb lautet wie folgt:
Put (e) -> e vorhandene Eingabe ex < - map.putiFababSent (ek, e) Wenn nicht vorhanden angeboten wird, hat er einen Eintrag e; Wenn die Größe Evict-Schwellenwert-REVICT einige Einträge End-Return-Eintrag E an Sonst vorhanden ist, haben wir einen vorhandenen Eintrag EX EX ENDE
Wie Sie sehen können, kann es einen Wettbewerb geben, wenn zwei oder mehr Themen eine Entität in die Karte bringen, aber nur ein Erfolg ist zulässig und das Angebot wird aufgerufen. Nachdem wir einen Knoten am Schwanz der LRU -Kette zur Verfügung gestellt haben, müssen wir überprüfen, ob der Cache seinen Schwellenwert erreicht hat. Dies ist die Kennung, mit der wir den Batch -Clear -Betrieb starten. In diesem spezifischen Anwendungsszenario ist die Einstellung des Schwellenwerts kleiner als die Kapazität. Der Löschvorgang erfolgt eher in einer kleinen Stapel als wenn jede Entität hinzugefügt wird. Mehrere Threads können am Clearing -Betrieb teilnehmen, bis die Cache -Kapazität ihre Kapazität erreicht. Das Sperren ist einfach, aber Fäden können sicher sein. Das Löschen des Kopfknotens der LRU-Kette erfordert eine Entfernung, was sorgfältige atomare Operationen erfordert, um Multi-Thread-Entfernungsvorgänge in der Karte zu vermeiden.
Dieser Angebot ist sehr interessant. Es versucht immer, einen Knoten zu erstellen, versucht jedoch nicht, Knoten zu entfernen und zu löschen, die nicht mehr in der LRU verwendet werden.
Angebot (e) Wenn sich der Tail-Knoten nicht auf Eintrag bezieht. Die aktuelle Knoten c <-en erstellen einen neuen Knoten n (e), neue Knoten verweisen auf Eintrag E Wenn Atomic-Vergleich und Set-Knoten en, erwarten Sie C, addieren
Erstens wird überprüft, dass der Knoten am Schwanz der Kette nicht auf die Entität hinweist, auf die zugegriffen wurde, worauf nicht alle Threads auf das gleiche Schlüsselwertpaar zugreifen. Es wird einen neuen Knoten am Schwanz der Kette erzeugen. Wenn diese Entität unterschiedlich ist, bevor sie einen neuen Knoten anbietet, versucht sie, einen Vergleich zu erstellen und den Betrieb für die Entität festzulegen, was verhindert, dass mehrere Threads dasselbe tun.
Der Thread, der erfolgreich Knoten zuteilt, liefert am Ende der LRU -Kette einen neuen Knoten. Diese Operation ist der gleiche wie der Fund in Concurrent LinkedQueue. Der Abhängigkeitsalgorithmus wird im folgenden Artikel beschrieben. Einfache, schnelle und praktische nicht blockierende und blockierende gleichzeitige Warteschlangenalgorithmen. Der Thread prüft dann, ob die Entität zuvor mit anderen Knoten in Verbindung gebracht wird. In diesem Fall wird der alte Knoten nicht sofort gelöscht, sondern als Loch gekennzeichnet (der Verweis auf seine Entität wird auf leer eingestellt)
Das obige ist die detaillierte Erklärung dieses Artikels über Hochdurchsatz- und Thread-Safe-LRU-Cache, und ich hoffe, dass es für alle hilfreich sein wird. Interessierte Freunde können weiterhin auf andere verwandte Themen auf dieser Website verweisen. Wenn es Mängel gibt, hinterlassen Sie bitte eine Nachricht, um darauf hinzuweisen. Vielen Dank an Freunde für Ihre Unterstützung für diese Seite!