Daten auf Vorrat

Verwendung der Cache-Komponente des Zend Frameworks
Kommentare

Durch den Einsatz von Caching lässt sich die Performance einer Applikation in der Regel deutlich verbessern. Die Zugriffszeit auf Informationen eines zu cachenden Speichers wird oft signifikant verringert. Insbesondere wenn es sich um komplizierte Berechnungen handelt, die – Cache sei Dank – nur einmalig durchgeführt werden müssen. In diesem Artikel werden Sie lernen, wie Sie die Zend_Cache-Komponente zu Ihrem Vorteil einsetzen können.

Das Caching von Daten ist ein probates Mittel, um die allgemeine Performance einer Applikation zu verbessern. In der Regel stehen beim Caching zwei Aspekte im Vordergrund: Die Verringerung der Zugriffszeit von Anfragen sowie die Verringerung der Anzahl von Zugriffen auf den zu cachenden Speicher. Alle Listings zum Artikel finden Sie übrigens auch unter www.phpmagazin.de.

Caching für Dummies

Lassen Sie uns die grundlegenden Konzepte anhand eines einfachen Beispiels betrachten. Stellen Sie sich einen Bibliothekar vor, der in einer Bibliothek für die Bücherausleihe zuständig ist. Sie können Bücher nur über den Bibliothekar beziehen und nicht selbstständig aus den Regalen nehmen. Der Ablauf zur Ausleihe eines Buchs ist demnach folgender: Der Bibliothekar wartet an seinem Schreibtisch. Fragt ein Klient nach einem Buch, so holt der Bibliothekar es aus dem Aufbewahrungsraum und händigt es dem Anfragenden aus. Schauen wir uns den Ablauf (ohne Caching) an einem konkreten Beispiel an. Kunde A fragt den Bibliothekar nach dem Buch „Faust I“. Der Bibliothekar geht in den Bücherraum, holt das Buch und händigt es dem Kunden aus. Natürlich dauert das eine Weile, denn der Bibliothekar muss zu den Regalen laufen, in dem großen Raum das Buch finden und es dann an den Schreibtisch bringen. Später gibt Kunde A das Buch zurück und es kommt wieder in den Bücherraum. Nur kurz danach kommt Kunde B und fragt ebenfalls nach dem Buch „Faust I“. Der nun folgende Ablauf ist der bereits beschriebene und wiederum nimmt der Vorgang einiges an Zeit in Anspruch. Sie erkennen bereits, dass der Bibliothekar den Kunden B schneller hätte bedienen können, wenn er das Buch noch in Reichweite seines Schreibtisches gehabt hätte.

Diese Erkenntnis bringt uns zu Teil zwei des Beispiels. Diesmal werden wir von einer Caching-Methode Gebrauch machen und rüsten den Bibliothekar mit einem Rucksack aus, der genügend Kapazität für zehn Bücher hat. Der Tag beginnt und der Bibliothekar startet mit einem leeren Rucksack. Wieder erscheint Kunde A und fragt nach dem „Faust I“. Der Bibliothekar holt das Buch aus dem Bücherraum und händigt es dem Kunden aus. Nach dem Mittag bringt der Kunde A das Buch zurück. Anstatt es wieder in den Bücherraum zu bringen, legt es der Bibliothekar diesmal jedoch in seinen Rucksack. Kurz darauf erscheint Kunde B und fragt ebenfalls nach dem Titel „Faust I“. Der Bibliothekar schaut nun in seinen Rucksack und überprüft, ob sich das entsprechende Buch dort findet. Tatsächlich liegt das Buch im Rucksack und anstatt in den Bücherraum gehen zu müssen, kann der Bibliothekar es einfach herausnehmen und dem Kunden aushändigen. Damit hat er den Kunden in Rekordzeit bedient, denn er musste ja nicht extra vom Schreibtisch in den Bücherraum und wieder zurück. Er musste nur überprüfen, ob das Buch sich im Rucksack befindet und es dem Kunden aushändigen.

