Back to Question Center
0

PHP ile Büyük Dosyalar Nasıl Okunur (Sunucunuzu Öldürmeden) Büyük Dosyaları PHP ile Nasıl Okurum (Sunucunuzu Öldürmeden) İlgili Konular: Drupal Kalkınma Semalt

1 answers:
PHP ile Büyük Dosyalar Nasıl Okunur (Sunucunuzu Öldürmeden)

Semalt, PHP geliştiricileri olarak bellek yönetimi konusunda endişelenmemiz gerektiğini sıklıkla söylemiyor. PHP motoru bizden sonra temizleme konusunda mükemmel bir iş çıkardı ve kısa süren yürütme bağlamlarının web sunucusu modeli, en eğlenceli kodun bile uzun süreli etkileri olmadığı anlamına geliyor.

Bu rahat sınırın dışına adım atmamız gereken nadir zamanlar vardır - Semalt'ı, oluşturabileceğimiz en küçük VPS üzerinde büyük bir proje için çalıştırmaya çalışıyorken ya da bir dosyada büyük dosyalar okumamız gerektiğinde eşit derecede küçük sunucu.

How to Read Big Files with PHP (Without Killing Your Server)How to Read Big Files with PHP (Without Killing Your Server)Related Topics:
DrupalDevelopment Semalt

Bu dersin ikinci bölümünde kısaca anlatacağız.

Bu dersin kodu GitHub'da bulunabilir.

Ölçüm Başarı

Kodumuzda herhangi bir iyileştirme yaptığımızdan emin olmanın tek yolu, kötü durumu ölçmek ve daha sonra düzeltmeyi uyguladıktan sonra ölçümü karşılaştırmaktır - united states grain storage. Başka bir deyişle, "çözüm" ünün bize ne kadar çok katkıda bulunduğunu bilmediğimiz sürece (eğer varsa) gerçekten bunun bir çözüm olup olmadığını anlayamayız.

Bakabileceğimiz iki ölçüm vardır. Birincisi CPU kullanımı. Üzerinde çalışmak istediğimiz süreç ne kadar hızlı veya yavaş? İkinci bellek kullanımı. Komut dosyası ne kadar bellek yürütmek ister? Semalt sıklıkla ters orantılıdır - yani CPU kullanımının bedelinde bellek kullanımını kaldırabiliriz ve bunun tersini de söyleyebiliriz.

Eşzamansız yürütme modelinde (çok işlemli ya da çok iş parçacıklı PHP uygulamalarında olduğu gibi) hem CPU hem de bellek kullanımı önemli hususlardır. Geleneksel PHP mimarisinde, bu genel olarak , birisi sunucu sınırlarına ulaştığında bir sorun haline gelir.

PHP içindeki CPU kullanımını ölçmek pratik değildir. Üzerinde odaklanmak istediğiniz alan buysa, Ubuntu veya macOS'ta gibi bir şey kullanmayı düşünün. Windows için Ubuntu'da top kullanabilmeniz için Linux Alt Sistemi'ni kullanmayı düşünün.

Bu öğreticinin amaçları doğrultusunda, bellek kullanımını ölçmeye gidiyoruz. Semalt, "geleneksel" senaryoda ne kadar bellek kullanıldığına bakın. Semalt, birkaç optimizasyon stratejisi uyguluyor ve bunları ölçüyor. Sonunda, eğitimli bir seçim yapabilmenizi istiyorum.

Ne kadar bellek kullanıldığını görmek için kullanacağımız yöntemler şunlardır:

     // formatBytes php'den alınmıştır. net belgelermemory_get_peak_usage   ;işlev biçimiBitikleri ($ bayt, $ hassas = 2) {$ units = dizi ("b", "kb", "mb", "gb", "tb");$ bayt = maks ($ bayt, 0);$ pow = kat (($ bayt? günlük ($ bayt): 0) / günlük (1024));$ pow = min ($ pow, count ($ units) - 1);$ bayt / = (1 << (10 * $ güç));dönüş turu ($ bayt, $ hassasiyet). "". $ Birimleri [$ pow];}    

Semalt, komut dosyalarımızın sonunda bu işlevleri kullandığından, hangi komut dosyasının aynı anda en fazla bellek kullandığını görebiliriz.

