php.de

Zurück   php.de > Webentwicklung > PHP-Fortgeschrittene

PHP-Fortgeschrittene Arbeiten mit PHP ohne Einschränkungen

Antwort
 
LinkBack Themen-Optionen Thema bewerten
Alt 14.10.2009, 21:32  
Benutzer
 
Registriert seit: 17.06.2009
Beiträge: 97
PHP-Kenntnisse:
Fortgeschritten
hondatuner befindet sich auf einem aufstrebenden Ast
Standard Race Conditions, atomar & flock()

Hallo,

ich beschäftige mich aktuell wieder mal mit meinem Filecache, den ich dazu nutze Abfrageergebnisse der Datenbank auszulagern.

Das typische Konstrukt:
Code:
$h = fopen($filename, 'r');
flock($h, LOCK_EX);
// hier atomar glücklich werden
flock($h, LOCK_UN);
fclose($h);
Allerdings ist ja bekannt, dass dieses Konstrukt eben nicht atomar ist.

So hieße es bei dem Beispielcode:
Code:
$h = fopen($filename, 'r');
// hier könnte ein zweiter Prozess ebenfalls mit fopen($filename, 'r'); loslegen
flock($h, LOCK_EX);
// je nachdem welcher Prozess zuerst locked, bleibt der andere stehen und wartet ab
// der letzte Prozess überschreibt die Datei dann noch mal
flock($h, LOCK_UN);
fclose($h);
file_exists() hilft an dieser Stelle auch nicht weiter, da vorher und nachher genauso wieder parallele Prozesse laufen könnten. Übrigens bringt auch is_writable(), include() oder file_get_contents(). Alle anderen Funktionen ignorieren den Lock-Status von flock().

Daher bedienen sich manche einem Konstrukt, dass auf link() basiert. Allerdings hängt das vom eigenen Bedarf ab, ob das überhaupt Sinn macht.

In der Praxis gibt es denke ich mal drei Nutzungsarten:
1.) Daten die geschrieben und wiederrum selbst als Datenquelle zur Aktualisierung genutzt werden (z.B. Counter)
2.) Daten die geschrieben werden, allerdings nur in Abhängigkeit zu einer dritten Quelle aktualisiert werden (z.B. MySQL)
3.) Daten werden nur einmal geschrieben, die erste Speicherung hat Vorrang (z.B. bei einem Suchspiel "Wer findet es zuerst")

Bei 2.) ist ein atomarer Prozess eigentlich unwichtig, denn dadurch, dass die Prozesse nacheinander abgearbeitet werden und sich entsprechend einer externen Quelle bedienen, wird am Ende immer der richtige Datensatz gespeichert. Natürlich kann es dabei vorkommen, dass mehrere Prozesse lesen, obwohl nur einer nötig wäre, aber hier muss man klar abwägen, was schneller ist. Mehrere Leseprozesse, wenn die Datensätze aktualisiert werden oder bei jedem Einlesen der Daten einen atomare Prozess ablaufen zu lassen.

Bei 3.) sollte man ein tempname() / link() Konstrukt wählen. Jeder folgende Prozess schlägt fehl.

Bei 1.) hätte man gerne einen atomaren Prozess, dieser ist aber nicht möglich, zumindest nicht mit einem tempname() / link() Konstrukt. Schließlich wollen wir die Daten einlesen und gleichzeitig aktualisieren und jeder Prozess soll sich möglichst hinten anstellen. Das geht in dem Fall nicht, da hier einfach nur der erste Prozess gewinnt und die letzten ignoriert werden.

Um es doch zu schaffen, muss man folgendes machen:
Code:
<?php
$filename = 'cache/counter.txt';
$hits = 0;
$h = fopen($filename, 'a');
// alle Prozesse reihen sich brav ein und warten, bis der vorherige fertig ist
if (flock($h, LOCK_EX)) {
	// erst jetzt lesen wird die Daten
	$hits = intval(@file_get_contents($filename)) + 1;
	// und überschreiben sie mit dem neuen wert
	ftruncate($h, 0);
	fwrite($h, $hits);
	flock($h, LOCK_UN);
	fclose($h);
	umask(0000);
	chmod($filename, 0644);
}
echo($hits);
?>
Ein file_exists() ist hier völlig unnötig, da beim ersten Aufruf file_get_contents() diesen Job "übernimmt". Das ist schneller, als bei jedem Prozess die Existenz der Datei zu prüfen, die in 99,9% der Fälle eh da sein muss. Schließlich löschen wir die counter.txt ja nicht mehr.

