Partielles Caching mit PHP

Hmm, habs mal umgesetzt und am Ende sieht es deiner Variante irgendwie ähnlich, bis auf marginale Unterschiede... Gestern Abend erschien es mir als gute Idee. ;)

Inwiefern soll es denn MemoryCaching sein?
Datenbank? Mit der RamDisk über die gesprochen wurde?
 
Hallo,

okay, dann bin ich ja beruhigt ;-)
Dario hat vorgeschlagen bei der InMemoryVariante das ganze in einer Server Variablen abzulegen. Also statt die generierte php.static / html in einem File abzulegen soll das ganze in einer Servervariable stehen.

Die MemoryCachingStrategy ist zwar etwas schneller als die FileSystemCachingStrategy, braucht aber mehr Server Speicher und ist transient. D.h. die Inhalte des Caches gehen bei beenden des Servers / Absturz verloren.

Ungefähr so:
MemoryCachingStrategy.class.php
PHP:
<?php

class MemoryCachingStrategy implements ICachingStrategy{
        
        function __construct(){
            $_SERVER["MemoryCachingStrategy_Cache"] = array();
        }
    
        function cache($item,$timeToCacheInSeconds){
            if(!isset($_SERVER["MemoryCachingStrategy_Cache"][$item]) || ((time() - $_SERVER["MemoryCachingStrategy_Cache"][$item]["time"]) > $timeToCacheInSeconds)){
                $this->cacheItem($item);
            }    
        }
                
        function cacheItem($item){
            ob_start();
            include($item);
            $contents = ob_get_contents();
            ob_end_clean();
            $_SERVER["MemoryCachingStrategy_Cache"][$item]["contents"] = $contents;
            $_SERVER["MemoryCachingStrategy_Cache"][$item]["time"] = time();
        }
        
        function retrieve($item){
            if(isset($_SERVER["MemoryCachingStrategy_Cache"][$item])){
                return $_SERVER["MemoryCachingStrategy_Cache"][$item]["contents"];
            }
        }
        
        function cacheAndRetrieve($item,$timeToCacheInSeconds){
            //todo
        }
}
?>

Die config dazu:

caching.config.php
PHP:
<?php
require_once("FileSystemCachingStrategy.class.php");
require_once("MemoryCachingStrategy.class.php");

Cache::registerCachingStrategy("Memory",new MemoryCachingStrategy());
Cache::registerCachingStrategy("FileSystem",new FileSystemCachingStrategy());

Cache::register("page.php","FileSystem", 10); //cache that page for 10 seconds (for the sake of testing ;-)
Cache::register("minimal_page.php","Memory", 10); //cache that page for 10 seconds (for the sake of testing ;-)
?>
... scheint so zu funktionieren. Kommentare?

Ein Cache mit Datenbanksupport wäre auch denkbar.

Gruß Tom
 
Soll der Cache immer ueber einen bestimmten Zeitraum genutzt werden oder nur solang sich nichts aendert?
Ich hatte mal mit dem Caching von gefuellten Templates gespielt, jedoch ist das Problem eben dass man bei festgelegten Zeitraeumen eventuell nicht immer die aktuellsten Informationen anzeigt.
Ein Ausweg waere eventuell ueber die Datenbank zu erreichen. Wenn ich mich recht erinnere kann man den Aktualisierungszeitpunkt einer Tabelle abfragen (bin nicht ganz sicher, meine aber mal was dazu gelesen zu haben).
Dies waere eine simple Abfrage, und die ausgelesene Zeit koennte man mit dem Zeitpunkt der Aktualisierung des Caches vergleichen.
Ist nach dem letzten Caching aktualisiert worden wird der komplette Prozess durchgefuehrt und ein neues Cache-File erstellt (oder eben das alte aktualisiert), andernfalls wird eben nur das Cache-File gelesen und ausgegeben.
 
Soweit ich weiß gibt es leider keine Möglichkeit die Servervariablen so anzulegen - bis darauf Extensions von Drittanbietern mit rein zu holen...

Und das die Daten transient sind, wäre ja nicht so schlimm... Falls der Server crashed gehe ich davon aus, bis er wieder oben ist und alles läuft, ist auch unsere Cache Zeit von einer Minute abgelaufen ;)

Übrigens sollten wir irgendwie bedenken was passiert, wenn das Caching Skript mal nur halbe Dateien schreibt und aus irgendeinem Grunde dort hängen bleibt (Murphys Law und solche Dinge).
Checksums?
Durchschnittsberechnungen der Größe?
Eine neue Meldenfunktion für Cachefehler so dass die Benutzer darauf Einfluss nehmen können und ab X Meldungen wird zwanghaft aktualisiert?
Tolerieren?
 
Hallo,