Sie werden bemerken, dass der Bibliothekar mit dem Rucksack-Cache unter bestimmten Umständen ineffizienter ist, als ohne. Nämlich genau in dem Fall, wo das Buch nicht im Rucksack zu finden ist und er nach der Suche trotzdem noch in den Bücherraum gehen muss. Um den negativen Einfluss dieser Eventualität zu minimieren, gilt es einen hinreichend performanten Cache zu verwenden und die Zeit für die Suche auf ein Minimum zu reduzieren. Damit werden die Kosten im Vergleich zum Nutzen vernachlässigbar klein. Das Nachschauen im Rucksack fällt also bei einer sehr effizienten „Rucksack-Suchmethode“ gegenüber dem zeitaufwändigen Holen eines Buchs überhaupt nicht mehr ins Gewicht.

Dieses kleine Beispiel verdeutlicht sehr gut die grundlegenden Konzepte, die dem Caching von Daten zugrunde gelegt werden:

  • Es wird ein schnellerer, aber im Verhältnis zum zu cachenden Speicher kleinerer Speichertyp verwendet, um häufig benötigte Daten vorzuhalten.
  • Bei Benutzung eines Cache wird anhand eines eindeutigen Identifiers geprüft, ob ein benötigtes Datum bereits im Cache vorhanden ist. Ist die Information vorhanden, so spricht man von einem Cache Hit. Ist der gesuchte Eintrag noch nicht im Cache vorhanden, muss diese Information erst beschafft/berechnet werden und wird danach im Cache abgelegt. Diesen Fall bezeichnet man als Cache Miss.
  • In der Regel ist die Größe des Cache-Speichers begrenzt und wie erwähnt kleiner als der zu cachende Speicher.
  • Ein Cache kann mehrere Schichten (layer) haben. Im Beispiel wurde ein one-level Cache beschrieben. Der große Bücherraum, der langsam zu erreichen ist und der kleine, aber schnell zugängliche Rucksack des Bibliothekars. Hätte hinter dem Schreibtisch noch ein Regal mit 100 Büchern gestanden, das mittelschnell zu erreichen gewesen wäre, so hätte ein two-level Cache vorgelegen.

Wie in dem Beispiel erwähnt, ist es wichtig, eine ordentliche Implementierung eines Caches zu haben. Zum einen ist ein ausgereiftes API von Vorteil und zum anderen die Performanz ein wichtiger Faktor. Denn je geringer der Aufwand für die Suche nach existierenden Einträgen im Cache, desto vernachlässigbarer wird dieser Overhead. Hier kommt die Cache-Komponente des Zend Frameworks ins Spiel, und wir wollen uns in der Folge anschauen, wie diese Sie beim Caching unterstützen kann. Um Zend_Cache zu verwenden, müssen Sie die Zend-Framework-Quellen von der Webseite herunterladen. In den nachfolgenden Beispielen wird davon ausgegangen, dass das in dem heruntergeladenen Archiv enthaltene Verzeichnis library im Include-Pfad Ihrer PHP-Installation angegeben ist.

Zend_Cache-Grundlagen

Der erste Schritt bei der Arbeit mit Zend_Cache ist immer die Erzeugung einer Cache-Instanz (Listing 1). Zur Erzeugung müssen Sie angeben, welches Frontend und welches Backend verwendet werden soll. Im Beispiel wird das Core-Frontend und das File-Backend verwendet – aber was bedeutet das?

Die Zend_Cache-Komponente arbeitet modular und trennt zwischen den Aspekten „Was wird gespeichert?“ und „Wohin wird etwas gespeichert?“. Der verwendete Frontend-Adapter bestimmt dabei, welche Daten gespeichert werden oder anders formuliert, woher die zu speichernden Daten kommen. Der Backend-Adapter hingegen beschäftigt sich nur damit, wohin zu speichernde Daten geschrieben werden.

