PHP file_put_contents Dateisperre

9

Der Senario:

Sie haben eine Datei mit einer Zeichenfolge (durchschnittlicher Satzwert) in jeder Zeile. Nehmen wir an, diese Datei hat eine Größe von 1 MB (Tausende von Zeilen).

Sie haben ein Skript, das die Datei liest, einige der Zeichenfolgen im Dokument ändert (nicht nur einige Zeilen anfügt, sondern auch entfernt und ändert) und dann alle Daten mit den neuen Daten überschreibt.

Die Fragen:

  1. Verfügt PHP, OS oder httpd usw. des Servers bereits über Systeme, um solche Probleme zu stoppen (Lesen / Schreiben nach der Hälfte des Schreibvorgangs)?

  2. Wenn ja, erklären Sie bitte, wie es funktioniert, und geben Sie Beispiele oder Links zu relevanten Dokumentationen.

  3. Wenn nicht, gibt es Dinge, die ich aktivieren oder einrichten kann, z. B. das Sperren einer Datei, bis ein Schreibvorgang abgeschlossen ist, und das Fehlschlagen aller anderen Lese- und / oder Schreibvorgänge, bis das vorherige Skript den Schreibvorgang abgeschlossen hat?

Meine Annahmen und andere Informationen:

  1. Auf dem betreffenden Server werden PHP und Apache oder Lighttpd ausgeführt.

  2. Wenn das Skript von einem Benutzer aufgerufen wird und sich in der Mitte des Schreibens in die Datei befindet und ein anderer Benutzer die Datei genau zu diesem Zeitpunkt liest. Der Benutzer, der es liest, erhält nicht das vollständige Dokument, da es noch nicht geschrieben wurde. (Wenn diese Annahme falsch ist, korrigieren Sie mich bitte)

  3. Ich beschäftige mich nur mit dem Schreiben und Lesen von PHP in eine Textdatei, insbesondere mit den Funktionen "fopen" / "fwrite" und hauptsächlich "file_put_contents". Ich habe mir die Dokumentation "file_put_contents" angesehen, aber weder den Detaillierungsgrad noch eine gute Erklärung dafür gefunden, was das "LOCK_EX" -Flag ist oder tut.

  4. Das Szenario ist ein Beispiel für ein Worst-Case-Szenario, bei dem ich davon ausgehen würde, dass diese Probleme aufgrund der Größe der Datei und der Art und Weise, wie die Daten bearbeitet werden, mit größerer Wahrscheinlichkeit auftreten. Ich möchte mehr über diese Probleme erfahren und möchte oder brauche keine Antworten oder Kommentare wie "benutze MySQL" oder "Warum machst du das?", Weil ich das nicht mache. Ich möchte nur etwas über das Lesen / Schreiben von Dateien lernen mit PHP und scheinen nicht an den richtigen Stellen / in der richtigen Dokumentation zu suchen und ja, ich verstehe, dass PHP nicht die perfekte Sprache ist, um auf diese Weise mit Dateien zu arbeiten.

hozza
quelle
2
Ich kann Ihnen aus Erfahrung sagen, dass das Lesen und Schreiben von großen Dateien mit PHP (1 MB ist nicht wirklich so groß, aber dennoch) schwierig (und langsam) sein kann. Sie können die Datei jederzeit sperren , aber es wäre wahrscheinlich einfacher und sicherer, nur eine Datenbank zu verwenden.
NullUserException
Ich weiß, dass es besser wäre, eine Datenbank zu verwenden. Bitte lesen Sie die Frage (letzter Absatz Nummer 4)
Hozza
2
Ich habe die Frage gelesen; Ich sage, es ist keine gute Idee und es gibt bessere Alternativen.
NullUserException
2
file_put_contents()ist nur ein Wrapper für den fopen()/fwrite()Tanz, LOCKEXmacht das gleiche, als ob Sie anrufen würden flock($handle, LOCKEX).
Yannis
2
@hozza Deshalb habe ich einen Kommentar gepostet, keine Antwort.
NullUserException

Antworten:

4

1) Nein 3) Nein

Es gibt mehrere Probleme mit dem ursprünglich vorgeschlagenen Ansatz:

Erstens ist auf einigen UNIX-ähnlichen Systemen wie Linux möglicherweise keine Sperrunterstützung implementiert. Das Betriebssystem sperrt Dateien standardmäßig nicht. Ich habe gesehen, dass die Syscalls NOP (No-Operation) sind, aber das ist ein paar Jahre her. Sie müssen also überprüfen, ob eine von Ihrer Instanz der Anwendung gesetzte Sperre von einer anderen Instanz respektiert wird. (dh 2 gleichzeitige Besucher). Wenn die Sperre immer noch nicht implementiert ist [sehr wahrscheinlich], können Sie diese Datei mit dem Betriebssystem überschreiben.

Das zeilenweise Lesen großer Dateien ist aus Leistungsgründen nicht möglich. Ich schlage vor, file_get_contents () zu verwenden, um die gesamte Datei in den Speicher zu laden und sie dann zu explodieren (), um die Zeilen zu erhalten. Alternativ können Sie fread () verwenden, um die Datei in Blöcken zu lesen. Ziel ist es, die Anzahl der Leseanrufe zu minimieren.

In Bezug auf das Sperren von Dateien:

LOCK_EX bedeutet eine exklusive Sperre (normalerweise zum Schreiben). Nur ein Prozess kann zu einem bestimmten Zeitpunkt eine exklusive Sperre für eine bestimmte Datei enthalten. LOCK_SH ist eine gemeinsam genutzte Sperre (normalerweise zum Lesen). Mehr als ein Prozess kann zu einem bestimmten Zeitpunkt eine gemeinsam genutzte Sperre für eine bestimmte Datei enthalten. LOCK_UN entsperrt die Datei. Das Entsperren erfolgt automatisch, wenn Sie file_get_contents () http://en.wikipedia.org/wiki/File_locking#In_Unix-like_systems verwenden

Elegante Lösung

PHP unterstützt Datenstromfilter, die zur Verarbeitung von Daten in Dateien oder von anderen Eingaben vorgesehen sind. Möglicherweise möchten Sie einen solchen Filter mithilfe der Standard-API ordnungsgemäß erstellen. http://php.net/manual/en/function.stream-filter-register.php http://php.net/manual/en/filters.php

Alternative Lösung (in 3 Schritten):

  1. Erstellen Sie eine Warteschlange. Anstatt einen Dateinamen zu verarbeiten, verwenden Sie die Datenbank oder einen anderen Mechanismus, um eindeutige Dateinamen irgendwo in ausstehend / und verarbeitet in / verarbeitet zu speichern. Auf diese Weise wird nichts überschrieben. Die Datenbank ist auch nützlich, um zusätzliche Informationen wie Metadaten, zuverlässige Zeitstempel, Verarbeitungsergebnisse und andere zu speichern.

  2. Lesen Sie für Dateien bis zu einigen MB die gesamte Datei in den Speicher und verarbeiten Sie sie dann (file_get_contents () + explode () + foreach ()).

  3. Bei größeren Dateien lesen Sie die Datei in Blöcken (dh 1024 Bytes) und verarbeiten und schreiben Sie jeden Block in Echtzeit als Lesevorgang (achten Sie auf die letzte Zeile, die nicht mit \ n endet. Sie muss im nächsten Stapel verarbeitet werden).


quelle
1
"Ich habe gesehen, dass die Syscalls NOP (No-Operation) sind ..." Welcher Kernel?
Massimo
1
"Das zeilenweise Lesen großer Dateien ist aus Leistungsgründen nicht möglich. Ich empfehle, file_get_contents () zu verwenden, um die gesamte Datei in den Speicher zu laden ..." Dies ist nicht sinnvoll. Ich kann sagen: Lesen Sie aus Leistungsgründen keine großen Dateien in den Speicher ... Was zu tun ist, hängt von vielen anderen Faktoren ab.
Massimo
4

Ich weiß, dass dies uralt ist, aber für den Fall, dass jemand darauf stößt. IMHO ist der Weg dahin wie folgt:

1) Öffnen Sie die Originaldatei (z. B. original.txt) mit file_get_contents ('original.txt').

2) Nehmen Sie Ihre Änderungen / Bearbeitungen vor.

3) Verwenden Sie file_put_contents ('original.txt.tmp') und schreiben Sie es in eine temporäre Datei original.txt.tmp.

4) Verschieben Sie dann die tmp-Datei in die Originaldatei und ersetzen Sie die Originaldatei. Dazu verwenden Sie die Umbenennung ('original.txt.tmp', 'original.txt').

