Zwischenspeichern

Der Cache beschleunigt Ihre Anwendung, indem er Daten, die einmal hart abgerufen wurden, für die spätere Verwendung speichert. Wir zeigen es Ihnen:

  • Wie Sie den Cache nutzen
  • Wie Sie den Cache-Speicher ändern
  • Wie man den Cache richtig ungültig macht

Die Verwendung des Cache ist in Nette sehr einfach, deckt aber auch sehr fortgeschrittene Cache-Anforderungen ab. Er ist auf Leistung und 100%ige Haltbarkeit ausgelegt. Im Grunde finden Sie Adapter für die gängigsten Backend-Speicher. Ermöglicht Tag-basierte Invalidierung, Cache-Stampede-Schutz, Zeitablauf, etc.

Installation

Laden Sie das Paket herunter und installieren Sie es mit Composer:

composer require nette/caching

Grundlegende Verwendung

Im Mittelpunkt der Arbeit mit dem Cache steht das Objekt Nette\Caching\Cache. Wir erstellen seine Instanz und übergeben dem Konstruktor als Parameter den sogenannten Speicher. Dabei handelt es sich um ein Objekt, das den Ort repräsentiert, an dem die Daten physisch gespeichert werden (Datenbank, Memcached, Dateien auf der Festplatte, …). Sie erhalten das Storage-Objekt, indem Sie es per Dependency Injection mit dem Typ Nette\Caching\Storage übergeben. Alles Wesentliche erfahren Sie im Abschnitt Storage.

In Version 3.0 hatte die Schnittstelle noch den I prefix, so the name was Nette\Caching\IStorage. Außerdem wurden die Konstanten der Klasse Cache großgeschrieben, also zum Beispiel Cache::EXPIRE statt Cache::Expire.

Für die folgenden Beispiele nehmen wir an, wir haben einen Alias Cache und eine Speicherung in der Variablen $storage.

use Nette\Caching\Cache;

$storage = /* ... */; // Instanz von Nette\Caching\Storage

Der Cache ist eigentlich ein Schlüsselwertspeicher, d. h. wir lesen und schreiben Daten unter Schlüsseln, genau wie bei assoziativen Arrays. Anwendungen bestehen aus einer Reihe unabhängiger Teile, und wenn sie alle einen Speicher verwenden würden (z. B. ein Verzeichnis auf einer Festplatte), käme es früher oder später zu einer Schlüsselkollision. Das Nette Framework löst das Problem, indem es den gesamten Speicherplatz in Namensräume (Unterverzeichnisse) unterteilt. Jeder Teil des Programms verwendet dann seinen eigenen Bereich mit einem eindeutigen Namen, und es können keine Kollisionen auftreten.

Der Name des Spaces wird als zweiter Parameter des Konstruktors der Cache-Klasse angegeben:

$cache = new Cache($storage, 'Full Html Pages');

Wir können nun das Objekt $cache verwenden, um aus dem Cache zu lesen und zu schreiben. Die Methode load() wird für beides verwendet. Das erste Argument ist der Schlüssel und das zweite ist der PHP-Callback, der aufgerufen wird, wenn der Schlüssel nicht im Cache gefunden wird. Der Callback generiert einen Wert, gibt ihn zurück und speichert ihn im Cache:

$value = $cache->load($key, function () use ($key) {
	$computedValue = /* ... */; // schwere Berechnungen
	return $computedValue;
});

Wenn der zweite Parameter nicht angegeben wird $value = $cache->load($key), wird null zurückgegeben, wenn sich das Element nicht im Cache befindet.

Das Tolle daran ist, dass beliebige serialisierbare Strukturen zwischengespeichert werden können, nicht nur Zeichenketten. Dasselbe gilt auch für Schlüssel.

Das Element wird mit der Methode remove() aus dem Cache geleert:

$cache->remove($key);

Sie können ein Element auch mit der Methode $cache->save($key, $value, array $dependencies = []) zwischenspeichern. Die obige Methode mit load() ist jedoch vorzuziehen.

Zwischenspeicherung

Memoisierung bedeutet, dass das Ergebnis einer Funktion oder Methode zwischengespeichert wird, so dass Sie es beim nächsten Mal verwenden können, anstatt immer wieder dasselbe zu berechnen.

