PHP file_put_contents Blokowanie plików


9

The Senario:

W każdym wierszu znajduje się plik z ciągiem znaków (średnia wartość zdania). Dla argumentów, powiedzmy, że ten plik ma rozmiar 1 Mb (tysiące linii).

Masz skrypt, który odczytuje plik, zmienia niektóre ciągi w dokumencie (nie tylko dodaje, ale także usuwa i modyfikuje niektóre wiersze), a następnie zastępuje wszystkie dane nowymi danymi.

Pytania:

  1. Czy PHP, system operacyjny lub httpd itd. „Serwer” ma już systemy umożliwiające zatrzymanie takich problemów (odczyt / zapis w połowie zapisu)?

  2. Jeśli tak, proszę wyjaśnić, jak to działa, i podać przykłady lub linki do odpowiedniej dokumentacji.

  3. Jeśli nie, czy są rzeczy, które mogę włączyć lub skonfigurować, takie jak blokowanie pliku do momentu zakończenia zapisu i wykonywanie wszystkich innych odczytów i / lub zapisów, dopóki poprzedni skrypt nie zakończy pisania?

Moje założenia i inne informacje:

  1. Na tym serwerze działa PHP i Apache lub Lighttpd.

  2. Jeśli skrypt jest wywoływany przez jednego użytkownika i znajduje się w połowie zapisu do pliku, a inny użytkownik czyta plik w tym samym momencie. Użytkownik, który ją czyta, nie otrzyma pełnego dokumentu, ponieważ nie został jeszcze napisany. (Jeśli to założenie jest błędne, proszę mnie poprawić)

  3. Zajmuję się tylko pisaniem i odczytywaniem PHP do pliku tekstowego, a w szczególności funkcjami „fopen” / „fwrite” i głównie „file_put_contents”. Przejrzałem dokumentację „file_put_contents”, ale nie znalazłem poziomu szczegółowości ani dobrego wyjaśnienia, co oznacza flaga „LOCK_EX”.

  4. Scenariusz jest przykładem najgorszego scenariusza, w którym przypuszczam, że te problemy są bardziej prawdopodobne ze względu na duży rozmiar pliku i sposób edycji danych. Chcę dowiedzieć się więcej o tych problemach i nie chcę ani nie potrzebuję odpowiedzi ani komentarzy, takich jak „użyj mysql” lub „dlaczego to robisz”, ponieważ tego nie robię, chcę tylko dowiedzieć się o czytaniu / zapisywaniu plików z PHP i nie wyglądam we właściwych miejscach / dokumentacji i tak, rozumiem, że PHP nie jest idealnym językiem do pracy z plikami w ten sposób.


2
Z doświadczenia mogę powiedzieć, że czytanie i pisanie dużych plików za pomocą PHP (1 MB nie jest tak duże, ale nadal) może być trudne (i powolne). Zawsze możesz zablokować plik, ale prawdopodobnie korzystanie z bazy danych byłoby łatwiejsze i bezpieczniejsze.
NullUserException

Wiem, że lepiej byłoby użyć DB. Przeczytaj pytanie (ostatni akapit nr 4)
hozza

2
Przeczytałem pytanie; Mówię, że to nie jest świetny pomysł i są lepsze alternatywy.
NullUserException

2
file_put_contents()to tylko opakowanie do fopen()/fwrite()tańca, LOCKEXrobi to samo, jakbyś zadzwonił flock($handle, LOCKEX).
yannis

2
@hozza Dlatego opublikowałem komentarz, a nie odpowiedź.
NullUserException

Odpowiedzi:


4

1) Nie 3) Nie

Istnieje kilka problemów z oryginalnym sugerowanym podejściem:

Po pierwsze, niektóre systemy uniksopodobne, takie jak Linux, mogą nie mieć zaimplementowanej obsługi blokowania. System operacyjny domyślnie nie blokuje plików. Widziałem, że syscalls to NOP (brak działania), ale to kilka lat temu, więc musisz sprawdzić, czy blokada ustawiona przez twoją instancję aplikacji jest respektowana przez inną instancję. (tj. 2 jednocześnie odwiedzających). Jeśli blokowanie jest nadal niezaimplementowane [najprawdopodobniej tak jest], system operacyjny pozwala na zastąpienie tego pliku.

Czytanie dużych plików wiersz po wierszu nie jest możliwe ze względu na wydajność. Sugeruję użycie file_get_contents (), aby załadować cały plik do pamięci, a następnie rozbić go (), aby uzyskać linie. Alternatywnie, użyj fread (), aby odczytać plik w blokach. Celem jest zminimalizowanie liczby odczytanych połączeń.

W odniesieniu do blokowania plików:

LOCK_EX oznacza blokadę wyłączną (zazwyczaj do pisania). Tylko jeden proces może posiadać blokadę wyłączności dla danego pliku w danym momencie. LOCK_SH jest blokadą współdzieloną (zwykle do odczytu), więcej niż jeden proces może posiadać blokadę współdzieloną dla danego pliku w danym momencie. LOCK_UN odblokowuje plik. Odblokowanie odbywa się automatycznie w przypadku użycia file_get_contents () http://en.wikipedia.org/wiki/File_locking#In_Unix-like_systems

Eleganckie rozwiązanie

PHP obsługuje filtry strumienia danych, które są przeznaczone do przetwarzania danych w plikach lub z innych danych wejściowych. Możesz chcieć utworzyć jeden taki filtr poprawnie przy użyciu standardowego interfejsu API. http://php.net/manual/en/function.stream-filter-register.php http://php.net/manual/en/filters.php