Das im Beispiel verwendete Frontend Core ist die generische Basis aller im Zend Framework vorhandenen Cache-Frontends. Es implementiert die Basisfunktionalität, die für das Caching von Daten benötigt wird. Dazu zählt Folgendes:

  • IDs: jedes im Cache abgelegte Datum wird einem eindeutigen Schlüssel zugeordnet. Wenn Sie Daten in den Cache legen, müssen Sie einen eindeutigen Identifier angeben. Diesen können Sie später verwenden, um festzustellen, ob entsprechende Daten im Cache vorliegen.
  • Cache lifetime Handling: die Daten in einem Cache werden mit einer „Haltbarkeit“ versehen. Solange diese nicht abgelaufen ist, können die Daten aus dem Cache verwendet werden. Ist die Haltbarkeit überschritten, müssen die angeforderten Daten neu berechnet werden, da der Cache diese als verfallen bzw. nicht existent ansieht.

Ein Frontend gestattet Ihnen also das Ablegen von zu cachenden Daten unter einem Schlüssel mit einer festgelegten Lebensdauer. Sie können außerdem mithilfe des Frontends feststellen, ob eine angefragte Information im Cache vorhanden ist. Im Fall eines Cache Hits können Sie somit Teile Ihres Quellcodes überspringen, die bei einem Cache Miss abgearbeitet werden müssten. Um das Ablegen von Daten im Cache kümmert das Frontend sich jedoch nicht selbst, sondern delegiert diese Aufgabe an das entsprechend konfigurierte Backend. Im Beispiel von Listing 1 wird das File Backend verwendet, das – nicht unerwartet – zu speichernde Informationen im Dateisystem ablegt. Zend_Cache verfügt über eine Vielzahl von Frontends und Backends, die Sie im Lauf des Artikels noch näher kennenlernen werden.

Nachdem Sie, wie in Listing 1 gesehen, eine Cache-Instanz über die Zend_Cache::factory()-Methode erzeugt haben, können Sie diese innerhalb Ihres Quellcodes verwenden, um durch das Caching eine Verringerung der Ausführungszeit des Skripts zu erreichen. Listing 2 zeigt den Einsatz der Methoden Zend_Cache_Core::save() und Zend_Cache_Core::load() in Verbindung mit dem Einsatz eines eindeutigen Identifiers für einen Cache-Eintrag. Im Fall eines Cache Miss wird die Schleife durchlaufen, was ca. 10 Sekunden in Anspruch nimmt. Die beim Schleifendurchlauf berechneten Daten werden im Cache unter dem entsprechenden Schlüssel abgespeichert (lifetime = 7 200 Sekunden). Dadurch ist bei erneuter Ausführung des Skripts ein Cache Hit zu verzeichnen und die Abarbeitung des Skripts geht um den Faktor 10 schneller.

Weitere Features

Bisher haben wir nur sehr grundlegende Funktionalität gesehen, die man mindestens für das Caching von Informationen benötigt. Weiterhin ist es natürlich möglich, im Cache abgelegte Informationen wieder zu entfernen. Zend_Cache erlaubt es außerdem, im Cache abgelegte Informationen mit Tags zu versehen. Dadurch hat man die Möglichkeit, die abgelegten Daten zu gruppieren/kategorisieren. Dies ist dann nützlich, wenn man nicht nur einzelne Einträge, sondern ganze Gruppen von Daten aus dem Cache löschen möchte. Listing 3 zeigt diverse Möglichkeiten, wie Informationen aus dem Cache entfernt werden können. Die Methode Zend_Cache_Core::remove() erwartet als Parameter einen Identifier und entfernt diesen spezifischen Wert aus dem Cache. Um mehrere Einträge auf einmal zu löschen, steht die Methode Zend_Cache_Core::clean() zur Verfügung. Diese können Sie über Konstanten steuern, um entweder alle, veraltete oder nur Einträge mit bestimmtem Tag zu entfernen. Eine Übersicht hierzu bietet Tabelle 1.

Tabelle 1: Konstanten zur Steuerung von Zend_Cache_Core::clean()

Die Ausgabe von Listing 3 sieht demnach wie folgt aus:

bool(false)
string(21) "Ein paar andere Daten"
bool(false)

Damit kennen Sie die grundlegenden Features von Zend_Cache. Im Folgenden werden wir uns die verfügbaren Frontends und Backends näher anschauen.

Verfügbare Frontends