Methoden und Funktionen können mit call(callable $callback, ...$args) memoisiert aufgerufen werden:

$result = $cache->call('gethostbyaddr', $ip);

Die Funktion gethostbyaddr() wird nur einmal für jeden Parameter $ip aufgerufen und beim nächsten Mal wird der Wert aus dem Cache zurückgegeben.

Es ist auch möglich, einen memoisierten Wrapper für eine Methode oder Funktion zu erstellen, der später aufgerufen werden kann:

function factorial($num)
{
	return /* ... */;
}

$memoizedFactorial = $cache->wrap('factorial');

$result = $memoizedFactorial(5); // zählt es
$result = $memoizedFactorial(5); // gibt es aus dem Cache zurück

Verfall & Ungültigmachung

Bei der Zwischenspeicherung ist es notwendig, sich mit der Frage zu befassen, dass einige der zuvor gespeicherten Daten mit der Zeit ungültig werden. Nette Framework bietet einen Mechanismus, um die Gültigkeit von Daten zu begrenzen und sie auf kontrollierte Weise zu löschen (“sie ungültig zu machen”, um die Terminologie des Frameworks zu verwenden).

Die Gültigkeit der Daten wird zum Zeitpunkt des Speicherns mit dem dritten Parameter der Methode save() festgelegt, z. B:

$cache->save($key, $value, [
	$cache::Expire => '20 minutes',
]);

Oder über den Parameter $dependencies, der als Referenz an den Callback in der Methode load() übergeben wird, z. B:

$value = $cache->load($key, function (&$dependencies) {
	$dependencies[Cache::Expire] = '20 minutes';
	return /* ... */;
});

Oder unter Verwendung des 3. Parameters in der Methode load(), z. B:

$value = $cache->load($key, function () {
	return ...;
}, [Cache::Expire => '20 minutes']);

In den folgenden Beispielen gehen wir von der zweiten Variante und damit vom Vorhandensein einer Variablen $dependencies aus.

Verfall

Die einfachste Verfallszeit ist das Zeitlimit. Hier sehen Sie, wie Sie Daten mit einer Gültigkeit von 20 Minuten zwischenspeichern können:

// er akzeptiert auch die Anzahl der Sekunden oder den UNIX-Zeitstempel
$dependencies[Cache::Expire] = '20 minutes';

Wenn wir die Gültigkeitsdauer bei jedem Lesen verlängern wollen, kann dies auf diese Weise erreicht werden, aber Achtung, dies erhöht den Cache-Overhead:

$dependencies[Cache::Sliding] = true;

Die praktische Option ist die Möglichkeit, die Daten ablaufen zu lassen, wenn eine bestimmte Datei oder eine von mehreren Dateien geändert wird. Dies kann z.B. für die Zwischenspeicherung von Daten verwendet werden, die aus der Verarbeitung dieser Dateien resultieren. Absolute Pfade verwenden.

$dependencies[Cache::Files] = '/path/to/data.yaml';
// oder
$dependencies[Cache::Files] = ['/path/to/data1.yaml', '/path/to/data2.yaml'];

Wir können ein Element im Cache ablaufen lassen, wenn ein anderes Element (oder eines von mehreren anderen) abläuft. Dies kann verwendet werden, wenn wir die gesamte HTML-Seite und Fragmente davon unter anderen Schlüsseln zwischenspeichern. Sobald sich das Snippet ändert, wird die gesamte Seite ungültig. Wenn wir Fragmente unter Schlüsseln wie frag1 und frag2 gespeichert haben, verwenden wir:

$dependencies[Cache::Items] = ['frag1', 'frag2'];

Der Ablauf kann auch über benutzerdefinierte Funktionen oder statische Methoden gesteuert werden, die immer beim Lesen entscheiden, ob das Element noch gültig ist. Zum Beispiel können wir das Element verfallen lassen, wenn sich die PHP-Version ändert. Wir erstellen eine Funktion, die die aktuelle Version mit dem Parameter vergleicht, und beim Speichern fügen wir ein Array in der Form [function name, ...arguments] zu den Abhängigkeiten:

