PHP file_put_contents Dosya Kilitleme


9

Senaryo:

Her satırda dize (ortalama cümle değeri) olan bir dosyanız var. Sake argümanları için bu dosyanın boyutu 1Mb (binlerce satır) diyelim.

Dosyayı okuyan, belgedeki bazı dizeleri değiştiren bir komut dosyanız var (yalnızca ekleme değil aynı zamanda bazı satırları kaldırma ve değiştirme) ve ardından tüm verilerin üzerine yeni veriler yazıyor.

Sorular:

  1. 'Sunucu' PHP, OS veya httpd vb. Bu tür sorunları durdurmak için halihazırda sistemlere sahip mi?

  2. Varsa, lütfen nasıl çalıştığını açıklayın ve ilgili belgelere örnekler veya bağlantılar verin.

  3. Değilse, bir yazma işlemi tamamlanana kadar bir dosyayı kilitlemek ve diğer tüm okuma ve / veya yazma işlemlerini önceki komut dosyasının yazmayı bitirmesine kadar başarısız hale getirmek gibi etkinleştirebileceğim veya ayarlayabileceğim şeyler var mı?

Varsayımlarım ve Diğer Bilgilerim:

  1. Söz konusu sunucu PHP ve Apache veya Lighttpd çalıştırıyor.

  2. Komut dosyası bir kullanıcı tarafından çağrılırsa ve dosyaya yazmanın yarısındaysa ve başka bir kullanıcı dosyayı tam olarak o anda okursa. Belgeyi okuyan kullanıcı henüz yazılmadığı için belgenin tamamını alamaz. (Bu varsayım yanlışsa lütfen beni düzeltin)

  3. Ben sadece bir metin dosyasına yazma ve okuma PHP ile ilgileniyorum, ve özellikle, fonksiyonlar "fopen" / "fwrite" ve özellikle "file_put_contents". Ben "file_put_contents" belgelerine baktım ama ayrıntı düzeyini veya "LOCK_EX" bayrağının ne veya ne olduğunu iyi bir açıklama bulamadık.

  4. Senaryo, dosyanın büyük boyutu ve verilerin düzenleme şekli nedeniyle bu sorunların oluşma olasılığının daha yüksek olduğunu düşündüğüm en kötü durum senaryosunun bir örneğidir. Bu konular hakkında daha fazla bilgi edinmek istiyorum ve "mysql kullan" veya "bunu neden yapıyorsunuz" gibi cevaplar veya yorumlara ihtiyaç duymuyorum veya buna ihtiyacım yok çünkü bunu yapmıyorum, sadece dosya okuma / yazma hakkında bilgi edinmek istiyorum PHP ile ve doğru yerlere / belgelere bakmak gibi görünmüyor ve evet ben PHP bu şekilde dosyaları ile çalışmak için mükemmel bir dil olmadığını anlıyorum.


2
PHP ile büyük dosyalara okuma ve yazma (1 MB gerçekten o kadar büyük değil, ama yine de) zor (ve yavaş) olabileceğini deneyimlerinden söyleyebilirim. Dosyayı her zaman kilitleyebilirsiniz , ancak sadece bir veritabanı kullanmak daha kolay ve daha güvenli olacaktır.
NullUserException

Bir DB kullanmak daha iyi olacağını biliyorum. Lütfen soruyu okuyun (son paragraf numarası 4)
hozza

2
Soruyu okudum; Harika bir fikir olmadığını ve daha iyi alternatifler olduğunu söylüyorum.
NullUserException

2
file_put_contents()sadece fopen()/fwrite()dans için bir sarıcı , LOCKEXaradığınız gibi yapar flock($handle, LOCKEX).
yannis

2
@hozza Bu yüzden bir cevap değil, bir yorum gönderdim.
NullUserException

Yanıtlar:


4

1) Hayır 3) Hayır

Önerilen orijinal yaklaşımla ilgili birkaç sorun vardır:

