PHP file_put_contents Блокировка файлов


9

Сенарио:

У вас есть файл со строкой (средняя стоимость предложения) в каждой строке. В качестве аргумента, скажем, этот файл имеет размер 1 МБ (тысячи строк).

У вас есть скрипт, который читает файл, изменяет некоторые строки в документе (не только добавляет, но и удаляет и изменяет некоторые строки), а затем перезаписывает все данные новыми данными.

Вопросы:

  1. У «серверного» PHP, ОС или httpd и т. Д. Уже есть системы, позволяющие остановить подобные проблемы (чтение / запись на полпути через запись)?

  2. Если да, объясните, как это работает, и приведите примеры или ссылки на соответствующую документацию.

  3. Если нет, есть ли какие-либо вещи, которые я могу включить или настроить, такие как блокировка файла до тех пор, пока запись не будет завершена, и все другие операции чтения и / или записи не будут выполнены, пока предыдущий сценарий не завершит запись?

Мои предположения и другая информация:

  1. Рассматриваемый сервер работает на PHP и Apache или Lighttpd.

  2. Если скрипт вызывается одним пользователем и находится в середине записи в файл, а другой пользователь читает файл именно в этот момент. Пользователь, который читает его, не получит полный документ, так как он еще не написан. (Если это предположение неверно, пожалуйста, поправьте меня)

  3. Я занимаюсь только написанием PHP и чтением в текстовый файл, в частности, функциями "fopen" / "fwrite" и в основном "file_put_contents". Я просмотрел документацию «file_put_contents», но не нашел уровня детализации или хорошего объяснения того, что флаг «LOCK_EX» делает или делает.

  4. Этот сценарий является примером сценария наихудшего случая, в котором я предполагаю, что эти проблемы более вероятны из-за большого размера файла и способа редактирования данных. Я хочу узнать больше об этих проблемах и не хочу или не нуждаюсь в ответах или комментариях, таких как «используйте mysql» или «почему вы это делаете», потому что я этого не делаю, я просто хочу узнать о чтении / записи файлов с PHP и, кажется, не ищет в нужных местах / документации, и да, я понимаю, что PHP не идеальный язык для работы с файлами таким образом.


2
По опыту могу сказать, что чтение и запись в большие файлы с помощью PHP (1 МБ на самом деле не так уж много, но все же) может быть сложным (и медленным). Вы всегда можете заблокировать файл, но, вероятно, было бы проще и безопаснее использовать базу данных.
NullUserException

Я знаю, что было бы лучше использовать БД. Пожалуйста, прочитайте вопрос (последний абзац № 4)
hozza

2
Я прочитал вопрос; Я говорю, что это не очень хорошая идея, и есть лучшие альтернативы.
NullUserException

2
file_put_contents()это просто обертка для fopen()/fwrite()танца, LOCKEXделает так же, как если бы вы позвонили flock($handle, LOCKEX).
Яннис

2
@hozza Вот почему я разместил комментарий, а не ответ.
NullUserException

Ответы:


4

1) нет 3) нет

Есть несколько проблем с оригинальным предложенным подходом:

Во-первых, некоторые UNIX-подобные системы, такие как Linux, могут не иметь реализованной поддержки блокировки. ОС не блокирует файлы по умолчанию. Я видел системные вызовы NOP (без операций), но это было несколько лет назад, поэтому вам нужно проверить, соблюдается ли блокировка, установленная вашим экземпляром приложения, другим экземпляром. (т.е. 2 одновременных посетителя). Если блокировка все еще не реализована [очень вероятно, что это так], ОС позволяет вам перезаписать этот файл.

Строковое чтение больших файлов не представляется возможным по соображениям производительности. Я предлагаю использовать file_get_contents (), чтобы загрузить весь файл в память, а затем взорвать его, чтобы получить строки. Или используйте fread () для чтения файла блоками. Цель состоит в том, чтобы минимизировать количество вызовов для чтения.

Что касается блокировки файлов:

LOCK_EX означает эксклюзивную блокировку (обычно для записи). Только один процесс может удерживать монопольную блокировку для данного файла в данный момент времени. LOCK_SH - это общая блокировка (обычно для чтения). Более одного процесса может удерживать общую блокировку для данного файла в данный момент времени. LOCK_UN разблокирует файл. Разблокировка выполняется автоматически, если вы используете file_get_contents () http://en.wikipedia.org/wiki/File_locking#In_Unix-like_systems

Элегантное решение

PHP поддерживает фильтры потока данных, которые предназначены для обработки данных в файлах или из других входных данных. Возможно, вы захотите создать один такой фильтр правильно, используя стандартный API. http://php.net/manual/en/function.stream-filter-register.php http://php.net/manual/en/filters.php

Альтернативное решение (в 3 этапа):

  1. Создать очередь. Вместо того, чтобы обрабатывать одно имя файла, используйте базу данных или другой механизм для хранения уникальных имен файлов где-то в ожидании / и обрабатывается в / обрабатывается. Таким образом, ничто не перезаписывается. База данных также будет полезна для хранения дополнительной информации, такой как метаданные, надежные метки времени, результаты обработки и другие.

  2. Для файлов размером до нескольких МБ прочитайте весь файл в память и затем обработайте его (file_get_contents () + explode () + foreach ())

  3. Для больших файлов читайте файл в блоках (т.е. 1024 байта) и обрабатывайте + записывайте в режиме реального времени каждый блок как чтение (осторожно с последней строкой, которая не заканчивается на \ n. Она должна быть обработана в следующем пакете)


1
«Я видел системные вызовы NOP (без операций) ...» какое ядро?
Массимо

1
«Чтение больших файлов построчно по соображениям производительности невозможно. Я предлагаю использовать file_get_contents () для загрузки всего файла в память ...» Это бессмысленно. Я могу сказать: по причинам производительности не читайте большие файлы в память ... Что делать, зависит от многих других факторов.
Массимо

4

Я знаю, что это очень давно, но на тот случай, если кто-то столкнется с этим. ИМХО, путь к этому таков:

1) Откройте исходный файл (например, original.txt), используя file_get_contents ('original.txt').

2) Внесите свои изменения / правки.

3) Используйте file_put_contents ('original.txt.tmp') и запишите его во временный файл original.txt.tmp.

4) Затем переместите файл tmp в исходный файл, заменив исходный файл. Для этого вы используете переименование («original.txt.tmp», «original.txt»).

Преимущества: пока файл обрабатывается и записывается в файл, он не блокируется, и другие могут по-прежнему читать старый контент. По крайней мере, в Linux / Unix блоки переименования являются атомарной операцией. Любые прерывания во время записи файла не затрагивают исходный файл. Только когда файл полностью записан на диск, он перемещается. Более интересно читать об этом в комментариях к http://php.net/manual/en/function.rename.php

Изменить для комментариев (тоже для комментариев):

/programming/7054844/is-rename-atomic содержит дополнительные ссылки на то, что вам может понадобиться, если вы работаете в файловых системах.

Что касается общей блокировки чтения, я не уверен, зачем это нужно, так как в этой реализации нет прямой записи в файл. Стая PHP (которая используется для получения блокировки) немного, но ненадежна и может игнорироваться другими процессами. Вот почему я предлагаю использовать переименование.

Файл переименования в идеале должен иметь уникальное имя для процесса, выполняющего переименование, чтобы убедиться, что 2 процесса не делают одно и то же. Но это, конечно, не мешает редактированию одного и того же файла более чем одним человеком одновременно. Но, по крайней мере, файл останется нетронутым (победит последнее редактирование).

Шаг 3) и 4) станет таким:

$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

Именно то, что я хотел предложить. Но я также хотел бы получить общую блокировку во время чтения, чтобы предотвратить клобер данных.
marco-a

Переименование - это атомарная операция на одном диске, а не на разных.
Xnoise

Для того, чтобы действительно гарантировать уникальное имя TempFile, вы также можете использовать теtempnam функции, которые атомарно создает файл и возвращает имя файла.
Маттис Коойман

1

В документации PHP для file_put_contents () вы можете найти в примере №2 использование для LOCK_EX , говоря просто:

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

LOCK_EX является константой с целочисленным значением , чем может быть использован на некоторых функциях в побитовом .

Для управления блокировкой файлов также существует специальная функция: flock () .


Хотя это интересно и может быть полезно в некоторых ситуациях, при чтении, изменении и перезаписи файла блокировка должна быть получена до того, как вы ее прочитаете, и будет поддерживаться до тех пор, пока она не будет полностью перезаписана (в противном случае другой процесс может прочитать старую копию и изменить ее). назад после завершения вашего процесса). Я не верю, что это может быть достигнуто с file_get/put_contents.
Жюль

0

Проблема, о которой вы не упомянули, с которой вам также следует быть осторожным, - это условия гонки, когда два экземпляра вашего сценария работают почти в одно и то же время, например, такой порядок событий:

  1. Экземпляр сценария 1: чтение файла
  2. Экземпляр сценария 2: чтение файла
  3. Экземпляр сценария 1: записывает изменения в файл
  4. Экземпляр сценария 2: переписывает изменения первого экземпляра сценария в файл с собственными изменениями (поскольку в этот момент его чтение устарело).

Поэтому при обновлении большого файла вам нужно LOCK_EX этот файл, прежде чем читать его, и не снимать блокировку до тех пор, пока не будут выполнены записи. Я полагаю, что в этом примере второй экземпляр сценария будет немного зависать, ожидая своей очереди для доступа к файлу, но это лучше, чем потерянные данные.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.