PHP file_put_contents Penguncian File

9

Senario:

Anda memiliki file dengan string (nilai kalimat rata-rata) di setiap baris. Demi argumen, katakanlah file ini berukuran 1 MB (ribuan baris).

Anda memiliki skrip yang membaca file, mengubah beberapa string dalam dokumen (tidak hanya menambahkan tetapi juga menghapus dan memodifikasi beberapa baris) dan kemudian menimpa semua data dengan data baru.

Pertanyaan-pertanyaan:

  1. Apakah 'server' PHP, OS atau httpd dll. Sudah memiliki sistem untuk menghentikan masalah seperti ini (membaca / menulis setengah jalan melalui penulisan)?

  2. Jika ya, tolong jelaskan cara kerjanya dan berikan contoh atau tautan ke dokumentasi yang relevan.

  3. Jika tidak, apakah ada hal-hal yang dapat saya aktifkan atau set-up, seperti mengunci file sampai penulisan selesai dan membuat semua bacaan lainnya dan / atau penulisan gagal sampai skrip sebelumnya selesai menulis?

Asumsi Saya dan Informasi Lainnya:

  1. Server yang dimaksud menjalankan PHP dan Apache atau Lighttpd.

  2. Jika skrip dipanggil oleh satu pengguna dan setengah jalan menulis ke file dan pengguna lain membaca file pada saat yang tepat. Pengguna yang membacanya tidak akan mendapatkan dokumen lengkap, karena belum ditulis. (Jika asumsi ini salah, mohon koreksi saya)

  3. Saya hanya peduli dengan penulisan dan pembacaan PHP ke file teks, dan khususnya, fungsi "fopen" / "fwrite" dan terutama "file_put_contents". Saya telah melihat dokumentasi "file_put_contents" tetapi belum menemukan tingkat detail atau penjelasan yang baik tentang apa bendera atau "LOCK_EX" itu.

  4. Skenario adalah contoh dari skenario terburuk di mana saya akan menganggap masalah ini lebih mungkin terjadi, karena ukuran file yang besar dan cara data diedit. Saya ingin mempelajari lebih lanjut tentang masalah ini dan tidak ingin atau memerlukan jawaban atau komentar seperti "gunakan mysql" atau "mengapa Anda melakukan itu" karena saya tidak melakukan itu, saya hanya ingin belajar tentang membaca / menulis file dengan PHP dan sepertinya tidak mencari di tempat yang tepat / dokumentasi dan ya saya mengerti PHP bukan bahasa yang sempurna untuk bekerja dengan file dengan cara ini.

hozza
sumber
2
Saya dapat memberitahu Anda dari pengalaman bahwa membaca dan menulis ke file besar dengan PHP (1 MB tidak terlalu besar, tapi tetap saja) bisa rumit (dan lambat). Anda selalu dapat mengunci file, tetapi mungkin akan lebih mudah dan lebih aman hanya dengan menggunakan database.
NullUserException
Saya tahu akan lebih baik menggunakan DB. Silakan baca pertanyaan (paragraf terakhir nomor 4)
hozza
2
Saya memang membaca pertanyaan; Saya mengatakan itu bukan ide bagus dan ada alternatif yang lebih baik.
NullUserException
2
file_put_contents()hanyalah bungkus untuk fopen()/fwrite()tarian, LOCKEXmelakukan hal yang sama seperti jika Anda akan menelepon flock($handle, LOCKEX).
yannis
2
@hozza Itu sebabnya saya mengirim komentar, bukan jawaban.
NullUserException

Jawaban:

4

1) Tidak 3) Tidak

Ada beberapa masalah dengan pendekatan yang disarankan asli:

Pertama, beberapa sistem mirip UNIX seperti Linux mungkin tidak memiliki dukungan penguncian yang diterapkan. OS tidak mengunci file secara default. Saya telah melihat syscalls menjadi NOP (tidak ada operasi), tapi itu beberapa tahun yang lalu, jadi Anda perlu memverifikasi apakah kunci yang ditetapkan oleh instance aplikasi Anda dihormati oleh instance lain. (yaitu 2 pengunjung bersamaan). Jika penguncian masih diimplementasikan [sangat mungkin itu], OS memungkinkan Anda menimpa file itu.

Membaca file besar baris demi baris tidak layak karena alasan kinerja. Saya sarankan menggunakan file_get_contents () untuk memuat seluruh file ke dalam memori dan kemudian meledak () untuk mendapatkan baris. Atau, gunakan fread () untuk membaca file dalam blok. Tujuannya adalah untuk meminimalkan jumlah panggilan yang dibaca.

Dalam hal penguncian file:

LOCK_EX berarti kunci eksklusif (biasanya untuk menulis). Hanya satu proses yang dapat menahan kunci eksklusif untuk file yang diberikan pada waktu tertentu. LOCK_SH adalah kunci bersama (biasanya untuk membaca), Lebih dari satu proses dapat menahan kunci bersama untuk file yang diberikan pada waktu tertentu. LOCK_UN membuka kunci file. Membuka kunci dilakukan secara otomatis jika Anda menggunakan file_get_contents () http://en.wikipedia.org/wiki/File_locking#In_Unix-like_systems

Solusi elegan

PHP mendukung filter aliran data yang dimaksudkan untuk memproses data dalam file atau dari input lain. Anda mungkin ingin membuat satu filter dengan benar menggunakan API standar. http://php.net/manual/en/function.stream-filter-register.php http://php.net/manual/en/filters.php