İlk olarak, Linux gibi bazı UNIX benzeri sistemlerde kilitleme desteği uygulanmamış olabilir. İşletim sistemi varsayılan olarak dosyaları kilitlemez. Sistem çağrılarının NOP (işlem dışı) olduğunu gördüm, ancak bu birkaç yıl önce, bu yüzden uygulama örneğiniz tarafından ayarlanan bir kilidin başka bir örnek tarafından saygı gösterip göstermediğini doğrulamanız gerekiyor. (yani 2 eşzamanlı ziyaretçi). Kilitleme hala uygulanmadıysa [büyük olasılıkla], işletim sistemi bu dosyanın üzerine yazmanıza izin verir.

Performans nedeniyle büyük dosyaları satır satır okumak mümkün değildir. Tüm dosyayı belleğe yüklemek için file_get_contents () kullanmanızı ve ardından satırları almak için patlatmayı () öneririm. Alternatif olarak, dosyayı bloklar halinde okumak için fread () yöntemini kullanın. Amaç, okunan çağrı sayısını en aza indirmektir.

Dosya kilitleme ile ilgili olarak:

LOCK_EX özel bir kilit anlamına gelir (genellikle yazma için). Belirli bir dosya için belirli bir zamanda yalnızca bir işlem özel bir kilit tutabilir. LOCK_SH paylaşılan bir kilittir (genellikle okumak için), Birden fazla işlem belirli bir dosya için belirli bir zamanda paylaşılan bir kilit tutabilir. LOCK_UN dosyanın kilidini açar. Kilit açma, file_get_contents () kullanmanız durumunda otomatik olarak yapılır http://en.wikipedia.org/wiki/File_locking#In_Unix-like_systems

Zarif çözüm

PHP, dosyalardaki veya diğer girdilerdeki verileri işlemek için tasarlanmış veri akışı filtrelerini destekler. Standart API'yı kullanarak böyle bir filtre düzgün bir şekilde oluşturmak isteyebilirsiniz. http://php.net/manual/en/function.stream-filter-register.php http://php.net/manual/en/filters.php

Alternatif çözüm (3 adımda):

  1. Bir kuyruk oluşturun. Bir dosya adını işlemek yerine, benzersiz dosya adlarını bekleyen / ve işlenen / işlenen bir yerde saklamak için veritabanını veya başka bir mekanizmayı kullanın. Bu şekilde hiçbir şeyin üzerine yazılmaz. Veritabanı meta veriler, güvenilir zaman damgaları, işlem sonuçları ve diğer bilgiler gibi ek bilgileri depolamak için de yararlı olacaktır.

  2. Birkaç MB'ye kadar olan dosyalar için, tüm dosyayı belleğe okuyun ve ardından işleyin (file_get_contents () + explode () + foreach ())

  3. Daha büyük dosyalar için dosyayı bloklar halinde okuyun (yani 1024 Bayt) ve her blokta okuma olarak gerçek zamanlı olarak işlem + yazma (\ n ile bitmeyen son satır hakkında dikkatli olun. Bir sonraki toplu işte işlenmesi gerekir)


1
"Sistem çağrılarının NOP (işlemsiz) olduğunu gördüm ..." hangi çekirdek?
Massimo

1
"Büyük dosyaları satır satır okumak performans nedenleriyle mümkün değildir. Tüm dosyayı belleğe yüklemek için file_get_contents () yöntemini kullanmanızı öneririm ..." Bu bir anlamsızlık. Şunu söyleyebilirim: performans nedenleriyle büyük dosyaları belleğe okumayın ... Ne yapmalı, diğer birçok faktöre bağlıdır.
Massimo

4

Bunun çok eski olduğunu biliyorum, ama birisi bununla karşılaşırsa. IMHO bu şekilde devam etmenin yolu şöyledir:

1) file_get_contents ('original.txt') kullanarak orijinal dosyayı (örn. Original.txt) açın.

2) Değişikliklerinizi / düzenlemelerinizi yapın.

3) file_put_contents ('original.txt.tmp') kullanın ve original.txt.tmp geçici dosyasına yazın.

4) Ardından, orijinal dosyayı değiştirerek tmp dosyasını orijinal dosyaya taşıyın. Bunun için rename ('original.txt.tmp', 'original.txt') kullanırsınız.