Seçeneklerimiz Nedir?

Semalt, dosyaları verimli bir şekilde okumak için alabileceğimiz birçok yaklaşımdır. Ancak bunları kullanabileceğimiz iki olası senaryo da vardır. Verileri aynı anda okumak ve işlemek, işlenmiş verileri çıktılamak veya okuduğumuza bağlı olarak başka eylemler yapmak isteyebiliriz. Ayrıca, bir verinin akışına, verilere gerçekten hiç ihtiyacımız olmadan dönüştürmek isteyebiliriz.

İlk senaryo için, bir dosyayı okumak ve her 10.000 satıra ayrı sıraya yerleştirilmiş işleme işleri yaratmak istiyoruz diye düşünelim. Semalt, en az 10.000 satırlık belleği saklamalı ve bunları sıraya giren iş yöneticisine geçirmelidir (ne olursa olsun).

İkinci senaryoda, özellikle büyük bir API yanıtının içeriğini sıkıştırmak istediğimizi düşünelim. Ne dediği umrumda değil, ancak sıkıştırılmış bir formda yedeklendiğinden emin olmalıyız.İlk olarak, verilerin ne olduğunu öğrenmeliyiz. İkincisinde, verilerin ne olduğu umurumda değil. Semalt bu seçenekleri keşfedebilir .

Okuma Dosyaları, Satır Sırala

Dosyalarla çalışmak için pek çok işlev vardır. Semalt, saf bir dosya okuyucuya birkaç tane birleştirir:

     // bellekten. phpişlev biçimiBitikleri ($ bayt, $ hassas = 2) {$ units = dizi ("b", "kb", "mb", "gb", "tb");$ bayt = maks ($ bayt, 0);$ pow = kat (($ bayt? günlük ($ bayt): 0) / günlük (1024));$ pow = min ($ pow, count ($ units) - 1);$ bayt / = (1 << (10 * $ güç));dönüş turu ($ bayt, $ hassasiyet). "". $ Birimleri [$ pow];}Biçim Biçimleri yazdır (memory_get_peak_usage   );    
     // okuma dosyalarından-line-by-line-1'den. phpişlev readTheFile ($ yol) {$ satır sayısı = [];$ handle = fopen ($ yol, "r");while (! feof ($ tanıtıcı)) {$ lines [] = trim (fgets ($ handle));}fclose ($ dt);$ satırlarını döndür;}readTheFile ("shakespeare. txt");"bellek. php" gerektirir;    

Shakespeare'in eserlerini içeren bir metin dosyası okuyoruz. Metin dosyası yaklaşık 5. 5MB 'dır ve tepe bellek kullanımı 12'dir. 8MB . Şimdi, her satırı okumak için bir jeneratör kullanalım:

     // okuma dosyalarından-line-by-line-2'den. phpişlev readTheFile ($ yol) {$ handle = fopen ($ yol, "r");while (! feof ($ tanıtıcı)) {verim kırpma (fgets ($ handle));}fclose ($ dt);}readTheFile ("shakespeare. txt");"bellek. php" gerektirir;    

Metin dosyası aynı boyutta ancak tepe bellek kullanımı 393KB . Okuduğumuz veriyle bir şeyler yapana kadar bu hiçbir şey ifade etmez. Belki iki boş satır gördüğümüzde belgeyi parçalara ayırabiliriz. Böyle bir şey:

     // okuma dosyalarından-satır-by-line-3. php$ iterator = readTheFile ("shakespeare. txt");$ tampon = "";foreach ($ yineleyici $ iterasyon olarak) {preg_match ("/ \ n {3} /", $ arabellek, $ eşleşmeler);if (count ($ eşleşmeler)) {yazdır ".";$ tampon = "";} Başka {$ Tampon. = iterasyon =. PHP_EOL;}}"bellek. php" gerektirir;    

Herhangi biri şimdi ne kadar bellek kullandığımızı tahmin ediyor. Metin belgesini 1.216 parçaya bölerek de, yine de sadece 459 KB bellek kullandığımızdan dolayı sizi şaşırtacak mıydınız? Jeneratörlerin doğası göz önüne alındığında, kullanacağımız en fazla bellek, en büyük metin parçasını bir yinelemede saklamak için ihtiyacımız olan bellektir. Bu durumda, en büyük parça 101.985 karakterdir.