Alternatywne rozwiązanie (w 3 krokach):

  1. Utwórz kolejkę. Zamiast przetwarzać jedną nazwę pliku, użyj bazy danych lub innego mechanizmu do przechowywania unikalnych nazw plików gdzieś w oczekiwaniu / i przetworzonych w / przetworzonych. W ten sposób nic nie zostanie zastąpione. Baza danych przyda się również do przechowywania dodatkowych informacji, takich jak metadane, wiarygodne znaczniki czasu, wyniki przetwarzania i inne.

  2. W przypadku plików o wielkości do kilku MB przeczytaj cały plik do pamięci, a następnie przetworz go (file_get_contents () + explode () + foreach ())

  3. W przypadku większych plików odczytaj plik w blokach (tj. 1024 bajtów) i przetwarzaj + pisz w czasie rzeczywistym każdy blok jako odczyt (uważaj na ostatni wiersz, który nie kończy się na \ n. Musi zostać przetworzony w następnej partii)


1
„Widziałem, że syscalls to NOP (brak operacji) ...” które jądro?
Massimo,

1
„Czytanie dużych plików wiersz po wierszu nie jest możliwe ze względu na wydajność. Sugeruję użycie file_get_contents () do załadowania całego pliku do pamięci ...” To nie ma sensu. Mogę powiedzieć: ze względu na wydajność nie wczytuj dużych plików do pamięci ... To, co należy zrobić, zależy od wielu innych czynników.
Massimo,

4

Wiem, że to ma wieki, ale na wypadek, gdyby ktoś na to wpadł. IMHO sposób na zrobienie tego jest następujący:

1) Otwórz oryginalny plik (np. Original.txt), używając file_get_contents ('original.txt').

2) Dokonaj zmian / edycji.

3) Użyj file_put_contents ('original.txt.tmp') i zapisz go w pliku tymczasowym original.txt.tmp.

4) Następnie przenieś plik tmp do oryginalnego pliku, zastępując oryginalny plik. W tym celu używasz zmiany nazwy („original.txt.tmp”, „original.txt”).

Zalety: Podczas przetwarzania i zapisywania pliku nie jest on zablokowany, a inni mogą nadal czytać starą treść. Przynajmniej w przypadku systemów Linux / Unix zmiana nazwy jest operacją atomową. Przerwy w zapisywaniu pliku nie dotykają oryginalnego pliku. Przenoszony jest dopiero po pełnym zapisaniu pliku na dysku. Bardziej interesujące przeczytanie tego w komentarzach do http://php.net/manual/en/function.rename.php

Edytuj, aby adresować zamówienia (również w celu komentarza):

/programming/7054844/is-rename-atomic zawiera dalsze odniesienia do tego, co możesz zrobić, jeśli działasz w różnych systemach plików.

Na wspólnej blokadzie odczytu nie jestem pewien, dlaczego byłoby to konieczne, ponieważ w tej implementacji nie ma bezpośredniego zapisu do pliku. Stado PHP (które służy do uzyskania blokady) jest trochę, ale zawodne i może zostać zignorowane przez inne procesy. Właśnie dlatego sugeruję zmianę nazwy.

Plik zmiany nazwy powinien idealnie mieć unikalną nazwę dla procesu dokonującego zmiany nazwy, aby mieć pewność, że nie 2 procesy zrobią to samo. Ale to oczywiście nie uniemożliwia edycji tego samego pliku przez więcej niż jedną osobę w tym samym czasie. Ale przynajmniej plik pozostanie nienaruszony (ostatnia edycja wygrywa).

Krok 3) i 4) wyglądałby następująco:

$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

Dokładnie to, co chciałem również zaproponować. Ale zyskałbym również wspólną blokadę podczas czytania, aby zapobiec zatarciu danych.
marco-a

Zmień nazwę to operacja atomowa na tym samym dysku, a nie na różnych dyskach.
Xnoise

Aby naprawdę gwarantuje unikalną nazwę pliku tymczasowego, można również skorzystać ztempnam funkcji, która niepodzielnie tworzy plik i zwraca nazwę pliku.
Matthijs Kooijman

1

W dokumentacji PHP dla file_put_contents () można znaleźć w przykładzie # 2 użycie LOCK_EX , po prostu:

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

LOCK_EX jest stałą o całkowitej wartości, niż może być stosowany na pewnych funkcji w bitowego .

Istnieją również specyficzne funkcje do kontrolowania blokowania plików: sposób flock () .


Chociaż jest to interesujące i może być przydatne w niektórych sytuacjach, podczas odczytywania, modyfikowania i przepisywania pliku, blokada powinna zostać uzyskana przed odczytaniem i utrzymywana aż do całkowitego przepisania (w innym przypadku inny proces może odczytać starą kopię i zmienić ją z powrotem po zakończeniu procesu). Nie wierzę, że można to osiągnąć file_get/put_contents.
Jules

0

Problemem, o którym nie wspomniałeś, że musisz również uważać, są warunki wyścigu, w których dwa wystąpienia skryptu działają prawie w tym samym czasie, na przykład ta kolejność występowania:

  1. Instancja skryptu 1: odczytuje plik
  2. Skrypt wystąpienie 2: czyta plik
  3. Skrypt wystąpienie 1: zapisuje zmiany w pliku
  4. Instancja skryptu 2: Zastępuje zmiany pierwszej instancji skryptu do pliku własnymi zmianami (ponieważ w tym momencie jego odczyt stał się nieaktualny).

Dlatego podczas aktualizacji dużego pliku musisz LOCK_EX ten plik przed jego odczytaniem i nie zwalniać blokady, dopóki nie zostaną zapisane. W tym przykładzie uważam, że spowoduje to, że druga instancja skryptu zawiesi się na chwilę, czekając na swoją kolej, aby uzyskać dostęp do pliku, ale jest to lepsze niż utrata danych.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.