PHP file_put_contents Verrouillage de fichier

9

Le Senario:

Vous avez un fichier avec une chaîne (valeur moyenne de la phrase) sur chaque ligne. Pour des raisons d'argument, disons que ce fichier a une taille de 1 Mo (milliers de lignes).

Vous disposez d'un script qui lit le fichier, modifie certaines des chaînes du document (non seulement en ajoutant mais également en supprimant et en modifiant certaines lignes), puis écrase toutes les données par les nouvelles données.

Questions:

  1. Est-ce que «le serveur» PHP, OS ou httpd etc. a déjà des systèmes en place pour arrêter des problèmes comme celui-ci (lecture / écriture à mi-chemin d'une écriture)?

  2. Si tel est le cas, veuillez expliquer comment cela fonctionne et donner des exemples ou des liens vers la documentation pertinente.

  3. Sinon, puis-je activer ou configurer des éléments, tels que le verrouillage d'un fichier jusqu'à la fin d'une écriture et l'échec de toutes les autres lectures et / ou écritures jusqu'à l'écriture du script précédent?

Mes hypothèses et autres informations:

  1. Le serveur en question exécute PHP et Apache ou Lighttpd.

  2. Si le script est appelé par un utilisateur et est à mi-chemin de l'écriture dans le fichier et qu'un autre utilisateur lit le fichier à ce moment précis. L'utilisateur qui le lit n'obtiendra pas le document complet, car il n'a pas encore été écrit. (Si cette hypothèse est fausse, veuillez me corriger)

  3. Je ne m'intéresse qu'à l'écriture et à la lecture de PHP dans un fichier texte, et en particulier aux fonctions "fopen" / "fwrite" et principalement "file_put_contents". J'ai regardé la documentation "file_put_contents" mais je n'ai pas trouvé le niveau de détail ou une bonne explication de ce que le drapeau "LOCK_EX" est ou fait.

  4. Le scénario est un exemple du pire des cas où je suppose que ces problèmes sont plus susceptibles de se produire, en raison de la grande taille du fichier et de la façon dont les données sont modifiées. Je veux en savoir plus sur ces problèmes et je ne veux pas ou n'ai pas besoin de réponses ou de commentaires tels que "utilisez mysql" ou "pourquoi faites-vous cela" parce que je ne fais pas cela, je veux juste en savoir plus sur la lecture / écriture de fichiers avec PHP et ne semblent pas chercher dans les bons endroits / documentation et oui je comprends que PHP n'est pas le langage parfait pour travailler avec des fichiers de cette façon.

hozza
la source
2
Je peux vous dire par expérience que la lecture et l'écriture de fichiers volumineux avec PHP (1 Mo n'est pas vraiment si grand, mais quand même) peut être délicate (et lente). Vous pouvez toujours verrouiller le fichier, mais il serait probablement plus facile et plus sûr d'utiliser une base de données.
NullUserException
Je sais qu'il serait préférable d'utiliser une base de données. Veuillez lire la question (dernier paragraphe numéro 4)
hozza
2
J'ai lu la question; Je dis que ce n'est pas une bonne idée et qu'il existe de meilleures alternatives.
NullUserException
2
file_put_contents()est juste un emballage pour la fopen()/fwrite()danse, LOCKEXfait la même chose que si vous appeliez flock($handle, LOCKEX).
yannis
2
@hozza C'est pourquoi j'ai posté un commentaire, pas une réponse.
NullUserException du

Réponses:

4

1) Non 3) Non

L'approche initiale suggérée pose plusieurs problèmes:

Premièrement, certains systèmes de type UNIX tels que Linux peuvent ne pas avoir de prise en charge de verrouillage implémentée. Le système d'exploitation ne verrouille pas les fichiers par défaut. J'ai vu que les appels système étaient NOP (aucune opération), mais il y a quelques années, vous devez donc vérifier si un verrou défini par votre instance de l'application est respecté par une autre instance. (soit 2 visiteurs simultanés). Si le verrouillage n'est toujours pas implémenté [il est très probable qu'il le soit], le système d'exploitation vous permet d'écraser ce fichier.

La lecture de gros fichiers ligne par ligne n'est pas possible pour des raisons de performances. Je suggère d'utiliser file_get_contents () pour charger le fichier entier en mémoire puis l'exploser () pour obtenir les lignes. Vous pouvez également utiliser fread () pour lire le fichier en blocs. L'objectif est de minimiser le nombre d'appels lus.

En ce qui concerne le verrouillage de fichiers:

LOCK_EX signifie un verrou exclusif (généralement pour l'écriture). Un seul processus peut détenir un verrou exclusif pour un fichier donné à un moment donné. LOCK_SH est un verrou partagé (généralement pour la lecture), plusieurs processus peuvent détenir un verrou partagé pour un fichier donné à un moment donné. LOCK_UN déverrouille le fichier. Le déverrouillage se fait automatiquement si vous utilisez file_get_contents () http://en.wikipedia.org/wiki/File_locking#In_Unix-like_systems

Solution élégante

PHP prend en charge les filtres de flux de données qui sont destinés au traitement des données dans des fichiers ou à partir d'autres entrées. Vous souhaiterez peut-être créer un tel filtre correctement à l'aide de l'API standard. http://php.net/manual/en/function.stream-filter-register.php http://php.net/manual/en/filters.php

Solution alternative (en 3 étapes):

  1. Créez une file d'attente. Au lieu de traiter un nom de fichier, utilisez la base de données ou un autre mécanisme pour stocker des noms de fichiers uniques quelque part dans en attente / et traités dans / traités. De cette façon, rien n'est écrasé. La base de données sera également utile pour stocker des informations supplémentaires, telles que des métadonnées, des horodatages fiables, des résultats de traitement et autres.

  2. Pour les fichiers jusqu'à quelques Mo, lisez le fichier entier en mémoire puis traitez-le (file_get_contents () + explode () + foreach ())

  3. Pour les fichiers plus volumineux, lisez le fichier en blocs (c'est-à-dire 1024 octets) et traitez + écrivez en temps réel chaque bloc en lecture (attention à la dernière ligne qui ne se termine pas par \ n. Elle doit être traitée dans le lot suivant)


la source
1
"J'ai vu les appels système être NOP (pas d'opération) ..." quel noyau?
Massimo
1
"La lecture des fichiers volumineux ligne par ligne n'est pas possible pour des raisons de performances. Je suggère d'utiliser file_get_contents () pour charger le fichier entier en mémoire ..." C'est un non-sens. Je peux dire: pour des raisons de performances, ne lisez pas les gros fichiers en mémoire ... Ce qu'il faut faire dépend de nombreux autres facteurs.
Massimo
4

Je sais que c'est vieux, mais au cas où quelqu'un se heurterait à cela. À mon humble avis, la façon de procéder est la suivante:

1) Ouvrez le fichier d'origine (par exemple original.txt) en utilisant file_get_contents ('original.txt').

2) Apportez vos modifications / modifications.

3) Utilisez file_put_contents ('original.txt.tmp') et écrivez-le dans un fichier temporaire original.txt.tmp.