function checkPhpVersion($ver): bool
{
	return $ver === PHP_VERSION_ID;
}

$dependencies[Cache::Callbacks] = [
	['checkPhpVersion', PHP_VERSION_ID] // ablaufen, wenn checkPhpVersion(...) === false
];

Natürlich können alle Kriterien kombiniert werden. Der Cache verfällt dann, wenn mindestens ein Kriterium nicht erfüllt ist.

$dependencies[Cache::Expire] = '20 minutes';
$dependencies[Cache::Files] = '/path/to/data.yaml';

Invalidierung mit Tags

Tags sind ein sehr nützliches Werkzeug zur Invalidierung. Wir können jedem im Cache gespeicherten Element eine Liste von Tags zuweisen, bei denen es sich um beliebige Zeichenfolgen handelt. Nehmen wir zum Beispiel an, wir haben eine HTML-Seite mit einem Artikel und Kommentaren, die wir zwischenspeichern wollen. Wir geben also beim Speichern im Cache Tags an:

$dependencies[Cache::Tags] = ["article/$articleId", "comments/$articleId"];

Wechseln wir nun zur Verwaltung. Hier haben wir ein Formular zur Bearbeitung von Artikeln. Zusammen mit dem Speichern des Artikels in einer Datenbank rufen wir den Befehl clean() auf, der zwischengespeicherte Artikel nach Tags löscht:

$cache->clean([
	$cache::Tags => ["article/$articleId"],
]);

An der Stelle, an der wir einen neuen Kommentar hinzufügen (oder einen Kommentar bearbeiten), vergessen wir nicht, das entsprechende Tag zu deaktivieren:

$cache->clean([
	$cache::Tags => ["comments/$articleId"],
]);

Was haben wir erreicht? Dass unser HTML-Cache ungültig gemacht (gelöscht) wird, wenn sich der Artikel oder die Kommentare ändern. Wenn Sie einen Artikel mit ID = 10 bearbeiten, wird der Tag article/10 zwangsweise ungültig gemacht und die HTML-Seite, die den Tag enthält, aus dem Cache gelöscht. Dasselbe geschieht, wenn Sie einen neuen Kommentar unter dem betreffenden Artikel einfügen.

Tags erfordern Journal.

Invalidierung nach Priorität

Wir können die Priorität für einzelne Elemente im Cache festlegen, und es wird möglich sein, sie kontrolliert zu löschen, wenn der Cache z. B. eine bestimmte Größe überschreitet:

$dependencies[Cache::Priority] = 50;

Löschen Sie alle Objekte mit einer Priorität gleich oder kleiner als 100:

$cache->clean([
	$cache::Priority => 100,
]);

Prioritäten erfordern das sogenannte Journal.

Cache löschen

Der Parameter Cache::All löscht alles:

$cache->clean([
	$cache::All => true,
]);

Bulk Reading

Zum massenhaften Lesen und Schreiben in den Cache wird die Methode bulkLoad() verwendet, bei der wir ein Array von Schlüsseln übergeben und ein Array von Werten erhalten:

$values = $cache->bulkLoad($keys);

Die Methode bulkLoad() funktioniert ähnlich wie load() mit dem zweiten Callback-Parameter, an den der Schlüssel des erzeugten Elements übergeben wird:

$values = $cache->bulkLoad($keys, function ($key, &$dependencies) {
	$computedValue = /* ... */; // schwere Berechnungen
	return $computedValue;
});

Verwendung mit PSR-16

Um Nette Cache mit der PSR-16-Schnittstelle zu verwenden, können Sie die PsrCacheAdapter verwenden. Sie ermöglicht eine nahtlose Integration zwischen Nette Cache und jedem Code oder jeder Bibliothek, die einen PSR-16-kompatiblen Cache erwartet.

$psrCache = new Nette\Bridges\Psr\PsrCacheAdapter($storage);

Jetzt können Sie $psrCache als PSR-16-Cache verwenden:

$psrCache->set('key', 'value', 3600); // speichert den Wert für 1 Stunde
$value = $psrCache->get('key', 'default');

Der Adapter unterstützt alle in PSR-16 definierten Methoden, einschließlich getMultiple(), setMultiple() und deleteMultiple().

