2R2L-Caching

Caching ist ein bekanntes und bekanntes Thema. Es können aber auch neue Lösungen darin erscheinen. Insbesondere - im Bereich der High-Level-Produkte (zum Beispiel in der Webentwicklung). Angesichts der Mängel des klassischen Ansatzes habe ich versucht, ein ideales Caching-Schema für den Fall abzuleiten, dass die Datenrelevanz nicht kritisch ist. Dann habe ich versucht, eine Beschreibung eines ähnlichen Schemas oder besser vorgefertigter Lösungen zu finden. Nicht gefunden. Deshalb habe ich es selbst genannt - 2R2L (2 Range 2 Location) - Zwei-Bereich-Zwei- "räumliches" Caching. Obwohl es wahrscheinlich schon irgendwo verwendet wird.



Alles begann mit einer einfachen Aufgabe - dem Benutzer neue Produkte unter Berücksichtigung seiner individuellen Vorlieben anzuzeigen. Und wenn es keine Probleme gab, neue Produkte zu erhalten, führte die Korrelation neuer Produkte mit Präferenzen (Analyse von Statistiken) bereits zu einer spürbaren Belastung (definieren wir sie beispielsweise nach 4 Sekunden). Die Besonderheit der Aufgabe bestand darin, dass ganze Organisationen als Benutzer fungieren konnten. Und es ist nicht ungewöhnlich, dass 200-300 Anfragen eines Benutzers sofort (innerhalb von 2-3 Sekunden) beim Server eintreffen. Jene. Der gleiche Block wird für viele Benutzer gleichzeitig generiert.



Die naheliegende Lösung besteht darin, es im RAM zwischenzuspeichern (wir werden das DBMS keiner Gewalt aussetzen und es dazu zwingen, einen großen Fluss von Anrufen zu verarbeiten). Klassisches Schema:



  1. Anfrage kam
  2. Überprüfen des Cache. Wenn Daten darin enthalten sind und diese nicht veraltet sind, geben wir sie einfach zurück.
  3. Keine Daten => ein Problem erzeugen
  4. Wir senden an den Benutzer
  5. Zusätzlich fügen wir es dem Cache hinzu und geben die TTL an


Der Nachteil dieser Lösung: Wenn sich keine Daten im Cache befinden, werden sie von allen Anforderungen der ersten Generation generiert, wobei Serverressourcen dafür aufgewendet werden (Lastspitzen). Und natürlich warten alle Benutzer beim "ersten Anruf".



Beachten Sie auch, dass bei einzelnen Cache-Werten die Anzahl der Einträge so stark ansteigen kann, dass der verfügbare Server-RAM einfach nicht ausreicht. Dann erscheint es logisch, einen lokalen Festplattenserver als Cache-Speicher zu verwenden. Aber wir verlieren sofort an Geschwindigkeit.



Wie soll ich sein?



Das erste, was mir in den Sinn kommt: Es wäre großartig, Datensätze an zwei Orten zu speichern - im RAM (häufig angefordert) und auf der Festplatte (alle oder nur selten angefordert). Das Konzept der "heißen und kalten Daten" in seiner reinsten Form. Es gibt viele Implementierungen dieses Ansatzes, daher werden wir nicht weiter darauf eingehen. Lassen Sie uns diese Komponente einfach als 2L bezeichnen. In meinem Fall wird es erfolgreich basierend auf dem Scylla DBMS implementiert.



Aber wie kann man Drawdowns beseitigen, wenn der Cache veraltet ist? Und hier schließen wir das Konzept von 2R ein, dessen Bedeutung einfach ist: Für einen Cache-Datensatz müssen Sie nicht 1 TTL-Wert angeben, sondern 2. TTL1 ist ein Zeitstempel, der bedeutet, dass "Daten veraltet sind, sie sollten neu generiert werden, aber Sie können sie trotzdem verwenden". TTL2 - "Alles ist so veraltet, dass es nicht mehr verwendet werden kann."



Somit erhalten wir ein etwas anderes Schema für das Caching:



  1. Anfrage kam
  2. Wir suchen nach Daten im Cache. Wenn die Daten vorhanden und nicht veraltet sind (t <TTL1), geben wir sie wie gewohnt an den Benutzer zurück und tun nichts anderes.
  3. Die Daten sind dort veraltet, aber Sie können sie verwenden (TTL1 <t <TTL2) - geben Sie sie dem Benutzer und initialisieren Sie die Prozedur zum Aktualisieren des Cache-Datensatzes
  4. Es gibt überhaupt keine Daten (nach Ablauf von TTL2 getötet) - wir generieren sie "wie gewohnt" und schreiben sie in den Cache.
  5. Nachdem wir den Inhalt dem Benutzer oder in einem parallelen Stream bereitgestellt haben, führen wir die Verfahren zum Aktualisieren der Cache-Datensätze aus.


Als Ergebnis haben wir:



  • Wenn Cache-Datensätze häufig genug verwendet werden, befindet sich der Benutzer nie in der Situation, "auf die Aktualisierung des Caches zu warten" - er erhält immer ein vorgefertigtes Ergebnis.
  • Wenn die Warteschlange der "Aktualisierungen" ordnungsgemäß organisiert ist, kann die Tatsache erreicht werden, dass bei mehreren gleichzeitigen Zugriffen auf den Datensatz mit TTL1 <t <TTL2 nur eine Aufgabe zum Aktualisieren in der Warteschlange vorhanden ist und nicht mehrere identische.


Beispiel: Für einen neuen Produkt-Feed können Sie TTL1 = 1 Stunde (neuer Inhalt wird jedoch nicht sehr intensiv angezeigt) und TTL2 - 1 Woche angeben.



Im einfachsten Fall könnte der PHP-Code zur Implementierung von 2R sein:



$tmp = cache_get($key);
If (!$tmp){
	$items = generate_items();
	cache_set($items, 60*60, 60*60*24*7);
}else{
	$items = $tmp[‘items’];
	If (time()-$tmp[‘tm’] > 60*60){
		$need_rebuild[] = [‘to’=>$key, ‘method’=>’generate_items’];
}
}
//   
echo json_encode($items);
//     ,   
If (isset($need_rebuild) && count($need_rebuild)>0){
	foreach($need_rebuild as $k=>$v){
		$tmp = ['tm'=>time(), 'items'=>$$v[‘method’]];
		cache_set($tmp, 60*60, 60*60*24*7);
}
}


In der Praxis dürfte die Umsetzung natürlich schwieriger sein. Ein Cache-Datensatzgenerator ist beispielsweise ein separates Skript, das als Dienst gestartet wird. Warteschlange - über Rabbit das Zeichen "Ein solcher Schlüssel befindet sich bereits in der Warteschlange zur Regeneration" - über Redis oder Scylla.



Wenn wir also den "Zwei-Band" -Ansatz und das Konzept der "heißen / kalten" Daten kombinieren, erhalten wir - 2R2L.



Danke!



All Articles