4) Déplacez ensuite le fichier tmp vers le fichier d'origine en remplaçant le fichier d'origine. Pour cela, vous utilisez renommer ('original.txt.tmp', 'original.txt').

Avantages: Pendant le traitement et l'écriture du fichier, le fichier n'est pas verrouillé et les autres peuvent toujours lire l'ancien contenu. Au moins sur les boîtes Linux / Unix, renommer est une opération atomique. Aucune interruption pendant l'écriture du fichier ne touche le fichier d'origine. Ce n'est qu'une fois le fichier entièrement écrit sur le disque qu'il est déplacé. Plus intéressant à lire à ce sujet dans les commentaires à http://php.net/manual/en/function.rename.php

Modifier pour répondre aux commentaires (aussi pour les commentaires):

/programming/7054844/is-rename-atomic contient d'autres références sur ce que vous devrez peut-être faire si vous utilisez plusieurs systèmes de fichiers.

Sur le verrou partagé pour la lecture, je ne sais pas pourquoi cela serait nécessaire car dans cette implémentation, il n'y a pas d'écriture directe dans le fichier. Le flock de PHP (qui est utilisé pour obtenir le verrou) est un peu peu fiable et peut être ignoré par d'autres processus. C'est pourquoi je suggère d'utiliser le renommage.

Le fichier de renommage devrait idéalement être nommé de manière unique pour le processus effectuant le changement de nom afin de s'assurer que 2 processus ne font pas la même chose. Mais cela n'empêche bien sûr pas d'éditer le même fichier par plus d'une personne à la fois. Mais au moins le fichier sera laissé intact (la dernière modification l'emporte).

Les étapes 3) et 4) deviendraient alors ceci:

$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
la source
Exactement ce que je voulais aussi proposer. Mais j'acquerrais également un verrou partagé pendant la lecture pour éviter le clobber des données.
d3L
Renommer est une opération atomique sur le même disque, pas sur des disques différents.
Xnoise
Pour vraiment garantir un nom de fichier temporaire unique, vous pouvez également utiliser lestempnam fonctions, qui créent atomiquement un fichier et renvoie le nom du fichier.
Matthijs Kooijman
1

Dans la documentation PHP pour file_put_contents (), vous pouvez trouver dans l' exemple # 2 l'utilisation de LOCK_EX , en mettant simplement:

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

Le LOCK_EX est une constante avec un nombre entier de valeur que peut être utilisé sur certaines fonctions dans une opération de bits .

Il existe également une fonction spécifique pour contrôler le verrouillage des fichiers: à la manière de flock () .

Augusto Pascutti
la source
Bien que cela soit intéressant et pourrait être utile dans certaines situations, lors de la lecture, de la modification et de la réécriture d'un fichier, le verrou doit être acquis avant de le lire et maintenu jusqu'à ce qu'il soit entièrement réécrit (sinon un autre processus peut lire une ancienne copie et le changer) de retour une fois votre processus terminé). Je ne crois pas que cela puisse être réalisé avec file_get/put_contents.
Jules
0

Un problème que vous n'avez pas mentionné et auquel vous devez également faire attention est les conditions de concurrence où deux instances de votre script s'exécutent presque en même temps, par exemple cet ordre d'occurrences:

  1. Instance de script 1: lit le fichier
  2. Instance de script 2: lit le fichier
  3. Instance de script 1: écrit les modifications dans le fichier
  4. Instance de script 2: remplace les modifications apportées à la première instance de script au fichier par ses propres modifications (car à ce stade, sa lecture est devenue obsolète).

Ainsi, lors de la mise à jour d'un fichier volumineux, vous devez verrouiller ce fichier avant de le lire et ne pas libérer le verrou tant que les écritures n'ont pas été effectuées. Dans cet exemple, je pense que la seconde instance de script se bloquera un peu pendant qu'elle attend son tour pour accéder au fichier, mais c'est mieux que les données perdues.

Thoracius Appotite
la source