In den vorhergehenden Beispielen haben Sie bereits das Core-Frontend kennengelernt, das allgemeine Funktionalitäten zum Caching von Daten bereitstellt und die Basis für alle weiteren Cache-Frontends ist. Ein weiteres interessantes Frontend ist Zend_Cache_Frontend_Output oder kurz Output. Damit ist es möglich, solche Daten zu cachen, die auf den Standardausgabe-Stream geschrieben, also mit print oder echo direkt ausgegeben werden. Hierbei kann ein Start- und ein Endpunkt angegeben werden. Ein einfaches Beispiel sehen Sie in Listing 4. Im if-Statement wird die start()-Methode aufgerufen. Im Fall eines Cache Hits liefert sie true zurück. Der Anweisungsblock innerhalb der Bedingung wird also nur ausgeführt, wenn keine Informationen im Cache vorhanden oder diese veraltet sind. Die Ausgabe des Skripts sieht wie folgt aus:

Hallo Welt! Gecached (1215861114) Nicht gecached (1215861118).

Der gecachte Wert wird alle 30 Sekunden aktualisiert, der nicht gecachte Wert dagegen immer neu berechnet und ausgegeben. Das Output-Frontend erlaubt es also, auf einfache Weise bestimmte Blöcke innerhalb einer Seite zu cachen. Auch eine nachträgliche Integration in bestehende Anwendungen sollte kein allzu großes Problem darstellen.

Zwei weitere interessante Frontends sind Function und Class. Diese erlauben es, Funktions- respektive Methodenaufrufe zu cachen. Das macht besonders dann Sinn, wenn ein solcher Aufruf besonders teuer, also die Ausführungszeit sehr lang ist. Listing 5 zeigt das Function Frontend im Einsatz. Die Cache-Instanz wird zuerst mit den entsprechenden Informationen erzeugt. Danach kann man auf diesem Objekt die call()-Methode aufrufen und den Funktionsnamen und eventuell notwendige Funktionsparameter in Form eines Arrays übergeben. Weitere optionale Parameter von call() dienen zum Tagging oder dem Festlegen einer speziellen Cache-Lifetime. Die Cache-Instanz führt die entsprechenden Funktionsaufrufe auf und kümmert sich automatisch um die Cache-Hit/-Miss-Prüfung. Das heißt Sie müssen, abgesehen von dem call()-Aufruf, nichts weiter machen. Es ist keine bedingte Ausführung innerhalb Ihrer Skripte notwendig. Ähnlich funktioniert das Caching mit dem Class-Frontend. Listing 6 zeigt den Aufruf der Methode expensiveMethod() auf einem Objekt der Klasse Foo. Hierbei ist es wichtig zu beachten, dass man in den Frontend-Optionen den Parameter cached_entity angibt. Handelt es sich dabei um einen String, so geht das Caching-Frontend davon aus, dass statische Methodenaufrufe gecacht werden sollen. Wird dagegen ein Objekt angegeben, ruft das Frontend die entsprechenden Instanzmethoden auf.

Der zweite Unterschied zum Function-Frontend ist, dass es keine call()-Methode gibt. Stattdessen arbeitet das Class-Frontend mit dem Einsatz von Object Overloading. Sie können direkt auf der Cache-Instanz die gewünschten Methoden aufrufen. Dabei erfolgen sowohl Ausgaben auf der Standardausgabe als auch die Werterückgabe genau so, als hätten Sie die Methode direkt auf der echten Objektinstanz und nicht per Cache-Objekt aufgerufen. Sie haben natürlich den Vorteil, dass sowohl Ausgaben als auch Rückgabewerte im Cache gehalten werden. Wiederum ist kein bedingter Anweisungsblock notwendig – das Caching funktioniert vollautomatisch.