Avantajları: Dosya işlenirken ve dosyaya yazılırken kilitlenmez ve diğerleri eski içeriği okuyabilir. En azından Linux / Unix kutularında yeniden adlandırma atomik bir işlemdir. Dosya yazma sırasında herhangi bir kesinti orijinal dosyaya dokunmaz. Yalnızca dosya diske tamamen yazıldıktan sonra taşınır. Http://php.net/manual/en/function.rename.php adresindeki yorumlarda bu konuda daha ilginç bir okuma

Açıklamaları adreslemek için düzenle (yorum için de):

/programming/7054844/is-rename-atomic , dosya sistemlerinde çalışıyorsanız yapmanız gerekebilecek başka referanslara da sahiptir.

Paylaşım için paylaşılan kilit üzerinde neden bu uygulamada doğrudan dosyaya yazma olduğu için gerekli olacağını emin değilim. PHP'nin sürüsü (kilidi almak için kullanılır) biraz ama güvenilmezdir ve diğer işlemler tarafından göz ardı edilebilir. Bu yüzden yeniden adlandırmayı kullanmanızı öneririm.

Yeniden adlandırma dosyası ideal olarak, 2 işlemin aynı şeyi yapmadığından emin olmak için yeniden adlandırma işlemini gerçekleştiren işleme benzersiz olarak adlandırılmalıdır. Ancak bu elbette aynı dosyanın aynı anda birden fazla kişi tarafından düzenlenmesini engellemez. Ancak en azından dosya bozulmadan bırakılacaktır (son düzenleme kazanır).

Adım 3) & 4) daha sonra şu olur:

$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

Tam olarak ne önermek istedim. Ama aynı zamanda veri hızını önlemek için okurken paylaşılan bir kilit elde ediyorum.
marco-a

Yeniden adlandırma, farklı disklerde değil, aynı diskteki bir atomik işlemdir.
Xnoise

İçin gerçekten benzersiz bir geçici dosya adını garanti, ayrıca kullanabilirsiniz atomik bir dosya ve döner dosya oluşturur fonksiyonları,. tempnam
Matthijs Kooijman

1

İçin PHP belgelerinde file_put_contents () sen bulabileceğiniz örnek 2. kullanımlık LOCK_EX , basitçe puting:

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

LOCK_EX bir ile sürekli olan bir tamsayı , bir bazı fonksiyonlar kullanılabilir daha değeri Bitwise .

Flock () biçiminde dosyalar için kilitlemeyi kontrol etmek amacıyla özel bir işlev de vardır .


Bu ilginç olsa da ve bazı durumlarda yararlı olsa da, bir dosyayı okurken, değiştirirken ve yeniden yazarken, kilidi okumadan önce edinilmeli ve tamamen yeniden yazılana kadar korunmalıdır (aksi takdirde başka bir işlem eski bir kopyayı okuyabilir ve değiştirebilir İşleminiz bittikten sonra geri). Bunun başarılabileceğine inanmıyorum file_get/put_contents.
Jules

0

Ayrıca dikkat etmeniz gereken bir konu, betiğinizin iki örneğinin neredeyse aynı anda çalıştığı yarış koşullarıdır, örneğin bu olay sırası:

  1. Komut dosyası örneği 1: Dosyayı okur
  2. Komut dosyası örneği 2: Dosyayı okur
  3. Komut dosyası örneği 1: Dosyadaki değişiklikleri yazar
  4. Komut dosyası örneği 2: İlk komut dosyası örneğinin kendi değişiklikleriyle dosyadaki değişikliklerinin üzerine yazar (bu noktada okunması bayat hale gelmiştir).

Bu nedenle, büyük bir dosyayı güncellerken, okumadan önce o dosyayı LOCK_EX yapmanız ve yazma işlemi yapılana kadar kilidi serbest bırakmamanız gerekir. Bu örnekte, dosyaya erişmek için sırasını beklerken ikinci komut dosyası örneğinin biraz askıda kalmasına neden olacağına inanıyorum, ancak bu, kayıp verilerden daha iyi.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.