Solusi alternatif (dalam 3 langkah):

  1. Buat antrian. Alih-alih memproses satu nama file, gunakan database atau mekanisme lain untuk menyimpan nama file unik di suatu tempat dalam proses / sedang diproses dalam / diproses. Dengan cara ini tidak ada yang ditimpa. Basis data juga akan berguna untuk menyimpan informasi tambahan, seperti metadata, cap waktu yang andal, hasil pemrosesan, dan lainnya.

  2. Untuk file hingga beberapa MB, baca seluruh file ke dalam memori dan kemudian proseskan (file_get_contents () + explode () + foreach ())

  3. Untuk file yang lebih besar baca file dalam blok (yaitu 1024 Bytes) dan proses + tulis secara real-time setiap blok sebagai bacaan (hati-hati tentang baris terakhir yang tidak berakhir dengan \ n. Ini perlu diproses dalam batch berikutnya)


sumber
1
"Saya telah melihat syscalls sebagai NOP (no-operation) ..." kernel mana?
Massimo
1
"Membaca file besar baris demi baris tidak layak karena alasan kinerja. Saya sarankan menggunakan file_get_contents () untuk memuat seluruh file ke dalam memori ..." Ini tidak masuk akal. Saya dapat mengatakan: untuk alasan kinerja tidak membaca file besar ke dalam memori ... Apa yang harus dilakukan tergantung pada banyak faktor lain.
Massimo
4

Saya tahu ini sudah tua, tetapi kalau-kalau ada yang mengalami ini. IMHO cara untuk melakukannya adalah seperti ini:

1) Buka file asli (mis. Original.txt) menggunakan file_get_contents ('original.txt').

2) Buat perubahan / pengeditan Anda.

3) Gunakan file_put_contents ('original.txt.tmp') dan tuliskan ke file temp original.txt.tmp.

4) Kemudian pindahkan file tmp ke file asli, ganti file asli. Untuk ini, Anda menggunakan rename ('original.txt.tmp', 'original.txt').

Keuntungan: Saat file sedang diproses dan ditulis ke file tidak terkunci dan yang lain masih bisa membaca konten lama. Setidaknya di Linux / kotak Unix rename adalah operasi atom. Gangguan apa pun selama penulisan file tidak menyentuh file asli. Hanya setelah file telah sepenuhnya ditulis ke disk, file dipindahkan. Lebih menarik baca ini di komentar ke http://php.net/manual/en/function.rename.php

Edit ke alamat komitmen (juga untuk komentar):

/programming/7054844/is-rename-atomic memiliki referensi lebih lanjut tentang apa yang mungkin perlu Anda lakukan jika Anda beroperasi di seluruh sistem file.

Pada kunci bersama untuk membaca saya tidak yakin mengapa itu akan diperlukan karena dalam implementasi ini tidak ada tulisan ke file secara langsung. Kawanan PHP (yang digunakan untuk mendapatkan kunci) sedikit tetapi tidak dapat diandalkan dan dapat diabaikan oleh proses lain. Itulah mengapa saya menyarankan untuk menggunakan rename.

File rename idealnya dinamai secara unik untuk proses melakukan penggantian nama sehingga untuk memastikan tidak 2 proses melakukan hal yang sama. Tetapi ini tentu saja tidak mencegah pengeditan file yang sama oleh lebih dari satu orang pada saat yang bersamaan. Tapi setidaknya file akan dibiarkan utuh (edit terakhir menang).

Langkah 3) & 4) akan menjadi ini:

$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
Dom
sumber
Persis apa yang ingin saya usulkan juga. Tapi saya juga akan mendapatkan kunci bersama saat membaca untuk mencegah data clobber.
d3L
Ganti nama adalah operasi atom pada disk yang sama, bukan pada disk yang berbeda.
Kenalkan
Untuk benar-benar menjamin nama tempfile yang unik, Anda juga dapat menggunakan dengantempnam fungsi, yang atom menciptakan sebuah file dan kembali nama file.
Matthijs Kooijman
1

Dalam dokumentasi PHP untuk file_put_contents () Anda dapat menemukan dalam contoh # 2 penggunaan untuk LOCK_EX , dengan kata lain:

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

The LOCK_EX adalah konstan dengan bilangan bulat nilai selain dapat digunakan pada beberapa fungsi dalam bitwise .

Ada juga fungsi khusus untuk mengontrol penguncian file: cara flock () .

Augusto Pascutti
sumber
Meskipun ini menarik dan dapat berguna dalam beberapa situasi, saat membaca, memodifikasi, dan menulis ulang file, kunci harus diperoleh sebelum Anda membacanya dan dipelihara hingga sepenuhnya ditulis ulang (jika tidak, proses lain dapat membaca salinan lama dan mengubahnya kembali setelah proses Anda selesai). Saya tidak percaya ini bisa dicapai dengan file_get/put_contents.
Jules
0

Masalah yang tidak Anda sebutkan adalah kondisi lomba di mana dua contoh skrip Anda berjalan pada waktu yang hampir bersamaan, misalnya urutan kejadian ini:

  1. Contoh skrip 1: Membaca file
  2. Contoh skrip 2: Membaca file
  3. Contoh skrip 1: Menulis perubahan ke file
  4. Contoh skrip 2: Menimpa perubahan instance skrip pertama ke file dengan perubahannya sendiri (karena saat ini bacaannya telah menjadi basi).

Jadi ketika memperbarui file besar, Anda perlu LOCK_EX file itu sebelum Anda membacanya dan tidak melepaskan kunci sampai menulis telah dibuat. Dalam contoh ini saya percaya bahwa akan menyebabkan contoh skrip kedua hang sebentar sementara menunggu gilirannya untuk mengakses file, tetapi ini lebih baik daripada kehilangan data.

Thoracius Appotite
sumber