Das Script ist nun atomar, allerdings gibt es da einen kleinen Haken, der sich Performance nennt. Es ist nunmal ziemlich langsam bei mehreren verschiedenen Caches parallel, ständig Filehandler zu generieren, die Dateien zu locken und deren Inhalte nur dann zu beziehen, wenn selber gelockt werden konnte. Auch hat man das Problem, dass sich jeder Prozess in die Warteschlange einreiht. Ein Flaschenhals ist hier vorprogrammiert.

Also bleibt einem eigentlich gar nichts anderes übrig, als damit zu leben, dass die Prozesse nicht atomar ablaufen. D.h. für unseren Counter folgendes:
Code:
<?php
$filename = 'cache/counter.txt';
$hits = 0;
$h = fopen($filename, 'a');
// diesmal resultiert flock() sofort false, wenn nicht gelockt werden kann
if (flock($h, LOCK_EX | LOCK_NB)) {
	// erst jetzt lesen wird die Daten
	$hits = intval(@file_get_contents($filename)) + 1;
	// und überschreiben sie mit dem neuen wert
	ftruncate($h, 0);
	fwrite($h, $hits);
	flock($h, LOCK_UN);
	fclose($h);
	umask(0000);
	chmod($filename, 0644);
}
else {
	// mit etwas glück, bekommen wir doch noch ein paar daten
	$hits = intval(@file_get_contents($filename)) + 1;
	fclose($h);
}
echo($hits);
?>
Nun haben wir keinen Flaschenhals mehr. Wir leben einfach damit, dass manche Zugriffe nicht gezählt werden (was natürlich sehr selten passiert). Allerdings generieren wir trotzdem ständig neue filehandler und locken wollen wir auch noch bei jedem Aufruf.

Daher gehen wir weg von flock() und atomar und machen einfach folgendes:
Code:
if (($hits = @file_get_contents($filename)) === false || $hits{strlen($hits)-1} == '$') {
	$hits = $hits ? $hits+1 : 1;
	$h = fopen($filename, 'w');
	fwrite($h, $hits . '$');
	fclose($h);
	umask(0000);
	chmod($filename, 0644);
}
In diesem Fall kann file_get_contents() nur dann FALSE resultieren, wenn die Datei nicht vorhanden ist. D.h. die erste Bedingung greift nur, wenn die Datei wirklich nicht vorhanden ist (=file_exists()).

Die zweite Bedingung ist ein einfacher Trick, der gleichzeitig den Datensatz validiert. Da wir der Zahl ein Dollarzeichen als Prüfwert anhängen (Wichtig: Der muss am Ende stehen, da Dateien von vorne nach hinten geschrieben werden), wissen wir bei Vorhandensein, dass die Datei weder leer, noch gerade beschrieben wird.

Nun kommen wir zu 2.):
Wenn wir die Daten nicht jedesmal aktualisieren, sondern hauptsächlich lesen, bietet sich das zuvor genannte Konstrukt perfekt an:
Code:
if (($data = @file_get_contents($filename)) === false || ($data = @unserialize($data)) === false) {
	// read data (db query...)
	$data = array(
		'id' => 1111,
		0 => 'Lorem ipsum dolor sit amet,',
		1 => 'Lorem ipsum amet,',
		2 => 'Lorem ipsum',
	);
	$h = @fopen($filename, 'w');
	@fwrite($h, serialize($data));
	@fclose($h);
	@umask(0000);
	@chmod($filename, 0644);
}
Hier wieder das gleiche. file_get_contents() spielt file_exists() und danach ist serialize() unser Validator, denn erst wenn der eingelesene String fehlerhaft ist (was nur passiert, wenn die Datei gerade beschrieben wird), wird der Datensatz live eingelesen und sehr seltenen Fällen passiert das mehrmals parallel, aber am Ende stehen wie gehabt die korrekten Daten im Cache.