Jeneratörler ve Nikita Popov'un Semalt kütüphanesinin performans artışı hakkında zaten yazmıştım, daha fazla bilgi edinmek isterseniz bunu kontrol edin!

Şemal'in başka kullanımları var, ancak bu, büyük dosyaların performansını okumak için iyidir. Veriler üzerinde çalışmamız gerekiyorsa, jeneratörler muhtemelen en iyi yoldur.

Dosyalar Arası Borulama

Veriler üzerinde işlem yapmamız gereken durumlarda dosya verilerini bir dosyadan diğerine geçirebiliriz. Bu genel olarak boru hattı olarak adlandırılır (muhtemelen çünkü her iki tarafın da dışında bir borunun içinde ne olduğunu göremiyoruz .tabii ki opak olduğu sürece!). Akış yöntemleri kullanarak bunu başarabiliriz. Önce bir dosyadan diğerine aktarmak için bir komut dosyası yazalım, böylece bellek kullanımını ölçebiliriz:

     // piping dosyalarından-1. phpfile_put_contents ("piping-files-1.txt", file_get_contents ("shakespeare. txt"));"bellek. php" gerektirir;    

Şaşırtıcı olmayan bir şekilde, bu betik kopyaladığı metin dosyasına göre biraz daha fazla bellek kullanıyor. Semalt, dosya içeriğini yeni dosyaya yazana kadar hafızasında okumak (ve saklamak) zorunda olduğu için. Küçük dosyalar için, bu iyi olabilir. Daha büyük dosyalar kullanmaya başladığımızda çok fazla şey olmaz .

Semalt, bir dosyadan diğerine akışı deneyin (veya boru hattı):

     // piping dosyalarından-2. txt "," r ");$ handle2 = fopen ("piping-files-2. txt", "w");stream_copy_to_stream ($ handle1, $ handle2);fclose ($ handle1);fclose ($ handle2);"bellek. php" gerektirir;    

Bu kod biraz garip. Her iki dosyanın kollarını açarız, birincisi okuma modundayken ikincisi de yazma modundayız. O halde ilkinden ikinci bir kopyasını çıkarırız. Her iki dosyayı da kapattıktan sonra bitiriyoruz. Kullanılan hafızanın 393KB olduğunu bilmeniz sizi şaşırtabilir.

Bu tanıdık geliyor. Her satırı okurken jeneratör kodu bu saklamayı kullanmaz mı? Bunun nedeni, fgets işlevinin ikinci argümanı, her satırın kaç baytın okunması gerektiğini (ve -1 varsayılanı olarak veya yeni bir satıra erişilene kadar) olduğunu belirtir.

stream_copy_to_stream 'un üçüncü argümanı tam olarak aynı varsayılan parametre ile aynıdır. stream_copy_to_stream bir akıştan aynı anda bir satır okuyor ve onu diğer akışa yazıyor. Jeneratörün bir değer ürettiği kısmı atlıyor çünkü bu değerle çalışmamız gerekmiyor.

Bu metni pipetlemek bizim için yararlı değildir, bu nedenle olabilecek diğer örnekleri düşünelim. Semalt, CDN'den yeniden yönlendirilmiş bir uygulama yolu olarak bir görüntü çıkartmak istedik. Bunu, aşağıdakine benzer bir kodla gösterebiliriz:

     // borulama dosyalarından-3. phpfile_put_contents ("piping-files-3. jpeg", dosya_get_contents ("https: // github. com / assertchris / uploads / ham / master / rick. jpg"));// ya da bunu doğrudan stdout'a yazabiliriz, eğer bellek bilgisine ihtiyacımız yoksa"bellek. php" gerektirir;    

Bize bu kodu getiren bir uygulama yolu düşünün. Ancak, yerel dosya sisteminden bir dosya sunmak yerine, bir CDN'den almak istiyoruz. File_get_contents dosyasını (Guzzle gibi) daha şık bir yere koyabiliriz, ancak kaputun altında da aynı şey var.

