PHP file_put_contents Блокування файлів


9

Сенаріо:

У вас є файл із рядком (середнє значення речення) у кожному рядку. Для аргументів давайте скажемо, що цей файл має розмір 1 Мбіт (тисячі рядків).

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

Питання:

  1. У PHP, OS або 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 функції, які атомарному створює файл і повертає ім'я файлу.
Matthijs Kooijman

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.