also irgendwie klappt das mit $_Server nicht so wirklich, beim nächsten Aufruf sind die Daten wieder weg und das element wird wieder neu gecached... wie realisiert man denn bei PHP persistente also Dauerhaft vorhandene Variablen?

In dem Beispiel hätte ich beispielsweise gern, dass für alle Scriptaufrufe immer die selbe Instanz des cacheStore verwendet wird, ist das möglich? Shared Memory?

Btw. man hat auch eine Möglichkeit die Caches von Hand zu invalidieren und damit ein neugenerieren der Dateien auszulösen.

MemoryCachingStrategy.class.php
PHP:
<?php

class MemoryCachingStrategy implements ICachingStrategy{
        
        private $cacheStore;
    
        function __construct(){
            $this->cacheStore = array();
        }
    
        function cache($item,$timeToCacheInSeconds){
            if(!isset($this->cacheStore[$item]) || ((time() - $this->cacheStore[$item]["time"]) > $timeToCacheInSeconds)){
                $this->cacheItem($item);
            }    
        }
                
        function cacheItem($item){
            ob_start();
            include($item);
            $contents = ob_get_contents();
            ob_end_clean();
            $cachedItem = array();
            $cachedItem["contents"] = $contents;
            $cachedItem["time"] = time();
            $this->cacheStore[$item] = $cachedItem;
        }
        
        function retrieve($item){
            if(isset($this->cacheStore[$item])){
                return $this->cacheStore[$item]["contents"];
            }
        }
        
        function cacheAndRetrieve($item,$timeToCacheInSeconds){
            $this->cache($item,$timeToCacheInSeconds);
            return $this->retrieve($item);
        }
}
?>

Gruß Tom
 
also irgendwie klappt das mit $_Server nicht so wirklich, beim nächsten Aufruf sind die Daten wieder weg und das element wird wieder neu gecached... wie realisiert man denn bei PHP persistente also Dauerhaft vorhandene Variablen?

Leider eben gar nicht.
$_SERVER beinhaltet Umgebungsvariablen die von PHP vorgegeben werden.
Standardmäßig kann PHP dort nichts hinterlegen.

Normalerweise werden solche Dinge dann eben in Dateien bzw. einer Datenbank hinterlegt. Ist bei Objekten allerdings nicht möglich.

PHP wird eben ausgeführt. Und Ende.
Der nächste Aufruf ist wieder ein neuer.

Das liegt unter anderem eben auch daran, das jeder Apache Thread ein "eigenes" PHP hat. Eben das Modul. (Achtung, Angaben nach meinem besten Wissen, bin kein Experte für die internen Techniken)

Du könntest allerdings das Objekt serialisieren, zwischenspeichern (Datei/Datenbank) und dann wieder laden beim nächsten Aufruf.
Inwiefern das performant ist oder Vorteile bringt, ist natürlich die Frage... Besonders bei simultanten Zugriffen.
 
Hallo,

ich glaube eine MemoryCachingStrategy ist gar nicht notwendig :) Linux macht das für uns:
/dev/shm -> das ist ein memory-backed Up Filesystem :)
Da kann ich einfach die FilesystemCachingStrategy drauf legen und fertig :)

hier nochmal ein Update:
Im Verzeichnis /caching

Cache.class.php
PHP:
<?php
require_once("ICachingStrategy.class.php");

include("caching.config.php");

class Cache{
		private static $instance = null;
		private $resourceToCacheConfigurationMapping= array();
		private $cachingStrategyNameToStrategyMapping = array();
		private $defaultCachingStrategyName = "FileSystem";
		
		static function registerCachingStrategy($cachingStrategyName,$cachingStrategy){
			Cache::getInstance()->cachingStrategyNameToStrategyMapping[$cachingStrategyName]=$cachingStrategy;
		}
		
		function registerItem($item,$cachingStrategyName,$timeToCacheInSeconds){
   		    $currentCachingStrategy = $this->cachingStrategyNameToStrategyMapping[$cachingStrategyName];
			if(!isset($currentCachingStrategy)){
				$currentCachingStrategy=$this->cachingStrategyNameToStrategyMapping[$this->defaultCachingStrategyName];
			}
			$this->resourceToCacheConfigurationMapping[$item]= array($timeToCacheInSeconds,$currentCachingStrategy);
		}
		
		function getCached($item){
			if(isset($this->resourceToCacheConfigurationMapping[$item])){
				$timeToCacheWithStrategy = $this->resourceToCacheConfigurationMapping[$item];
				return $timeToCacheWithStrategy[1]->cacheAndRetrieve($item,$timeToCacheWithStrategy[0]);
			}
		}
		

		public static function getInstance(){
			if(self::$instance == null){
				self::$instance = new Cache();
			}
			return self::$instance;
		}
		
		
		/**
		 * Shortcut method
		 *
		 * @param unknown_type $item
		 */
		static function get($item){
			return Cache::getInstance()->getCached($item);
		}
		