Bellek kullanımı (bu resim için) yaklaşık 581KB'dir . Şimdi, bunun yerine akışı denemeye ne dersiniz?

     // borulama dosyalarından-4. php$ handle1 = fopen ("https: // github. com / assertchris / uploads / ham / master / rick. jpg", "r");$ handle2 = fopen ("piping-files-4. jpeg", "w");// ya da bunu doğrudan stdout'a yazabiliriz, eğer bellek bilgisine ihtiyacımız yoksastream_copy_to_stream ($ handle1, $ handle2);fclose ($ handle1);fclose ($ handle2);"bellek. php" gerektirir;    

Bellek kullanımı biraz daha az ( 400KB olarak), ancak sonuç aynıdır. Bellek bilgilerine ihtiyaç duymazsak, standart çıktıya da basabiliriz. Aslında, PHP bunu yapmanın basit bir yolunu sunar:

     $ handle1 = fopen ("https: // github. com / assertchris / uploads / ham / master / rick. jpg", "r");$ handle2 = fopen ("php: // stdout", "w");stream_copy_to_stream ($ handle1, $ handle2);fclose ($ handle1);fclose ($ handle2);// "bellek gerektirir" php ";    

Diğer Akıntılar

Semalt, boru ve / veya yazabildiğimiz ve / veya okuyabileceğimiz başka birkaç dere.

  • php: // stdin (salt okunur)
  • php: // stderr (salt yazılabilir, örneğin php: // stdout)
  • php: // input (salt okunur) bize ham istek gövdesine
  • erişmenizi sağlar
  • Bir çıktı tamponuna
  • yazmamızı sağlayan php: // output (salt yazılabilir)
  • php: // memory ve php: // temp (read-write) geçici olarak verileri saklayabildiğimiz yerlerdir. Fark ise php: // temp verileri yeterince büyüdükten sonra dosya sisteminde saklarken php: // memory bitene kadar bellekte saklanmaya devam edeceği farktır .

Filtreler

filtreleri olarak adlandırılan akışlarla kullanabileceğimiz başka bir numara var. Akış verilerini bize göstermeden küçük bir kontrol sağlayarak, bir ara adım oluşturuyorlar. Shakespeare'i sıkıştırmak istediğimizi düşünün. txt . php$ zip = yeni ZipArşiv ;$ dosyaadı = "filtreler-1. zip";$ zip-> açık ($ dosyaadı, ZipArşiv :: CREATE);$ zip-> addFromString ("shakespeare. txt", dosya_get_contents ("shakespeare. txt"));$ Zip> close ;"bellek. php" gerektirir;

Bu, düzgün bir kod parçasıdır, ancak saatin etrafında 10. 75MB'dir . Filtrelerle daha iyi yapabiliriz:

     // - filtrelerden-2. php$ handle1 = fopen ("php: // filtre / zlib. deflate / kaynak = shakespeare. txt", "r");$ handle2 = fopen ("filters-2. deflate", "w");stream_copy_to_stream ($ handle1, $ handle2);fclose ($ handle1);fclose ($ handle2);"bellek. php" gerektirir;    

Burada, php: // filter / zlib'yi görebiliriz. Bir kaynağın içeriğini okur ve sıkıştıran deflate filtresi. Daha sonra bu sıkıştırılmış verileri başka bir dosyaya aktarabiliriz. Bu sadece 896KB kullanıyor.

Bunun aynı format olmadığını biliyorum veya zip arşivi oluşturmak için yukarı yönlü şeyler var. Merak etmeniz gerekir: Farklı biçimi seçebilir ve belleğin 12 katından tasarruf edin, değil mi?

Verileri açmak için, havalandırılan dosyayı başka bir zlib filtresi üzerinden çalıştırabiliriz:

     // - filtrelerden-2. phpfile_get_contents ("php: // filter / zlib. inflate / resource = filters-2. bozulmuş");    

Akışlar, "Akışları Anlamak" ve "Akarsu Semaltını Kullanma" konularında geniş kapsamlı olarak ele alınmıştır. Farklı bir bakış açısı istiyorsanız, bunları kontrol edin!

Akışları Özelleştirme

