PHP file_put_contents Bloqueo de archivos

9

El senario:

Tiene un archivo con una cadena (valor promedio de la oración) en cada línea. Por el bien de los argumentos, digamos que este archivo tiene un tamaño de 1Mb (miles de líneas).

Tiene una secuencia de comandos que lee el archivo, cambia algunas de las cadenas dentro del documento (no solo agrega sino que también elimina y modifica algunas líneas) y luego sobrescribe todos los datos con los nuevos datos.

Las preguntas:

  1. ¿El 'servidor' PHP, OS o httpd etc. ya tiene sistemas para detener problemas como este (lectura / escritura a mitad de una escritura)?

  2. Si es así, explique cómo funciona y proporcione ejemplos o enlaces a la documentación relevante.

  3. Si no, ¿hay cosas que puedo habilitar o configurar, como bloquear un archivo hasta que se complete una escritura y hacer que todas las otras lecturas y / o escrituras fallen hasta que el script anterior haya terminado de escribir?

Mis suposiciones y otra información:

  1. El servidor en cuestión ejecuta PHP y Apache o Lighttpd.

  2. Si un usuario llama al script y está a la mitad de la escritura en el archivo y otro usuario lee el archivo en ese momento exacto. El usuario que lo lea no obtendrá el documento completo, ya que aún no se ha escrito. (Si esta suposición es incorrecta, corríjame)

  3. Solo me preocupa la escritura y lectura de PHP en un archivo de texto, y en particular, las funciones "fopen" / "fwrite" y principalmente "file_put_contents". He examinado la documentación de "file_put_contents" pero no he encontrado el nivel de detalle o una buena explicación de lo que es o hace el indicador "LOCK_EX".

  4. El escenario es un ejemplo del peor de los casos en el que asumiría que es más probable que ocurran estos problemas, debido al gran tamaño del archivo y la forma en que se editan los datos. Quiero aprender más sobre estos temas y no quiero o necesito respuestas o comentarios como "use mysql" o "¿por qué está haciendo eso?" Porque no estoy haciendo eso, solo quiero aprender sobre la lectura / escritura de archivos con PHP y no parece estar buscando en los lugares / documentación correctos y sí, entiendo que PHP no es el lenguaje perfecto para trabajar con archivos de esta manera.

hozza
fuente
2
Puedo decirle por experiencia que leer y escribir en archivos grandes con PHP (1 MB no es realmente tan grande, pero aún así) puede ser complicado (y lento). Siempre puede bloquear el archivo, pero probablemente sería más fácil y seguro usar una base de datos.
NullUserException
Sé que sería mejor usar un DB. Lea la pregunta (último párrafo número 4)
hozza
2
Yo leí la pregunta; Estoy diciendo que no es una gran idea y que hay mejores alternativas.
NullUserException
2
file_put_contents()es solo una envoltura para el fopen()/fwrite()baile, LOCKEXhace lo mismo que si llamaras flock($handle, LOCKEX).
Yannis
2
@hozza Por eso publiqué un comentario, no una respuesta.
NullUserException

Respuestas:

4

1) No 3) No

Hay varios problemas con el enfoque sugerido original:

En primer lugar, algunos sistemas similares a UNIX como Linux pueden no tener implementado el soporte de bloqueo. El sistema operativo no bloquea los archivos de forma predeterminada. He visto que las llamadas al sistema son NOP (sin operación), pero eso fue hace unos años, por lo que debe verificar si otra instancia respeta el bloqueo establecido por su instancia de la aplicación. (es decir, 2 visitantes concurrentes). Si el bloqueo sigue sin implementarse [muy probablemente lo sea], el sistema operativo le permite sobrescribir ese archivo.

La lectura de archivos grandes línea por línea no es factible por razones de rendimiento. Sugiero usar file_get_contents () para cargar todo el archivo en la memoria y luego explotarlo () para obtener las líneas. Alternativamente, use fread () para leer el archivo en bloques. El objetivo es minimizar el número de llamadas de lectura.

En lo que respecta al bloqueo de archivos:

LOCK_EX significa un bloqueo exclusivo (generalmente para escritura). Solo un proceso puede contener un bloqueo exclusivo para un archivo determinado en un momento dado. LOCK_SH es un bloqueo compartido (generalmente para lectura), más de un proceso puede contener un bloqueo compartido para un archivo determinado en un momento dado. LOCK_UN desbloquea el archivo. El desbloqueo se realiza automáticamente en caso de que use file_get_contents () http://en.wikipedia.org/wiki/File_locking#In_Unix-like_systems

Solución elegante

PHP admite filtros de flujo de datos que están destinados a procesar datos en archivos o desde otras entradas. Es posible que desee crear uno de estos filtros correctamente utilizando la API estándar. http://php.net/manual/en/function.stream-filter-register.php http://php.net/manual/en/filters.php

