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.