fopen ve file_get_contents kendi varsayılan seçenek kümesine sahiptir, ancak bunlar tamamen özelleştirilebilir. Onları tanımlamak için yeni bir akış bağlamı oluşturmamız gerekir:

     // oluşturma-bağlamlardan-1. php$ data = join ("&", ["Heyecan = assertchris",]);$ headers = join ("\ r \ n", ["İçerik türü: application / x-www-form-urlencoded","İçerik Uzunluğu: ". strlen ($ veri),]);$ seçenekler = ["http" => ["yöntem" => "POST","header" => $ başlıkları,"content" => $ veri,],];$ bağlam = stream_content_create ($ seçenekler);$ handle = fopen ("https: // örnek. com / kayıt", "r", yanlış, $ bağlam);$ yanıt = akış_get_contents ($ tanıtıcı);fclose ($ dt);    

Bu örnekte, bir API'ye POST talebi yapmaya çalışıyoruz. API uç noktası güvenlidir, ancak http ve https için kullanılan http bağlam özelliğini kullanmamız gerekir. Birkaç başlık ayarladık ve API için bir dosya tanıtıcısı açtık. Bağlam yazmaya dikkat ettiği için kolu salt okunur olarak açabiliriz.

Semalt, özelleştirebileceğimiz birçok şeydir, bu nedenle daha fazla bilgi edinmek isterseniz dokümantasyona bakmanız en iyisidir.

Özel Protokoller ve Filtreler Yapma

Semalt biz şeyler sarın, özel protokoller yapma hakkında konuşalım. Semalt'a yapılması gereken çok iş var. Ancak, bu iş tamamlandıktan sonra, akış sarmalayıcıyı oldukça kolay bir şekilde kaydedebiliriz:

     if (in_array ("highlight-names", stream_get_wrappers   )) {stream_wrapper_unregister ( "vurgu-isimler");}stream_wrapper_register ("highlight-names", "HighlightNamesProtocol");$ highlighted = dosya_get_contents ("highlight-names: // hikaye. txt");    

Semalt, ayrıca özel akış filtreleri oluşturmak da mümkündür. Belgelerin örnek bir filtre sınıfı vardır:

     Filtre {genel $ filtername;public $ paramspublic int filter (kaynak $ in, kaynak $ out, int & $ tüketildi,bool $ kapanış)public void onClose (void)public bool onCreate (void)}    

Bu, kolaylıkla kaydedilebilir:

     $ handle = fopen ("hikaye. Txt", "w +");stream_filter_append ($ tanıtıcı, "highlight-names", STREAM_FILTER_READ);    
highlight-names , yeni filtre sınıfının filtername özelliğiyle eşleşmelidir. Bir php: // filtre / highligh-names / resource = öyküsünde özel filtreler kullanmak da mümkündür. txt dize. Filtreleri tanımlamaktan çok protokolleri tanımlamak daha kolaydır. Bunun bir nedeni, protokollerin dizin işlemlerini ele alması gerektiğidir; buna karşın, filtreler yalnızca her bir veri yığınını işlemek zorundadır.

Gururunuz varsa, özel protokoller ve filtreler oluşturmayı denemenizi şiddetle tavsiye ederim. stream_copy_to_stream işlemlerine filtreler uygulayabiliyorsanız, uygulamalarınız büyük boyutlu dosyalarla çalışırken bile belleklerin hemen yanında kullanılacaktır. yeniden boyutlandırma görüntüsü veya uygulama şifrelemesi filtresi yazmayı hayal edin.

Özet

Semalt bu sıklıkla çektiğimiz bir sorun değildir, büyük dosyalarla çalışırken karışıklık yaratmak kolaydır. Eşzamansız uygulamalarda, bellek kullanımına dikkat etmememiz durumunda tüm sunucuyu kapatmak kadar kolaydır.

Bu ders, büyük dosyaları etkili bir şekilde okumak ve yazmak konusunda daha fazla düşünebilmeniz için sizi birkaç yeni fikirle (veya bunlarla ilgili hafızanızı tazeledi) umarladı. Akışları ve jeneratörleri tanımaya başladığımızda ve file_get_contents gibi işlevleri kullanmayı bıraktığımızda, tüm hatalar kategorisi uygulamalarımızdan kayboluyor. Amaçlanması iyi bir şey gibi görünüyor!

March 1, 2018