Vorwort
Webseiten mit PHP zu erstellen ist schon eine tolle Sache. Noch besser wird es, wenn man seine ganzen Daten und Informationen aus einer Datenbank ziehen kann. Ein paar Funktionen hier, eine Datenbankverbindung da, dazu noch einige Queries und schon wird die dynamisch generierte Seite zum Besucher geschickt. Möchten jetzt 500 Leute die selbe Seite gleichzeitig sehen ( z.B. eine Produktseite in einem Onlineshop ) werden die eben erwähnten Funktionen, Datenbankverbindung und Queries eben 500 mal ausgeführt - nur um eine identische Information 500 anzuzeigen!
Was auf den ersten Blick so dynamisch wirkt, entpuppt sich schnell als eine statische Information die bei jedem Abruf wieder und wieder Ressourcen belegt und somit den Server belastet. Abhilfe kann hier das sogenannte Caching schaffen.
In diesem Tutorial möchte ich eine recht simple Variante vorstellen, damit der Leser einen Einblick bekommt wie Caching funktioniert.
Was ist Caching und wie funktioniert es?
Als Caching bezeichnet man das zwischenspeichern von Informationen bzw Daten, damit diese bei einem erneuten Aufruf sofort zur Verfügung stehen. Anders ausgedrückt bedeutet das für uns und dieses Tutorial: Wir nehmen eine dynamisch generierte Seite und wandeln sie ( temporär ) in eine statische HTML-Seite um.
Die Vorteile einer statischen Seite liegen auf der Hand:
- Schnellerer Seitenaufbau, weil weniger Daten durch den Parser gejagd werden müssen. Statt die angeforderten Daten dynamisch zu generieren wird lediglich eine statische Seite eingebunden.
- Der Server wird -teilweise massiv- entlastet, weil weniger Ressourcen belegt werden. Es müssen keine Variablen, Funktionen und Klassen im Arbeitsspeicher gehalten werden, die Kommunikation mit dem Datenbankserver entfällt. Somit können Aufrufe von mehr Besucher schneller bedient werden.
- Teilweise lassen sich auch Ausfälle der Datenbank abfangen, da man Daten aus dem Cache anzeigen lässt statt sie aus der DB zu laden.
Das liest sich ja recht gut, aber leider ist nicht alles Gold was glänzt. Zu den negativen Punkten komme ich im Fazit.
Voraussetzungen
Da dieses Tutorial ausschließlich das Caching zum Thema hat sollte der Leser folgendes mitbringen
- einfache PHP Kenntnisse
- Erfahrung im Umgang mit Datenbanken ( auslesen, updaten, einfügen, etc. )
- Grundlagenwissen im Umgang mit Dateien ( schreiben, lesen, löschen )
Da die Mehrheit sicherlich noch PHP 4.x.x einsetzt, werde ich mich in diesem Tutorial auch daran halten. Für PHP5 User werde ich aber auch kurz den bequemeren Weg zeigen. Die Unterschiede sind nicht wirklich gravierend, sondern ersparen einem mit PHP5 nur Schreibarbeit.
Los geht's... Caching im Einsatz
Schauen wir uns nun an wie das in der Praxis funktioniert. Die hier benutzen Datenbankzugriffe und Ausgaben sollten Basiswissen sein und werden hier nicht weiter erläutert. Als Anschauungsobjekt dient hier ein Gästebuch aus dem wir die ersten 3 Einträge auslesen möchten. Zugegeben, ein Gästebuch ist hier ein suboptimales Beispiel, aber es geht auch nur um das verdeutlichen der Caching-Technik.
Das Script ohne Caching
PHP-Code:
<?php
// Zur DB verbinden und Tabelle auswaehlen
$conid = @mysql_connect( "localhost", "root", "" ) or die( "Fehler: MySQL nicht erreichbar!" );
mysql_select_db( "gaestebuch", $conid );
// Daten aus der DB holen
$result = mysql_query( "SELECT `nachricht` FROM `gaestebuch` LIMIT 3" );
// Datensaetze ausgeben
while ($zeile = mysql_fetch_array( $result ) )
{
echo nl2br( htmlspecialchars( $zeile['nachricht'] ) ). "<hr>";
}
// Ressourcen freigeben und DB-Verbindung schliessen
mysql_free_result( $result );
mysql_close( $conid );
?>
Wie hier zu sehen ist, handelt es sich um ein ganz banales Script das sich zu einer Datenbank verbindet, 3 Einträge aus dem Gästebuch holt und diese dann in einer Schleife ausgibt. Danach werden artig die Ressourcen wieder freigegeben und alles ist bestens. Greifen jetzt aber unsere fiktive 500 Besucher gleichzeitig auf das Gästebuch zu, wird der oben stehen Code 500 Mal ausgeführt und belegt 500 Mal Ressourcen. Das geht doch bestimmt effektiver, oder?!
Das Script mit anlegen der Cache Datei
PHP-Code:
<?php
// Der interne Ausgabe-Puffer wird aktiviert
ob_start();
// Zur DB verbinden und Tabelle auswaehlen
$conid = @mysql_connect( "localhost", "root", "" ) or die( "Fehler: MySQL nicht erreichbar!" );
mysql_select_db( "gaestebuch", $conid );
// Daten aus der DB holen
$result = mysql_query( "SELECT `nachricht` FROM `gaestebuch` LIMIT 3" );
// Datensaetze ausgeben
while ($zeile = mysql_fetch_array( $result ) )
{
echo nl2br( htmlspecialchars( $zeile['nachricht'] ) ). "<hr>";
}
// Ressourcen freigeben und DB-Verbindung schliessen
mysql_free_result( $result );
mysql_close( $conid );
// Die Daten vom Puffer werden umladen
$output = ob_get_contents();
// Die Cache-Datei anlegen und die Daten hinein schreiben
$datei = fopen( "cache/nachrichten.htm", "w" );
fwrite( $datei, $output );
fclose( $datei );
// Puffer an den Browser ausgeben und die Pufferung deaktivieren
ob_end_flush();
?>
Im Vergleich zum vorherigen Listing wurden nur wenige Zeilen ergänzt, aber die haben es in sich!
Gehen wir kurz die neuen Zeilen durch um zu sehen was dahinter steckt.
Damit wird der PHP-interne Ausgabepuffer eingeschaltet. Alles was ab jetzt als Ausgabe erscheinen würde ( in unserem Script die echo-Befehle in der while()-Schleife ) landet statt auf dem Bildschirm als String im Puffer. Sofern man nicht an einer bestimmten Stelle im Script die Daten im Puffer verarbeitet, wird am Ende des Scripts automatisch ein
ob_end_flush() ausgelöst und die Daten werden an den Browser gesendet.
PHP-Code:
$output = ob_get_contents();
Mit
ob_get_contents() werden die Daten aus dem internen Puffer an die Variable
$output übergeben, wo sie auf eine weitere Verarbeitung warten.
PHP-Code:
$datei = fopen( "cache/gaestebuch.htm", "w" );
fwrite( $datei, $output );
fclose( $datei );
Dieser Code sollte bekannt sein, da hier lediglich eine HTML-Datei im Ordner
cache angelegt und der Inhalt von
$output darin abgelegt wird.
Hinweis: Der Ordner
cache muß bereits existieren und Schreibrechte besitzen!
PHP-Code:
ob_end_flush();
Diese Zeile sendet den Puffer an den Browser, damit dort nicht einfach eine weiße Seite angezeigt wird. Anschliessend wird die Pufferung beendet und der Arbeitsspeicher freigegeben.
Na toll, jetzt erzeugen/überschreiben wir also jedesmal eine Datei wenn jemand die Seite aufruft. Irgendwie ist das aber noch nicht so das Gelbe vom Ei!?
Was uns jetzt noch fehlt ist die Möglichkeit...:
- zu prüfen ob beim Seitenaufruf bereits eine entsprechende Cache-Datei existiert. Falls Nein, wird eine erzeugt. Falls Ja, dann...
- die Cache-Datei anzeigen lassen, statt erst eine dynamische Seite zu generieren.
- festzustellen ob die Cache-Datei überhaupt noch aktuell ist oder ob es nicht bereits eine aktuellere Version geben sollte.
Das fertige Script
PHP-Code:
<?php
// Pfad und Name der Cache-Datei und Gueltigkeitsdauer in Sekunden
$CacheDatei = "cache/gaestebuch.htm";
$CacheDauer = 3600;
// Pruefen ob eine entsprechende Cache-Datei bereits existiert
// und ob sie noch gueltig ist
if (file_exists( $CacheDatei ) &&
(time() - filemtime( $CacheDatei )) < $CacheDauer )
{
// Statt die Seite dynamisch generieren zu lassen wird die
// statische HTML-Datei eingebunden
include( $CacheDatei );
exit();
}
// Der interne Ausgabe-Puffer wird aktiviert
ob_start();
// Zur DB verbinden und Tabelle auswaehlen
$conid = @mysql_connect( "localhost", "root", "" ) or die( "Fehler: MySQL nicht erreichbar!" );
mysql_select_db( "gaestebuch", $conid );
// Daten aus der DB holen
$result = mysql_query( "SELECT `nachricht` FROM `gaestebuch` LIMIT 5" );
// Datensaetze ausgeben
while ($zeile = mysql_fetch_array( $result ) )
{
echo nl2br( htmlspecialchars( $zeile['nachricht'] ) ). "<hr>";
}
// Ressourcen freigeben und DB-Verbindung schliessen
mysql_free_result( $result );
mysql_close( $conid );
// Die Daten im Puffer umladen
$output = ob_get_contents();
// Die Cache-Datei anlegen und die Daten hinein schreiben
$datei = fopen( $CacheDatei, "w" );
fwrite( $datei, $output );
fclose( $datei );
// Puffer an den Browser ausgeben und die Pufferung deaktivieren
ob_end_flush();
?>
Hinzugekommen ist nur der obere Teil des Scripts. Zuerst setzen wir den Pfad und Namen zur Cache-Datei, dann noch die Zeit in Sekunden wie lange die Cache-Datei als gültig angesehen wird. Ist die Datei älter als erlaubt, wird beim nächsten Aufruf der Seite die Cache-Datei neu generiert.
PHP-Code:
if (file_exists( $CacheDatei ) &&
(time() - filemtime( $CacheDatei )) < $CacheDauer )
{
// Statt die Seite dynamisch generieren zu lassen wird die
// statische HTML-Datei eingebunden
include( $CacheDatei );
exit();
}
In der IF-Anweisung wird geschaut ob die Cache-Datei existiert und ob sie noch gültig ist. Falls nicht, wird der Block übersprungen und eine neue Datei erzeugt. Falls aber alle Kriterien zutreffen, wird einfach nur die statische HTML-Datei eingebunden und das Script per
exit(); verlassen. Wir sparen uns hier also die komplette DB-Kommunikation und das erneute anlegen der Cache-Datei. Somit wird nur einmal alle 3600 Sekunden ( 1 Stunde ) eine Seite generiert und an unsere 500 Besucher ausgegeben, statt 500 Mal die Datenbank zu kontaktieren um die Seite dynamisch erzeugen zu lassen!
Das Script für PHP 5 User
Mit PHP 5 kamen einige neue Funktionen hinzu, die einem Schreibarbeit abnehmen. Dieser PHP 4 Block...
PHP-Code:
// Die Daten im Puffer umladen
$output = ob_get_contents();
// Die Cache-Datei anlegen und die Daten hinein schreiben
$datei = fopen( $CacheDatei, "w" );
fwrite( $datei, $output );
fclose( $datei );
// Puffer an den Browser ausgeben und die Pufferung deaktivieren
ob_end_flush();
...läßt sich verkürzen indem man statt dessen...
PHP-Code:
file_put_contents( $CacheDatei, ob_get_flush() );
...diesen Einzeiler verwendet.
Das Fazit
Wie man sieht läßt sich mit sehr wenigen Zeilen Code eine Verbesserung einbauen die auf stärker frequentierten Seiten doch zu erheblichem Geschwindigkeitszuwachs beitragen kann. Die Entlastung für den Server ist enorm und auch die Besucher bekommen schneller die angeforderte Information auf den Bildschirm.
Bevor jetzt alle anfangen ihre Scripts umzuschreiben möchte ich noch kurz auf die eingangs erwähnten negativen Punkte dieses Verfahrens eingehen.
Für kleine(re) Seiten lohnt sich der Aufwand kaum, da der Performancegewinn minimal ist. Für größere Projekte kann es sich sehr wohl lohnen aber man sollte bedenken, daß der zusätzlich benötigte Platz auf dem Server enorm sein kann. Immerhin müssen die Daten doppelt vorhanden sein, einmal in der DB, einmal als HTML-Variante. Es empfiehlt sich auch ein eigener Server, da das Dateisystem bei Shared Hosting i.d.R. unter einer recht hohen Belastung steht und der Performancegewinn durch lange Festplatten-Zugriffszeiten wieder zunichte gemacht wird. Letztendlich findet man das aber nur in einem praktischen Testlauf heraus.
Das oben gezeigte Verfahren dient nur als Einblick. Effektiv wird es natürlich erst, wenn man ein Projekt von Grund auf mit Cache-System plant. Letztendlich bleibt es der Phantasie des User überlassen, auf welche Art so ein Cache-System eingebaut werden soll.
Ich hoffe dieses kleine Tutorial ist dem ein oder anderen von Nutzen und wünsche ein erfolgreiches Caching
Zum Abschluß gibt es noch die...
Link-Übersicht
Benutzte PHP-Funktionen