Zwischenspeicherung der Ausgabe

Die Ausgabe kann auf sehr elegante Weise erfasst und zwischengespeichert werden:

if ($capture = $cache->capture($key)) {

	echo ... // Drucken einiger Daten

	$capture->end(); // Speichern der Ausgabe im Cache
}

Falls die Ausgabe bereits im Cache vorhanden ist, druckt die Methode capture() sie aus und gibt null zurück, so dass die Bedingung nicht ausgeführt wird. Andernfalls beginnt sie, die Ausgabe zu puffern und gibt das Objekt $capture zurück, mit dem wir die Daten schließlich im Cache speichern.

In Version 3.0 hieß die Methode $cache->start().

Caching in Latte

Das Zwischenspeichern in Latte-Vorlagen ist sehr einfach, indem man einen Teil der Vorlage mit Tags umgibt {cache}...{/cache}. Der Cache wird automatisch ungültig gemacht, wenn sich die Quellvorlage ändert (einschließlich aller in den {cache} -Tags enthaltenen Vorlagen). Tags {cache} können verschachtelt werden, und wenn ein verschachtelter Block ungültig wird (z. B. durch ein Tag), wird auch der übergeordnete Block ungültig.

Im Tag können die Schlüssel angegeben werden, an die der Cache gebunden werden soll (hier die Variable $id), und die Ablauf- und Ungültigkeits-Tags gesetzt werden

{cache $id, expire: '20 minutes', tags: [tag1, tag2]}
	...
{/cache}

Alle Parameter sind optional, d.h. Sie müssen weder Verfallsdatum noch Tags oder Schlüssel angeben.

Die Verwendung des Cache kann auch durch if bedingt werden – der Inhalt wird dann nur dann zwischengespeichert, wenn die Bedingung erfüllt ist:

{cache $id, if: !$form->isSubmitted()}
	{$form}
{/cache}

Speicherplätze

Ein Speicher ist ein Objekt, das darstellt, wo Daten physisch gespeichert werden. Wir können eine Datenbank, einen Memcached-Server oder den am häufigsten verwendeten Speicher, nämlich Dateien auf der Festplatte, verwenden.

Speicher Beschreibung
FileStorage Standardspeicher mit Speicherung in Dateien auf der Festplatte
MemcachedStorage verwendet den Memcached Server
MemoryStorage Daten werden temporär im Speicher abgelegt
SQLiteStorage Daten werden in einer SQLite-Datenbank gespeichert
DevNullStorage Daten werden nicht gespeichert – für Testzwecke

Sie erhalten das Speicherobjekt, indem Sie es mittels Dependency Injection mit dem Typ Nette\Caching\Storage übergeben. Standardmäßig bietet Nette ein FileStorage-Objekt, das Daten in einem Unterordner cache im Verzeichnis für temporäre Dateien speichert.

Sie können den Speicherort in der Konfiguration ändern:

services:
	cache.storage: Nette\Caching\Storages\DevNullStorage

FileStorage

Schreibt den Cache in Dateien auf der Festplatte. Der Speicher Nette\Caching\Storages\FileStorage ist sehr leistungsoptimiert und gewährleistet vor allem eine vollständige Atomisierung der Operationen. Was bedeutet das? Dass es bei der Verwendung des Caches nicht passieren kann, dass wir eine Datei lesen, die von einem anderen Thread noch nicht vollständig geschrieben wurde, oder dass jemand sie “unter der Hand” löscht. Die Nutzung des Caches ist also völlig sicher.

Dieser Speicher hat auch eine wichtige eingebaute Funktion, die einen extremen Anstieg der CPU-Auslastung verhindert, wenn der Cache geleert oder kalt (d.h. nicht erstellt) ist. Dies ist die “Cache-Stampede”-P:https://en.wikipedia.org/…che_stampede rävention. Es kommt vor, dass zu einem Zeitpunkt mehrere gleichzeitige Anfragen das Gleiche aus dem Cache wollen (z. B. das Ergebnis einer teuren SQL-Abfrage), und da es nicht zwischengespeichert ist, beginnen alle Prozesse mit der Ausführung derselben SQL-Abfrage. Die Prozessorlast vervielfacht sich und es kann sogar passieren, dass kein Thread innerhalb des Zeitlimits antworten kann, der Cache nicht erstellt wird und die Anwendung abstürzt. Glücklicherweise funktioniert der Cache in Nette so, dass bei mehreren gleichzeitigen Anfragen für ein Element dieses nur vom ersten Thread erzeugt wird, die anderen warten und verwenden dann das erzeugte Ergebnis.