		/**
		 * Shortcut method
		 *
		 * @param unknown_type $item
		 * @param ICachingStrategy $cachingStrategyName
		 * @param unknown_type $timeToCacheInSeconds
		 */
		static function register($item,$cachingStrategyName,$timeToCacheInSeconds){
			Cache::getInstance()->registerItem($item,$cachingStrategyName,$timeToCacheInSeconds);
		}
		
	}
	
	//echo Cache::getInstance()->get("page.php");
?>

ICachingStrategy.class.php:
PHP:
<?php
interface ICachingStrategy{
		function cache($item,$timeToCacheInSeconds);
		function retrieve($item);
		function cacheAndRetrieve($item,$timeToCacheInSeconds);
}
?>

FileSystemCachingStrategy.class.php
PHP:
<?php

class FileSystemCachingStrategy implements ICachingStrategy{
		private $cacheLocation;
		private $cachedFileExtension;
		
		function __construct($cacheLocation,$cachedFileExtension){
			$this->cacheLocation = $cacheLocation;
			$this->cachedFileExtension = $cachedFileExtension;
		}
		
		function cacheAndRetrieve($item,$timeToCacheInSeconds){
			$staticItem = $this->cacheLocation . $item . $this->cachedFileExtension;
			$this->cache($item,$timeToCacheInSeconds);
			return $this->retrieve($item);
		}
		
		function cache($item,$timeToCacheInSeconds){
			$staticItem = $this->cacheLocation . $item . $this->cachedFileExtension;
			if(!file_exists($staticItem) || ((time() - filemtime($staticItem)) > $timeToCacheInSeconds)){
				$this->cacheItem($item,$staticItem);
			}	
		}
				
		function cacheItem($item,$staticItem){
			ob_start();
			include($item);
			$contents = ob_get_contents();
			ob_end_clean();
			file_put_contents($staticItem,$contents,LOCK_EX);
		}
		
		function retrieve($item){
			$staticItem = $this->cacheLocation . $item . $this->cachedFileExtension;
			
			if(file_exists($staticItem)){
				return  file_get_contents($staticItem);
			}
		}
}
?>


caching.config.php:
PHP:
<?php
require_once("FileSystemCachingStrategy.class.php");

$OUTPUT_DIRECTORY = "static/";
$PAGE_EXTENSION = ".static";
		
Cache::registerCachingStrategy("FileSystem",new FileSystemCachingStrategy($OUTPUT_DIRECTORY,$PAGE_EXTENSION));
Cache::registerCachingStrategy("Memory",new FileSystemCachingStrategy("/dev/shm/tutorials.de/MemoryCachingStrategy/".$OUTPUT_DIRECTORY,$PAGE_EXTENSION));

Cache::register("page.php","FileSystem", 10); //cache that page for 10 seconds (for the sake of testing ;-)
//Cache::register("minimal_page.php","FileSystem", 10); //cache that page for 10 seconds (for the sake of testing ;-)
?>

in /
template.php

PHP:
<?php

echo "A ";
include("page_cached.php");
echo " B";

/*
echo "<br/>C ";
include("minimal_page_cached.php");
echo " D";
*/

?>

page.php:
PHP:
<?php

echo "<b>Current time: ". time() ." </b>\n";

?>

page_cached.php:
PHP:
<?php
require_once ("./caching/Cache.class.php");
echo Cache::get(str_replace("_cached","",basename(__file__)));
?>

in /static liegt dann beispielsweise:
page.php.static
HTML:
<b>Current time: 1206449879 </b>

Ich glaube jetzt haben wirs fürs erste :) Was meint ihr?

Gruß Tom
 
Hört sich bisher recht gut an, finde ich...

Gibt es eine Testphase? ;)
Mich würde echt mal der Performancegewinn interessieren, der dabei entsteht.
 
Hallo,

@Dennis:
Soll der Cache immer ueber einen bestimmten Zeitraum genutzt werden oder nur solang sich nichts aendert?
Ich hatte mal mit dem Caching von gefuellten Templates gespielt, jedoch ist das Problem eben dass man bei festgelegten Zeitraeumen eventuell nicht immer die aktuellsten Informationen anzeigt.
Die vom Cache zwischengespeicherten Werte haben ein konfigurierbares Verfalldatum Beispielsweise 5 min. Der erste Zugriff nach dieser Zeit stößt dann die Neugenerierung an. Nur dann neu zugenerieren wenn sich der dahinter liegende Content geändert hat wäre eine Optimierung.

Wie schon gesagt die Datenbank als Cache zu verwenden sehe ich als eine weitere Möglichkeit. Vorerst reicht das Filesystem Caching vollkommen aus.

Wenn ihr euer Okay gebt werd ich das heute Abend mal so einbauen und schauen was sich tut.

Gruß Tom
 
Zurück