Vorteile: Während die Datei verarbeitet und in die Datei geschrieben wird, ist sie nicht gesperrt und andere können den alten Inhalt weiterhin lesen. Zumindest unter Linux / Unix-Boxen ist das Umbenennen eine atomare Operation. Unterbrechungen während des Schreibens der Datei berühren nicht die Originaldatei. Erst wenn die Datei vollständig auf die Festplatte geschrieben wurde, wird sie verschoben. Weitere interessante Informationen finden Sie in den Kommentaren zu http://php.net/manual/en/function.rename.php

Bearbeiten, um Kommentare zu adressieren (auch für Kommentare):

/programming/7054844/is-rename-atomic enthält weitere Verweise darauf, was Sie möglicherweise tun müssen, wenn Sie dateisystemübergreifend arbeiten.

Bei der gemeinsam genutzten Sperre für das Lesen bin ich mir nicht sicher, warum dies erforderlich wäre, da in dieser Implementierung nicht direkt in die Datei geschrieben wird. Die Herde von PHP (die verwendet wird, um die Sperre zu erhalten) ist ein wenig, aber unzuverlässig und kann von anderen Prozessen ignoriert werden. Deshalb schlage ich vor, die Umbenennung zu verwenden.

Die Umbenennungsdatei sollte idealerweise eindeutig für den Prozess benannt werden, der das Umbenennen durchführt, um sicherzustellen, dass nicht zwei Prozesse dasselbe tun. Dies verhindert jedoch natürlich nicht, dass dieselbe Datei gleichzeitig von mehr als einer Person bearbeitet wird. Aber zumindest bleibt die Datei intakt (die letzte Bearbeitung gewinnt).

Schritt 3) & 4) würde dann dies werden:

$tempfile = uniqid(microtime(true)); // make sure we have a unique name
file_put_contents($tempFile); // write temp file
rename($tempfile, 'original.txt'); // ideally on the same filesystem
Dom
quelle
Genau das, was ich auch vorschlagen wollte. Ich würde aber auch beim Lesen eine gemeinsame Sperre erwerben, um Datenüberfälle zu vermeiden.
d3L
Das Umbenennen ist eine atomare Operation auf derselben Festplatte, nicht auf verschiedenen Festplatten.
Xnoise
Um einen eindeutigen temporären Namen wirklich zu gewährleisten, können Sie auch dietempnam Funktionen verwenden, mit denen eine Datei atomar erstellt und der Dateiname zurückgegeben wird.
Matthijs Kooijman
1

In der PHP-Dokumentation für file_put_contents () finden Sie in Beispiel 2 die Verwendung für LOCK_EX .

file_put_contents('somefile.txt', 'some text', LOCK_EX);

Der LOCK_EX ist eine Konstante mit einem ganzzahligen Wert, der für einige Funktionen bitweise verwendet werden kann .

Es gibt auch eine spezielle Funktion, um das Sperren von Dateien zu steuern: flock () Weise.

Augusto Pascutti
quelle
Dies ist zwar interessant und kann in einigen Situationen beim Lesen, Ändern und Umschreiben einer Datei hilfreich sein. Die Sperre sollte jedoch vor dem Lesen erworben und beibehalten werden, bis sie vollständig neu geschrieben wurde (andernfalls kann ein anderer Prozess eine alte Kopie lesen und ändern zurück, nachdem Ihr Prozess abgeschlossen ist). Ich glaube nicht, dass dies mit erreicht werden kann file_get/put_contents.
Jules
0

Ein Problem, von dem Sie nicht erwähnt haben, dass Sie auch vorsichtig sein müssen, sind die Rennbedingungen, bei denen zwei Instanzen Ihres Skripts fast gleichzeitig ausgeführt werden, z. B. diese Reihenfolge:

  1. Skriptinstanz 1: Liest die Datei
  2. Skriptinstanz 2: Liest die Datei
  3. Skriptinstanz 1: Schreibt Änderungen in die Datei
  4. Skriptinstanz 2: Überschreibt die Änderungen der ersten Skriptinstanz an der Datei mit ihren eigenen Änderungen (da zu diesem Zeitpunkt das Lesen veraltet ist).

Wenn Sie also eine große Datei aktualisieren, müssen Sie diese Datei LOCK_EX, bevor Sie sie lesen, und die Sperre erst aufheben, wenn die Schreibvorgänge ausgeführt wurden. In diesem Beispiel wird die zweite Skriptinstanz meines Erachtens ein wenig hängen bleiben, während sie darauf wartet, auf die Datei zuzugreifen. Dies ist jedoch besser als Datenverlust.

Thoracius Appotite
quelle