Beispiel für die Erstellung eines FileStorage:

// der Speicher wird das Verzeichnis '/pfad/zu/temp' auf der Festplatte sein
$storage = new Nette\Caching\Storages\FileStorage('/path/to/temp');

MemcachedStorage

Der Server Memcached ist ein hochleistungsfähiges verteiltes Speichersystem, dessen Adapter Nette\Caching\Storages\MemcachedStorage ist. Geben Sie in der Konfiguration die IP-Adresse und den Port an, falls dieser vom Standard 11211 abweicht.

Erfordert die PHP-Erweiterung memcached.

services:
	cache.storage: Nette\Caching\Storages\MemcachedStorage('10.0.0.5')

SpeicherStorage

Nette\Caching\Storages\MemoryStorage ist ein Speicher, der Daten in einem PHP-Array speichert und daher verloren geht, wenn die Anfrage beendet wird.

SQLiteStorage

Die SQLite-Datenbank und der SQLite-Adapter Nette\Caching\Storages\SQLiteStorage bieten eine Möglichkeit, in einer einzigen Datei auf der Festplatte zu speichern. In der Konfiguration wird der Pfad zu dieser Datei angegeben.

Erfordert die PHP-Erweiterungen pdo und pdo_sqlite.

services:
	cache.storage: Nette\Caching\Storages\SQLiteStorage('%tempDir%/cache.db')

DevNullStorage

Eine spezielle Implementierung des Speichers ist Nette\Caching\Storages\DevNullStorage, der eigentlich gar keine Daten speichert. Sie eignet sich daher zum Testen, wenn wir die Wirkung des Cache ausschalten wollen.

Verwendung von Cache im Code

Bei der Verwendung von Caching im Code gibt es zwei Möglichkeiten, wie man es machen kann. Die erste ist, dass Sie das Speicherobjekt erhalten, indem Sie es mittels Dependency Injection übergeben und dann ein Objekt Cache erstellen:

use Nette;

class ClassOne
{
	private Nette\Caching\Cache $cache;

	public function __construct(Nette\Caching\Storage $storage)
	{
		$this->cache = new Nette\Caching\Cache($storage, 'my-namespace');
	}
}

Die zweite Möglichkeit ist, dass Sie das Speicherobjekt Cache erhalten:

class ClassTwo
{
	public function __construct(
		private Nette\Caching\Cache $cache,
	) {
	}
}

Das Objekt Cache wird dann direkt in der Konfiguration wie folgt erstellt:

services:
	- ClassTwo( Nette\Caching\Cache(namespace: 'my-namespace') )

Journal

Nette speichert Tags und Prioritäten in einem sogenannten Journal. Standardmäßig werden dafür SQLite und die Datei journal.s3db verwendet, und PHP-Erweiterungen pdo und pdo_sqlite sind erforderlich.

Sie können das Journal in der Konfiguration ändern:

services:
	cache.journal: MyJournal

DI-Dienste

Diese Dienste werden dem DI-Container hinzugefügt:

Name Typ Beschreibung
cache.journal Nette\Caching\Storages\Journal journal
cache.storage Nette\Caching\Storage repository

Cache ausschalten

Eine Möglichkeit, die Zwischenspeicherung in der Anwendung zu deaktivieren, besteht darin, den Speicher auf DevNullStorage zu setzen:

services:
	cache.storage: Nette\Caching\Storages\DevNullStorage

Diese Einstellung wirkt sich nicht auf die Zwischenspeicherung von Vorlagen in Latte oder dem DI-Container aus, da diese Bibliotheken nicht die Dienste von nette/caching nutzen und ihren Cache unabhängig verwalten. Außerdem muss ihr Cache im Entwicklungsmodus nicht abgeschaltet werden.

Version: 3.x