Zuletzt möchte ich auf eine Aktualisierung per Interval eingehen:
Code:
if (($data = @file_get_contents($filename)) === false || ($data = @unserialize($data)) === false || filemtime($filename) + 86400 < time()) {
Hier wird die Datei im Fehlerfall und wenn sie älter als 86400 Sekunden ist (1 Tag) aktualisiert.

filemtime() liest hier die Dateizeit aus, allerdings ist filemtime() ziemlich langsam und deswegen sollte man wie folgt vorgehen:
Code:
if (($data = @file_get_contents($filename)) === false || ($data = @unserialize($data)) === false || (mt_rand(0, 100) == 100 && filemtime($filename) + 86400 < $time())) {
Mit mt_rand(0, 100) == 100 wird nur in jedem 100. Fall filemtime() aufgerufen. Damit wird die Funktion um 50% beschleunigt. Umso höher der Faktor, umso schneller die Funktion. Allerdings muss man hier abwägen, wie oft das Script innerhalb des Intervalls durchläuft, damit überhaupt noch die Wahrscheinlichkeit besteht, dass filemtime() aufgerufen wird.

Hier sind verschiedene Varianten denkbar. Bei meiner Template-Klasse arbeite ich z.B. mit einem Cookie, dass nur ich als Designer übertrage. Alle anderen Nutzer lösen filemtime() gar nicht erst aus.

Bei einem Intervall, wo die Daten möglichst um 00:00 zur Verfügung stehen (wie z.B. Geburtstage), sollte man mit mktime() arbeiten und einen "von bis"-Bereich hinterlegen.

Ich hoffe ein paar konnten mit meinem Beitrag etwas anfangen

Hier übrigens die Benchmarks zu den verschiedenen Varianten:
Benchmark: $gentime vs. filemtime (Filecache) - Forum: PHP

Gruß
__________________
meine PHP Scripte
hondatuner ist offline   Mit Zitat antworten
Sponsor Mitteilung
PHP Code Flüsterer

Registriert seit: 21.08.2005
Beiträge: 4682
PHP-Kenntnisse:
Fortgeschritten

Alt 14.10.2009, 21:36  
Moderator und Wett-König
 
Benutzerbild von dr.e.
 
Registriert seit: 21.05.2008
Beiträge: 3.657
PHP-Kenntnisse:
Fortgeschritten
dr.e. ist ein Lichtblickdr.e. ist ein Lichtblickdr.e. ist ein Lichtblickdr.e. ist ein Lichtblickdr.e. ist ein Lichtblickdr.e. ist ein Lichtblick
dr.e. eine Nachricht über Skype™ schicken
Standard

Zitat:
Ich hoffe ein paar konnten mit meinem Beitrag etwas anfangen
Du solltest jedoch dazu sagen, dass das alles nur für Single-Host-Filesysteme funktioniert, im Cluster (z.B. auf GFS) ist das kontraproduktiv. Stichwort: Lock-Acquise und Journaling!
__________________
Viele Grüße,
Dr.E.

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1. Think about software design before you start to write code!
2. Discuss and review it together with experts!
3. Choose good tools (-> Adventure PHP Framework (APF))!
4. Write clean and reusable software only!
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
dr.e. ist offline   Mit Zitat antworten
Alt 14.10.2009, 21:55  
Benutzer
 
Registriert seit: 17.06.2009
Beiträge: 97
PHP-Kenntnisse:
Fortgeschritten
hondatuner befindet sich auf einem aufstrebenden Ast
Standard

Jo das steht in dem ersten Link, den ich gepostet habe, daher bin ich davon ausgegangen

Bei flock() / tempname() funktioniert es nur in der gleichen Partition. Bei meinem Script ist das allerdings egal, da es sich nicht atomar bewegt und es auch nicht braucht (aus den zuvor genannten Gründen).

Ich denke auch, dass ein atomarer Zugriff so gut wie nie gebraucht wird. Zumindest habe ich noch kein Projekt erlebt, wo das wichtig gewesen wäre, außer bei einem Such-und-Find-Spiel, wo der erste Besucher als Gewinner ermittelt werden sollte. Aber selbst das kann man bei Clustern gut in den Griff kriegen, wenn man parallel eine Datei mit einem unique-Namen speichert, die die microtime() des Besuchers und seine ID enthält. Falls dann mehrere Dateien vorhanden sein sollten, kann man immer noch die auslesen, die die ältesten microtime() enthält.
__________________
meine PHP Scripte
hondatuner ist offline   Mit Zitat antworten
Alt 14.10.2009, 21:59  
Moderator und Wett-König
 
Benutzerbild von dr.e.
 
Registriert seit: 21.05.2008
Beiträge: 3.657
PHP-Kenntnisse:
Fortgeschritten
dr.e. ist ein Lichtblickdr.e. ist ein Lichtblickdr.e. ist ein Lichtblickdr.e. ist ein Lichtblickdr.e. ist ein Lichtblickdr.e. ist ein Lichtblick
dr.e. eine Nachricht über Skype™ schicken
Standard

Zitat:
Bei flock() funktioniert es nur in der gleichen Partition.
... was GFS auf einem Shared Root Cluster zweifeldohne ist.

Zitat:
Falls dann mehrere Dateien vorhanden sein sollten, kann man immer noch die auslesen, die die ältesten microtime() enthält.
Und da kommt Journaling wieder zum Zug. Insbesondere bei GFS (oder im allgemeinen bei Storage-Clustern) sollte man hier genau prüfen, ob so ein Lock - wenn auch nicht exklusiv - in Hochlastzeiten kein D-Prozesse erzeugt.
__________________
Viele Grüße,
Dr.E.

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1. Think about software design before you start to write code!
2. Discuss and review it together with experts!
3. Choose good tools (-> Adventure PHP Framework (APF))!
4. Write clean and reusable software only!
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
dr.e. ist offline   Mit Zitat antworten
Alt 14.10.2009, 22:05  
Benutzer
 
Registriert seit: 17.06.2009
Beiträge: 97
PHP-Kenntnisse:
Fortgeschritten
hondatuner befindet sich auf einem aufstrebenden Ast
Standard

Welchen nicht exklusiven Lock meinst Du? Meine Idee enthält keinen Lock.
__________________
meine PHP Scripte
hondatuner ist offline   Mit Zitat antworten
Alt 14.10.2009, 22:09  
Erfahrener Benutzer
 
Registriert seit: 16.07.2005
Beiträge: 1.007
PHP-Kenntnisse:
Fortgeschritten
brian johnson befindet sich auf einem aufstrebenden Ast
Standard

Zitat:
if (flock($h, LOCK_EX | LOCK_NB)) {
du musst hier die $ewouldblock bedingung testen, denn flock gibt auch TRUE zurück, wenn es nicht blockt aber dennoch keinen lock beziehen konnte mit LOCK_NO_BLOCKING.

Zitat:
Die zweite Bedingung ist ein einfacher Trick, der gleichzeitig den Datensatz validiert. Da wir der Zahl ein Dollarzeichen als Prüfwert anhängen (Wichtig: Der muss am Ende stehen, da Dateien von vorne nach hinten geschrieben werden), wissen wir bei Vorhandensein, dass die Datei weder leer, noch gerade beschrieben wird.
es kann dir dennoch passieren, das die datei nur teilweise geschrieben wurde, da es X dateihandler geben kann. das ist eben der wichtige punkt, niemand garantiert dir, das jetzt gerade nur du in die datei schreibst, denn auch das schreiben ist nicht atomar.

sorry, für mehr bin ich jetzt zu müde....
__________________
PHP4?!?>>>Aktuelle PHP Version: 5.2.11 || 5.3.0
Suse 11.2 *vorfreude*

Geändert von brian johnson (14.10.2009 um 22:23 Uhr).
brian johnson ist offline   Mit Zitat antworten
Alt 14.10.2009, 23:28  
Moderator und Wett-König
 
Benutzerbild von dr.e.
 
Registriert seit: 21.05.2008
Beiträge: 3.657
PHP-Kenntnisse:
Fortgeschritten
dr.e. ist ein Lichtblickdr.e. ist ein Lichtblickdr.e. ist ein Lichtblickdr.e. ist ein Lichtblickdr.e. ist ein Lichtblickdr.e. ist ein Lichtblick
dr.e. eine Nachricht über Skype™ schicken
Standard

Zitat:
Zitat von hondatuner Beitrag anzeigen
Welchen nicht exklusiven Lock meinst Du? Meine Idee enthält keinen Lock.
Die Diskussion würde jetzt zu weit führen. Schau dir mal
FAQ/GeneralQuestions - Cluster Wiki und im Speziellen GFS an. Hier findest du unter dem Stichwort DLM einiges.
__________________
Viele Grüße,
Dr.E.

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1. Think about software design before you start to write code!
2. Discuss and review it together with experts!
3. Choose good tools (-> Adventure PHP Framework (APF))!
4. Write clean and reusable software only!
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
dr.e. ist offline   Mit Zitat antworten
Alt 19.10.2009, 11:45  
Erfahrener Benutzer
 
Registriert seit: 30.07.2008
Beiträge: 1.167
PHP-Kenntnisse:
Fortgeschritten
xm22 sorgt für eine eindrucksvolle Atmosphärexm22 sorgt für eine eindrucksvolle Atmosphärexm22 sorgt für eine eindrucksvolle Atmosphäre
Standard

Das ist zwar nicht so flexibel, aber was spricht denn gegen file_put_contents?
xm22 ist offline   Mit Zitat antworten
Alt 19.10.2009, 16:25  
Erfahrener Benutzer
 
Registriert seit: 16.07.2005
Beiträge: 1.007
PHP-Kenntnisse:
Fortgeschritten
brian johnson befindet sich auf einem aufstrebenden Ast
Standard

Zitat:
Zitat von xm22 Beitrag anzeigen
Das ist zwar nicht so flexibel, aber was spricht denn gegen file_put_contents?
Lost update
__________________
PHP4?!?>>>Aktuelle PHP Version: 5.2.11 || 5.3.0
Suse 11.2 *vorfreude*
brian johnson ist offline   Mit Zitat antworten
Alt 20.10.2009, 13:12  
Erfahrener Benutzer
 
Registriert seit: 30.07.2008
Beiträge: 1.167
PHP-Kenntnisse:
Fortgeschritten
xm22 sorgt für eine eindrucksvolle Atmosphärexm22 sorgt für eine eindrucksvolle Atmosphärexm22 sorgt für eine eindrucksvolle Atmosphäre
Standard

Finden bei file_put_contents parallele Schreibzugriffe statt?

EDIT: Ich meine so, dass PHP mehrere Prozesse gleichzeitig auf eine Datei zugreifen lässt.
xm22 ist offline   Mit Zitat antworten
Antwort


Themen-Optionen
Thema bewerten
Thema bewerten:

Forumregeln
Es ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.

BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus.
Trackbacks are an
Pingbacks are an
Refbacks are an
Gehe zu

Ähnliche Themen
Thema Autor Forum Antworten Letzter Beitrag
Race Condition Problem (INSERT -> SELECT) R4v3r Datenbanken 5 11.08.2009 12:58
Neue Versionen von Thunderbird, SeaMonkey und Flock PHP Tipps 2007 0 03.08.2007 12:06
flock() brian johnson PHP-Fortgeschrittene 7 27.12.2006 17:46

Besucher kamen über folgende Suchanfragen bei Google auf diese Seite
php atomar, php flock include, php flock, php race condition, php flock race condition, atomarer prozess, race condition php, parallele prozesse php, flock php, php race conditions, race conditions php, php flock hängt, php flock atomar, php datei exklusiv erzeugen atomar, file_put_contents php lock_ex, php.de flock atomar, was bedeutet atomar php, flock chmod php, php txt racing conditions, flock race condition

Alle Zeitangaben in WEZ +2. Es ist jetzt 22:19 Uhr.




Powered by vBulletin® Version 3.7.2 (Deutsch)
Copyright ©2000 - 2012, Jelsoft Enterprises Ltd.
Search Engine Optimization by vBSEO 3.2.0
Aprilia-Forum, Aquaristik-Forum, Liebeskummer-Forum, Zierfisch-Forum, Geizkragen-Forum