PHPGangsta - Der praktische PHP Blog

PHP Blog von PHPGangsta


Speichern der Session-Daten im Memcached

with 16 comments

Normalerweise speichert PHP die Session-Daten lokal auf der Festplatte, und zwar im Verzeichnis das in der php.ini unter session.save_path eingetragen ist (z.B. /tmp unter Linux). Das sollte man jedoch dringend anpassen wenn man mehrere Applikationen auf einem Webserver laufen hat, da sonst alle Applikationen dort ihre Session-Daten im selben Verzeichnis ablegen. Wer sich in Applikation 1 einloggt ist auch direkt in Applikation 2 eingeloggt (natürlich nur wenn beide Applikationen ähnliche Daten in der Session speichern, zum Beispiel die UserId).

Also sollte man für jede Applikation einen eigenen Pfad angeben, um die Daten zu trennen. Das macht man normalerweise mit

ini_set('session.save_path', '/application1/sessiondata/);

Der Webserver muß dort selbstverständlich Lese- und Schreibrechte besitzen.

Doch das lokale Speichern auf der Festplatte hat auch Nachteile. Einerseits ist die Geschwindigkeit nicht so berauschend, andererseits möchte man eventuell einen zentralen Speicher nutzen falls man mehrere Webserver hinter einem einfachen Loadbalancer hat, der die einzelnen Requests jeweils auf einen zufälligen Webserver verteilt.

Spätestens dann benötigt man einen zentralen Session-Speicher. Im Zend Framework gibt es bereits eine Lösung mit Hilfe einer Datenbank. Ich möchte hier die Memcached-Lösung vorstellen in Verbindung mit Zend_Session. Ganz ähnlich funktioniert auch die native Lösung mit Hilfe der session_set_save_handler() Funktion.

Wir benötigen zuerst eine Klasse, die einige wichtige Funktionen zur Verfügung stellt:

<?php
class App_Session_SaveHandler_Memcached implements Zend_Session_SaveHandler_Interface
{
	/**
	 * @var int
	 */
	private $_maxlifetime = 3600;
	/**
	 * @var Zend_Cache_Core
	 */
	private $_cache;

	public function __construct(Zend_Cache_Core $cacheHandler) {
		$this->_cache = $cacheHandler;
	}

	public function open($save_path, $name) {
		return true;
	}

	public function close() {
		return true;
	}

	public function read($id) {
		if (!($data = $this->_cache->load('SessionData_'.$id))) {
			return '';
		} else {
			return $data;
		}
	}

	public function write($id, $sessionData) {
		$this->_cache->save($sessionData, 'SessionData_'.$id, array(), $this->_maxlifetime);
		return true;
	}

	public function destroy($id) {
		$this->_cache->remove('SessionData_'.$id);
		return true;
	}

	public function gc($notusedformemcache) {
		return true;
	}
}

Nun können wir diese Klasse als SaveHandler nutzen:

$cache = Zend_Cache::factory(
				'Core',
				$backend,
				$frontendOptions,
				$backendOptions
);

Zend_Session::setSaveHandler(new App_Session_SaveHandler_Memcached($cache));
Zend_Session::setOptions(
	array(
		//'cookie_secure' 	=> true,	// only for https
		'name' 				=> 'AppName1',
		'cookie_httponly'	=> true,
		//'gc_maxlifetime'	=> 60*60,
));
Zend_Session::start();

Nun werden die Session-Daten nicht mehr auf der Festplatte gespeichert, sondern im zentralen Memcached abgelegt. Falls die PHP-Session-Funktionen genutzt werden statt Zend_Session, ist die Lösung fast genauso einfach und die App_Session_SaveHandler_Memcached Klasse mit ein paar Anpassungen nutzbar.

EDIT: Statt dieses in PHP geschriebenen Save-Handlers kann man auch direkt den Handler auf „memcache“ setzen und den Server mittels „save_path“ definieren. Funktioniert genauso, es hat nur den einzigen Nachteil dass man den Key nicht frei wählen kann. Mehrere Memcached-Server kann man einfach kommasepariert auflisten:

Zend_Session::setOptions(
	array(
		'name' 			=> 'App1',
		'save_handler' 	=> 'memcache',
		'save_path'		=> 'tcp://'.$this->_applicationIni->cache->memcached->ip.':' .
								$this->_applicationIni->cache->memcached->port.
								'?persistent=1&amp;weight=1&amp;timeout=1&amp;retry_interval=15',
		'cookie_httponly'=> true,
		'gc_maxlifetime'=> 60*60,
	)
);
Zend_Session::start();

Falls die Fehlermeldung „Cannot find save handler ‚memcache'“ kommt mußt man die memcache-Extension neu kompilieren, dann mit Session-Handler-Support:

pecl uninstall memcache
pecl install memcache
Enable memcache session handler support? [yes] : yes

Danke an Ulf für den Druck, dass ich das auch mal ausprobiere 😉

Written by Michael Kliewe

März 24th, 2010 at 6:23 am

16 Responses to 'Speichern der Session-Daten im Memcached'

Subscribe to comments with RSS or TrackBack to 'Speichern der Session-Daten im Memcached'.

  1. Danke für die Anleitung. Das kann mal ganz nützlich werden und landet gleich mal in den Bookmarks.

    Dieser Blog ist momentan mit Abstand der interessanteste. In meinem Reader (rsslounge) hast du schon höchste Prio erreicht und ich hoffe die Themen bleiben so interessant und knackig.

    Viele Grüße
    Tobi

    Tobi

    24 Mrz 10 at 09:02

  2. Ich versteh immer nicht ganz, warum man es sich so schwierig machen sollte? http://www.dotdeb.org/2008/08/25/storing-your-php-sessions-using-memcached/

    Steffkes

    24 Mrz 10 at 09:15

  3. Warum so kompliziert?

    Unter der Annahme dass man bei einem vorhandenen Memcache auch Zugriff auf die Konfigurationsdateien von PHP hat (die php.ini) kann man auch einfach folgendes in die php.ini schreiben.

    session.save_handler = memcache
    session.save_path = PATH_TO_MEMCACHE (natürlich auch Verbidnung zu anderen Sever möglich)

    Den großen Nachteil mit obiger Lösung sehe ich vor allem beim lokalen Entwickeln auf Windows. Da Memcached sich da äußerst bescheiden installieren lässt, wäre man immer gezwungen eine entsprechende Applikationsweiche für das Verwenden von Session-Daten zu nutzen (oder auf jeden lokalen Windows-Rechner Memcache zu installieren). Dazu muss nicht immer wieder der Handler in der Bootstrap gesetzt werden. Da Zend_Session auch selber die PHP-Session-Funktionen nutzt sehe ich auch keinen Nachteil.

    Ulf

    24 Mrz 10 at 09:17

  4. Da habt ihr beide teilweise recht. ABER:
    – Wie lautet beispielsweise der Key, unter dem die Sessions im Memcached gespeichert werden? Man kann ihn ja nicht selbst festlegen (-> 1 Memcached-Server für 2 Applikationen nicht möglich?)
    – Ist es möglich, dort mehrere memcached-Server anzugeben?
    – Man müßte die Memcached-Server-Liste an vielen Orten pflegen, einmal in den php.ini’s auf allen Webservern (wir nehmen an wir haben ein halbes Dutzend hinter dem genannten Loadbalancer) und einmal in der Applikation. Oder aber an mehreren Stellen im PHP-Code, falls man sie mit ini_set() setzt. Deployments dürften dadurch aufwändiger werden.
    – Man kann glaube ich keine Flags setzen falls man sie benötigt.

    Aber vielleicht setzte ich mich da nochmal ran und probiere damit ein wenig rum. Flexibler fand ich eigentlich die oben beschriebene Lösung und Wiederverwendung des $cache-Objekts.

    Michael Kliewe

    24 Mrz 10 at 09:35

  5. Natürlich musst du die Memcached-Keys selbst erzeugen, aber das musst du auch in deinem Beispiel. Und wenn mehr als eine Applikation auf den Memcache schreibt, dann können auch dort Namenskonflikte passieren (da ja Applikation 1 zufällig den gleichen Key wie Applikation 2 für eine absolut unterschiedliche Aufgabe generieren kann). Der Memcache ist doch so ein dummer Datenhalter dass eine Verwendung über verschiedenen Applikationen hinaus auch sicherheitsmäßig nicht ratsam ist. Es ist eben nur ein dummer – aber verdammt schneller – Key-Value-Store.

    Mehrere Memcached-Instanzen innerhalb einer Applikation zu nutzen für Session-Daten ist doch auch nicht ratsam. Dann habe ich doch da das Problem der Verteilung das die gleiche Session auf die gleiche Memcache-Instanz schreiben / lesen muss. Wenn der Memcache zu klein wird, ist doch eine Aufstockung des Speichers für den Memcache weitaus sinnvoller. Auch da macht es sich bezahlt, dass der Memcache ein solch kleines resourcenschonendes Progrämmchen ist.

    Um um Flags zu setzen, kann man doch immer noch die Applikationslogik für die Memcache-Instanz nutzen. An dieser kommt man ja so oder so nicht vorbei, d.h. gewisse Funktionalitäten müssen implementiert werden. Nur die Anbindung wie die Session-Daten gespeichert werden gehört imo eher in die Konfiguration des Webservers.

    Ulf

    24 Mrz 10 at 10:15

  6. Wenn man es klug anstellt kann man recht gut 2 Applikationen mit nur einem Memcached betreiben, indem man beispielsweise alle Keys von Applikation1 mit „App1_“ beginnen lässt, genauso bei Applikation2. Namenskonflikte sind dann ausgeschlossen. Bei den beiden oben gezeigten Zeilen kann ich nicht erkennen wo man soetwas umsetzen könnte (in meinem Code habe ich „SessionData_“ als Prefix gewählt).

    Mehrere Memcaches innerhalb einer Applikation machen durchaus Sinn. Man definiert beispielsweise zwei, und die Memcached-Extension nimmt normalerweise Memcache1. Falls dieser nicht erreichbar ist, wechselt er zu Memcache2, also ein schöner eingebauter Failover. Dazu muß man aber 2 Server festlegen. (Man kann glaube ich auch 2 Server angeben und die Daten werden auf beide Server geschrieben, also eine Art doppelte Datenhaltung/Replikation, das habe ich aber noch nicht ausprobiert).
    Infos dazu:
    http://www.php.net/manual/en/function.memcache-addserver.php
    http://framework.zend.com/manual/de/zend.cache.backends.html

    Flags (das ist der array, 3. Parameter beim save() Aufruf) können beispielsweise genutzt werden um alle Daten im Memcached, die User1 gehören, mit „User1“ zu flaggen. Dann kann man bei einem Logout des Users alle Memcached-Einträge mit dem Flag „User1“ löschen, um den Memcached-Speicher zu entlasten. Oder ich flagge alle Session-Einträge mit „App1Session“. Dann kann ich sehr einfach alle Sessions im Memcached löschen und die User zum Neueinloggen zwingen. Benutze ich aktuell nicht, aber Flags haben ihre Daseinsberechtigung. Beim Sessionhandling direkt über den „memcache“-Handler kann man glaube ich solche Flags beim Session-Eintrag nicht setzen (berichtigt mich wenn ich falsch liege).

    Michael Kliewe

    24 Mrz 10 at 11:25

  7. Wenn ich beide Applikationen auf den Server programmiere, dann kann ich doch auch zwei Memcache-Instanzen auf unterschiedlichen Ports laufen lassen. Dann sind beide Applikationen nicht voneinander abhängig und gerade der Memcache kann nicht entsprechend konfiguriert werden, weil er eben so primitiv ist. Im Worst Case nutzt meine HighEnd-Applikation 1 den gesamten Memcache und Applikation 2 hat keine Vorteile weil der Memcache durch Applikation 1 geblockt ist.

    Den Failure für den Memcache halte ich auch etwas gewagt. Da bricht dann Memcache 1 weg, da liegen aber noch relevante Session-Daten und dann werden anderen Daten in Memcache 2 geschrieben. Somit sind die Session-Daten auf den verschiedenen Memcache-Instanzen verteilt. Zu allem Übel werden die Session-Daten auf den Memcache 2 noch aktualisiert somit sind die von Memcache 1 ungültig, aber noch vorhanden…

    Bei Punkt 3 sehe ich auch nicht den Punkt. Dann hält der Memcache eben unnütze Daten die er nicht mehr benötigt. Diese werden doch später sowieso eliminiert wenn der Cache voll ist. Da der Memcache so wenig Funktionalität bietet würde ich ihn gerade nicht mit kaskadierenden Löschen beauftragen, da gibt es sicherlich bessere Tools.

    Verstehe mich nicht falsch, in irgendwelchen Szenarien macht es sicherlich Sinn 2 Memcache-Instanzen sowie mehrere Applikationen für den gleichen Memcache zu nutzen in 95% der Fälle aber nicht. Und für einen Memcache für eine Applikation ist die Konfiguration über die php.ini der bessere Weg (denn dann braucht man sogar nicht mal das ZF obwohl man dies auf jedne Fall verwenden sollte). 😉

    Ulf

    24 Mrz 10 at 17:17

  8. Ich habe eben „deine“ Variante ausprobiert, funktioniert soweit ganz gut, habe es aber nicht in die php.ini geschrieben (weil dann eben Probleme mit dem Deployment auftreten würden), sondern Zend_Session das Setzen mittels ini_set() überlassen.

    Zend_Session::setOptions(
    array(
    ’name‘ => ‚App1′,
    ’save_handler‘ => ‚memcache‘,
    ’save_path‘ => ‚tcp://‘.$this->_applicationIni->cache->memcached->ip.‘:‘ .
    $this->_applicationIni->cache->memcached->port.
    ‚?persistent=1&weight=1&timeout=1&retry_interval=15‘,
    ‚cookie_httponly‘ => true,
    ‚gc_maxlifetime‘ => 60*60,
    )
    );
    Zend_Session::start();

    Einen Stolperstein hatte ich allerdings: Ich hatte meine memcache-Extension natürlich ohne den Save-Handler Support kompiliert. Also mußte ich ihn nun neu kompilieren, ging aber problemlos mit
    pecl uninstall memcache
    pecl install memcache
    Enable memcache session handler support? [yes] : yes

    Man kann übrigens mehrere Memcache-Server definieren im save_path, einfach kommaseperiert hintereinander schreiben.

    Michael Kliewe

    24 Mrz 10 at 17:51

  9. Welche Probleme würden denn beim Deployment auftreten wenn dies in der php.ini stehen würde? Weil mehrere Applikationen über das PHP-Modul laufen? Dann ist die Lösung natürlich ok, kann bei privaten /Hobby-Projekten durchaus vorkommen im Business-Bereich aber wohl selten.

    Ich nutze die Zend_Cache_Backend_Memcached Klasse im übrigen zum Cachen von anderen Werten, z.b. Datenbank-Result-Sets oder serialisierten Objekten. Das ist auch super praktisch, weil man auf der lokalen Windows-Kiste einfach eine andere Zend_Cache_Backend Klasse nutzen kann und somit nicht Memcache zwingend auf Windows installieren muss.

    Ansonsten ist Memcache super. Sehr einfach zu bedienen, ein sehr dummer Datenspeichert aber unheimlich performant und schnell.

    Ulf

    24 Mrz 10 at 20:13

  10. Naja, wie bereits gesagt, wenn ich einen Server aus dem Pool nehmen möchte oder einen weiteren hinzufügen möchte, muß ich nur eine neue Version der Applikation deployen, wo dann die entsprechenden Änderungen an der application.ini bzw. Zend_Cache_Backend_Memcached Klasse (bzw. nun auch die Zend_Session::setOptions) gemacht sind. Fertig.
    Sollte ich die Konfiguration des Memcaches sowohl in der Applikation als auch in den php.ini’s stehen haben, muß ich dann auch gleichzeitig auf allen Webservern eine neue php.ini ausrollen, was aber häufig nicht im normalen Deploy-Prozess enthalten ist.

    Ich rede hier natürlich nicht von einem kleinen Privatprojekt, da lohnt sich wahrscheinlich nicht mal das Auslagern der Session-Daten in einen Memcache, sondern von vielen Webservern hinter einem Loadbalancer, wieviele das auch immer sein mögen.

    Michael Kliewe

    24 Mrz 10 at 21:42

  11. Gut klar, einen neuen Webserver ins Loadbalance-Set zu nehmen verursacht Mehraufwand. Da man für dieses Unterfangen aber auch auf jeden Fall auch DocumentRoot o.Ä. anpassen muss, ist das jetzt auch nicht soooo kompliziert. Server anschalten reicht ja leider nie.

    Ansonsten danke für die sehr gute Diskussion und deine hochwertigen Artikel. Kann mich da nur Tobi anschließen!

    Ulf

    25 Mrz 10 at 07:59

  12. Nein, ich meine nicht einen neuen Webserver ins Loadbalancing nehmen, sondern einen neuen Memcached-Server in den Memcached-Pool nehmen (weil mehr Kapazität erforderlich ist), oder aber man muss einen raus nehmen (defekte Hardware).

    Danke für euer Lob!

    Michael Kliewe

    25 Mrz 10 at 08:20

  13. […] Dieser Eintrag wurde auf Twitter von Radek Suski, Michael Kliewe erwähnt. Michael Kliewe sagte: Neuer Blogartikel: Speichern der Session-Daten im Memcached ( https://www.phpgangsta.de/1200 ) […]

  14. memcache !== memcached

    status500

    1 Apr 10 at 09:29

  15. @status500: Wenn man es genau nimmt hast du Recht, der Server heißt „memcached“, und die PHP-Extensions heißen „memcache“ und „memcached“. Die letztere basiert auf libmemcached, die erste (ältere) nicht.
    Man könnte natürlich immer memcache/memcached schreiben wenn man von einer der Extensions redet. Oder man lässt es und redet über die „wichtigen“ Dinge 😉

    Michael Kliewe

    1 Apr 10 at 09:36

Leave a Reply

You can add images to your comment by clicking here.