Ein Einsatzszenario, bei dem der Einsatz von Caching ebenfalls oft Sinn macht, ist das Lesen von Informationen aus Dateien. Insbesondere Konfigurationsdateien einer Anwendung ändern sich nur relativ selten, die Informationen werden aber bei jedem Request neu ausgelesen. In diesem Fall ist es viel effizienter, wenn man die Dateien nur einmal einliest und die ermittelten Informationen danach im Cache vorhält. Erst wenn die Konfigurationsdatei geändert wird, muss das Einlesen erneut erfolgen. Genau für diesen Einsatzzweck bietet Zend_Cache das File-Frontend. Der Inhalt der (Konfigurations-)Datei ist in Listing 7 zu sehen, und Listing 8 zeigt den Einsatz des Frontends. Zur Konfiguration der Cache-Instanz müssen Sie lediglich den Parameter master_file angeben. Dabei handelt es sich um den Namen der Datei, deren Informationen für uns relevant sind. Ein Cache Miss liegt genau dann vor, wenn keine Daten zu einer gegebenen Cache-Id vorhanden sind oder diese eingelesen wurden, bevor die Datei das letzte Mal aktualisiert wurde, der Zeitstempel der gecachten Daten also vor der mtime (modification time) der Datei liegt. In jedem anderen Fall handelt es sich um einen Cache Hit, und die Informationen können aus dem Cache bezogen werden. Das File-Frontend funktioniert in der Nutzung damit genauso wie das Core-Frontend. Der einzige Unterschied liegt darin, dass keine Information zur Lifetime angegeben werden muss, sondern die Gültigkeit der Daten im Cache an das Datum der letzten Modifikation einer Master-Datei geknüpft ist.

Sie haben nun die wichtigsten Frontends, die Zend_Cache zu bieten hat, kennengelernt. Tabelle 2 gibt einen Überblick über alle aktuell verfügbaren Frontends. Nähere Informationen und Codebeispiele finden Sie in der Dokumentation zu Zend_Cache.

Tabelle 2: Für Zend_Cache verfügbare Frontends
Verfügbare Backends

In den bisher gesehenen Listings wurde immer das File-Backend verwendet. Das heißt, die gecachten Daten wurden lokal im Dateisystem abgelegt. Zend_Cache bietet allerdings noch eine handvoll weiterer Backends, die zur Speicherung von Cache-Einträgen verwendet werden können. Tabelle 3 zeigt eine Auflistung der aktuell unterstützten Backends.

Tabelle 3: Für Zend_Cache verfügbare Backends

Die Namen der Backends sind eigentlich selbsterklärend. Das SQLite-Backend erlaubt die Speicherung von Cache-Einträgen in einer SQLite-Datenbank. Weitere Backends erlauben die Verwendung von Memcached, APC und der Zend Platform zur Speicherung von zu cachenden Daten. Memcached und APC bieten dabei keinen Support für Tags. Beim Einsatz des ZendPlatform-Backends werden Tags zwar unterstützt, allerdings kann der Cleaning-Mode CLEANING_MODE_NOT_MATCHING_TAG nicht verwendet werden. Für die meisten Ansprüche werden die Backends File und SQLite wohl ausreichen. Sollten Sie jedoch erweiterte Anforderungen an die Geschwindigkeit des Caches haben oder einen Cache benötigen, der auch in verteilten Systemen ohne allzu großen Aufwand eingesetzt werden kann, so werden die etwas exotischeren Backends sicher interessant für Sie sein.

Fazit

Die Caching-Komponente des Zend Frameworks ist gut durchdacht, sehr stabil und einfach zu handhaben. Das modulare Konzept von Frontends und Backends überzeugt, und auch die Dokumentation ist sehr gut. Die zahlreichen Frontends bieten so ziemlich alle Features, die man in der täglichen Arbeit benötigt. Falls man dennoch an die Grenzen der verfügbaren Adapter stoßen sollte, so hindert einen nichts an der Erstellung eigener Komponenten. Fazit des Autors: Sehr gut gelungen!

src=“http://entwickler.com/develop/zonen/magazine/onlineartikel/pspic/picture_file/20/carsten_lu48edf5c807604.png“ hspace=“5″ vspace=“5″ alt=““>

Carsten Lucke arbeitet als Senior-Softwareingenieur bei der sd&m AG in München. Er ist aktiver PEAR-Entwickler, Autor von Fachartikeln und -büchern sowie nebenberuflich als Dozent für Internet- und Webentwicklung tätig. Sie erreichen ihn über diese Mailadresse oder über seine Webseite (www.lucke.info).

Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -