Сенарио:
У вас есть файл со строкой (средняя стоимость предложения) в каждой строке. В качестве аргумента, скажем, этот файл имеет размер 1 МБ (тысячи строк).
У вас есть скрипт, который читает файл, изменяет некоторые строки в документе (не только добавляет, но и удаляет и изменяет некоторые строки), а затем перезаписывает все данные новыми данными.
Вопросы:
У «серверного» PHP, ОС или httpd и т. Д. Уже есть системы, позволяющие остановить подобные проблемы (чтение / запись на полпути через запись)?
Если да, объясните, как это работает, и приведите примеры или ссылки на соответствующую документацию.
Если нет, есть ли какие-либо вещи, которые я могу включить или настроить, такие как блокировка файла до тех пор, пока запись не будет завершена, и все другие операции чтения и / или записи не будут выполнены, пока предыдущий сценарий не завершит запись?
Мои предположения и другая информация:
Рассматриваемый сервер работает на PHP и Apache или Lighttpd.
Если скрипт вызывается одним пользователем и находится в середине записи в файл, а другой пользователь читает файл именно в этот момент. Пользователь, который читает его, не получит полный документ, так как он еще не написан. (Если это предположение неверно, пожалуйста, поправьте меня)
Я занимаюсь только написанием PHP и чтением в текстовый файл, в частности, функциями "fopen" / "fwrite" и в основном "file_put_contents". Я просмотрел документацию «file_put_contents», но не нашел уровня детализации или хорошего объяснения того, что флаг «LOCK_EX» делает или делает.
Этот сценарий является примером сценария наихудшего случая, в котором я предполагаю, что эти проблемы более вероятны из-за большого размера файла и способа редактирования данных. Я хочу узнать больше об этих проблемах и не хочу или не нуждаюсь в ответах или комментариях, таких как «используйте mysql» или «почему вы это делаете», потому что я этого не делаю, я просто хочу узнать о чтении / записи файлов с PHP и, кажется, не ищет в нужных местах / документации, и да, я понимаю, что PHP не идеальный язык для работы с файлами таким образом.
источник
file_put_contents()
это просто обертка дляfopen()/fwrite()
танца,LOCKEX
делает так же, как если бы вы позвонилиflock($handle, LOCKEX)
.Ответы:
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 этапа):
Создать очередь. Вместо того, чтобы обрабатывать одно имя файла, используйте базу данных или другой механизм для хранения уникальных имен файлов где-то в ожидании / и обрабатывается в / обрабатывается. Таким образом, ничто не перезаписывается. База данных также будет полезна для хранения дополнительной информации, такой как метаданные, надежные метки времени, результаты обработки и другие.
Для файлов размером до нескольких МБ прочитайте весь файл в память и затем обработайте его (file_get_contents () + explode () + foreach ())
Для больших файлов читайте файл в блоках (т.е. 1024 байта) и обрабатывайте + записывайте в режиме реального времени каждый блок как чтение (осторожно с последней строкой, которая не заканчивается на \ n. Она должна быть обработана в следующем пакете)
источник
Я знаю, что это очень давно, но на тот случай, если кто-то столкнется с этим. ИМХО, путь к этому таков:
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) станет таким:
источник
tempnam
функции, которые атомарно создает файл и возвращает имя файла.В документации PHP для file_put_contents () вы можете найти в примере №2 использование для LOCK_EX , говоря просто:
LOCK_EX является константой с целочисленным значением , чем может быть использован на некоторых функциях в побитовом .
Для управления блокировкой файлов также существует специальная функция: flock () .
источник
file_get/put_contents
.Проблема, о которой вы не упомянули, с которой вам также следует быть осторожным, - это условия гонки, когда два экземпляра вашего сценария работают почти в одно и то же время, например, такой порядок событий:
Поэтому при обновлении большого файла вам нужно LOCK_EX этот файл, прежде чем читать его, и не снимать блокировку до тех пор, пока не будут выполнены записи. Я полагаю, что в этом примере второй экземпляр сценария будет немного зависать, ожидая своей очереди для доступа к файлу, но это лучше, чем потерянные данные.
источник