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:
Czy PHP, system operacyjny lub httpd itd. „Serwer” ma już systemy umożliwiające zatrzymanie takich problemów (odczyt / zapis w połowie zapisu)?
Jeśli tak, proszę wyjaśnić, jak to działa, i podać przykłady lub linki do odpowiedniej dokumentacji.
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:
Na tym serwerze działa PHP i Apache lub Lighttpd.
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ć)
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”.
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.
źródło
file_put_contents()
to tylko opakowanie dofopen()/fwrite()
tańca,LOCKEX
robi to samo, jakbyś zadzwoniłflock($handle, LOCKEX)
.Odpowiedzi:
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):
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.
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 ())
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)
źródło
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:
źródło
tempnam
funkcji, która niepodzielnie tworzy plik i zwraca nazwę pliku.W dokumentacji PHP dla file_put_contents () można znaleźć w przykładzie # 2 użycie LOCK_EX , po prostu:
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 () .
źródło
file_get/put_contents
.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:
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.
źródło