Solución alternativa (en 3 pasos):

  1. Crea una cola. En lugar de procesar un nombre de archivo, use la base de datos u otro mecanismo para almacenar nombres de archivo únicos en algún lugar en espera / y procesados ​​en / procesados. De esta manera, nada se sobrescribe. La base de datos también será útil para almacenar información adicional, como metadatos, marcas de tiempo confiables, resultados de procesamiento y otros.

  2. Para archivos de hasta unos pocos MB, lea todo el archivo en la memoria y luego proceselo (file_get_contents () + explode () + foreach ())

  3. Para archivos más grandes, lea el archivo en bloques (es decir, 1024 Bytes) y procese + escriba en tiempo real cada bloque como lectura (cuidado con la última línea que no termina con \ n. Debe procesarse en el siguiente lote)


fuente
1
"He visto que las llamadas al sistema son NOP (sin operación) ..." ¿qué núcleo?
Massimo
1
"Leer archivos grandes línea por línea no es factible por razones de rendimiento. Sugiero usar file_get_contents () para cargar todo el archivo en la memoria ..." Esto no tiene sentido. Puedo decir: por razones de rendimiento, no lea archivos grandes en la memoria ... Lo que debe hacer depende de muchos otros factores.
Massimo
4

Sé que esto tiene años, pero en caso de que alguien se encuentre con esto. En mi humilde opinión, la forma de hacerlo es así:

1) Abra el archivo original (por ejemplo, original.txt) usando file_get_contents ('original.txt').

2) Realiza tus cambios / ediciones.

3) Use file_put_contents ('original.txt.tmp') y escríbalo en un archivo temporal original.txt.tmp.

4) Luego mueva el archivo tmp al archivo original, reemplazando el archivo original. Para esto usa rename ('original.txt.tmp', 'original.txt').

Ventajas: mientras el archivo se está procesando y escribiendo en el archivo no está bloqueado y otros aún pueden leer el contenido anterior. Al menos en Linux / Unix, el cambio de nombre de las cajas es una operación atómica. Cualquier interrupción durante la escritura del archivo no toca el archivo original. Solo una vez que el archivo se ha escrito completamente en el disco se mueve. Más interesante leer sobre esto en los comentarios a http://php.net/manual/en/function.rename.php

Editar para abordar comentarios (también para comentarios):

/programming/7054844/is-rename-atomic tiene más referencias a lo que podría necesitar hacer si está operando en sistemas de archivos.

En el bloqueo compartido para la lectura, no estoy seguro de por qué sería necesario, ya que en esta implementación no se escribe directamente en el archivo. El lote de PHP (que se usa para obtener el bloqueo) es un poco poco confiable y otros procesos pueden ignorarlo. Es por eso que sugiero usar el cambio de nombre.

Idealmente, el archivo de cambio de nombre debe denominarse de forma exclusiva al proceso que realiza el cambio de nombre para asegurarse de que no 2 procesos hagan lo mismo. Pero, por supuesto, esto no impide la edición del mismo archivo por más de una persona al mismo tiempo. Pero al menos el archivo se dejará intacto (la última edición gana).

Paso 3) y 4) se convertiría en esto:

$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
fuente
Exactamente lo que quería proponer también. Pero también adquiriría un bloqueo compartido mientras leía para evitar el bloqueo de datos.
d3L
Renombrar es una operación atómica en el mismo disco, no en discos diferentes.
Xnoise
Para garantizar realmente un nombre de archivo temporal único, también puede usar lastempnam funciones, que crean atómicamente un archivo y devuelven el nombre de archivo.
Matthijs Kooijman
1

En la documentación de PHP para file_put_contents () puede encontrar en el ejemplo # 2 el uso de LOCK_EX , simplemente:

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

El LOCK_EX es una constante con un número entero de valor que puede ser utilizado en algunas funciones en un bit a bit .

También hay una función específica para controlar el bloqueo de archivos: forma flock () .

Augusto Pascutti
fuente
Si bien esto es interesante y podría ser útil en algunas situaciones, al leer, modificar y reescribir un archivo, el bloqueo se debe adquirir antes de leerlo y mantenerlo hasta que se haya reescrito por completo (de lo contrario, otro proceso puede leer una copia antigua y cambiarlo volver después de que termine su proceso). No creo que esto se pueda lograr con file_get/put_contents.
Jules
0

Un problema que no mencionó y que también debe tener cuidado son las condiciones de carrera en las que se ejecutan dos instancias de su script casi al mismo tiempo, por ejemplo, este orden de ocurrencias:

  1. Instancia de script 1: lee el archivo
  2. Instancia de script 2: lee el archivo
  3. Instancia de script 1: escribe los cambios en el archivo
  4. Instancia de script 2: sobrescribe los cambios de la primera instancia de script al archivo con sus propios cambios (ya que en este punto su lectura se ha vuelto obsoleta).

Por lo tanto, al actualizar un archivo grande, debe LOCK_EX ese archivo antes de leerlo y no liberar el bloqueo hasta que se hayan realizado las escrituras. En este ejemplo, creo que hará que la segunda instancia del script se cuelgue un poco mientras espera su turno para acceder al archivo, pero esto es mejor que la pérdida de datos.

Appotite de Thoracius
fuente