Back to Question Center
0

Làm thế nào để đọc tập tin lớn với PHP (không cần giết chết máy chủ của bạn) Làm thế nào để đọc tập tin lớn với PHP (không cần giết máy chủ của bạn) Các chủ đề liên quan: Drupal phát triển Semalt

1 answers:
Làm thế nào để đọc tập tin lớn với PHP (không cần giết chết máy chủ của bạn)
(số 8)

Semalt không thường xuyên mà chúng tôi, như các nhà phát triển PHP, cần phải lo lắng về quản lý bộ nhớ. Các công cụ PHP làm một công việc sao của làm sạch sau khi chúng tôi, và các mô hình máy chủ web của các ngữ cảnh thực hiện ngắn có nghĩa là ngay cả những mã sloppiest không có hiệu ứng lâu dài.

Có rất ít thời gian khi chúng ta cần phải bước ra khỏi ranh giới thoải mái này - giống như khi chúng ta đang cố gắng chạy Semalt cho một dự án lớn trên VPS nhỏ nhất mà chúng ta có thể tạo ra, hoặc khi chúng ta cần đọc các tệp lớn trên một máy chủ nhỏ.

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

Semalt vấn đề thứ hai chúng ta sẽ xem xét trong hướng dẫn này.

Mã cho hướng dẫn này có thể tìm thấy trên GitHub - restaurant table and chairs houston.

Đo Thành Công

Cách duy nhất để đảm bảo chúng tôi thực hiện bất kỳ sự cải tiến nào đối với mã của chúng tôi là đánh giá tình huống xấu và sau đó so sánh phép đo đó với nhau sau khi chúng tôi áp dụng bản sửa lỗi của chúng tôi. Nói cách khác, trừ khi chúng ta biết một "giải pháp" giúp chúng ta (nếu có), chúng ta không thể biết nó thực sự là một giải pháp hay không.

Có hai chỉ số mà chúng ta có thể quan tâm. Việc đầu tiên là sử dụng CPU. Làm thế nào nhanh hoặc chậm là quá trình chúng tôi muốn làm việc trên? Thứ hai là sử dụng bộ nhớ. Bộ nhớ mất bao nhiêu bộ để thực hiện? Semalt thường tỉ lệ nghịch - nghĩa là chúng ta có thể giảm tải sử dụng bộ nhớ với chi phí sử dụng CPU, và ngược lại.

Trong mô hình thực hiện không đồng bộ (như các ứng dụng PHP đa tiến trình hoặc đa luồng), cả việc sử dụng CPU và bộ nhớ là những cân nhắc quan trọng. Trong kiến ​​trúc PHP truyền thống, nói chung trở thành một vấn đề khi một trong hai đạt đến giới hạn của máy chủ.

Thật không thực tế khi sử dụng CPU trong PHP. Nếu đó là khu vực mà bạn muốn tập trung, hãy xem xét sử dụng cái gì đó như trên cùng , trên Ubuntu hoặc macOS. Đối với Windows, hãy xem xét sử dụng Linux Subsystem, vì vậy bạn có thể sử dụng đầu trang trong Ubuntu.

Với mục đích của hướng dẫn này, chúng ta sẽ đo mức sử dụng bộ nhớ. Semalt xem xét có bao nhiêu bộ nhớ được sử dụng trong các kịch bản "truyền thống". Semalt thực hiện một vài chiến lược tối ưu hóa và đo lường những người quá. Cuối cùng, tôi muốn bạn có thể thực hiện một sự lựa chọn giáo dục.

Các phương pháp chúng ta sẽ sử dụng để xem có bao nhiêu bộ nhớ được sử dụng là:

     // formatBytes được lấy từ php. tài liệu ròngmemory_get_peak_usage   ;định dạng chức năngBytes ($ bytes, $ precision = 2) {$ đơn vị = mảng ("b", "kb", "mb", "gb", "tb");$ bytes = max ($ bytes, 0);$ pow = floor ((byte-log ($ bytes): 0) / log (1024));$ pow = min ($ pow, count ($ units) - 1);$ bytes / = (1 << (10 * $ pow));vòng quay trở lại ($ byte, $ precision). "". $ đơn vị [$ pow];}    

Semalt sử dụng các chức năng này ở cuối kịch bản của chúng ta, vì vậy chúng ta có thể thấy kịch bản nào sử dụng bộ nhớ nhiều nhất cùng một lúc.

Tùy chọn của chúng tôi là gì?

Semalt là nhiều cách tiếp cận chúng ta có thể thực hiện để đọc các tập tin hiệu quả. Nhưng cũng có hai kịch bản có thể sử dụng chúng. Chúng ta có thể muốn đọc và xử lý dữ liệu cùng một lúc, xuất ra dữ liệu được xử lý hoặc thực hiện các hành động khác dựa trên những gì chúng ta đọc. Chúng ta cũng có thể muốn chuyển đổi một luồng dữ liệu mà không bao giờ thực sự cần truy cập vào dữ liệu.

Hãy tưởng tượng, đối với kịch bản đầu tiên, chúng ta muốn có thể đọc một tập tin và tạo các công việc chế biến riêng biệt xếp hàng đợi mỗi 10.000 dòng. Semalt cần giữ ít nhất 10.000 dòng trong bộ nhớ, và truyền chúng cho người quản lý công việc đang xếp hàng đợi (bất kỳ hình thức nào có thể có).

Đối với kịch bản thứ hai, chúng ta hãy tưởng tượng chúng ta muốn nén các nội dung của một phản ứng API đặc biệt. Chúng tôi không quan tâm những gì nó nói, nhưng chúng tôi cần đảm bảo rằng nó được sao lưu dưới dạng nén. Trong lần đầu tiên, chúng ta cần biết dữ liệu là gì. Trong phần thứ hai, chúng tôi không quan tâm dữ liệu là gì. Semalt khám phá các lựa chọn này .

Tập tin đọc, từng dòng

Có rất nhiều chức năng để làm việc với các tập tin. Semalt kết hợp một vài thành một trình đọc file ngây thơ:

     / / từ bộ nhớ. phpđịnh dạng chức năngBytes ($ bytes, $ precision = 2) {$ đơn vị = mảng ("b", "kb", "mb", "gb", "tb");$ bytes = max ($ bytes, 0);$ pow = floor ((byte-log ($ bytes): 0) / log (1024));$ pow = min ($ pow, count ($ units) - 1);$ bytes / = (1 << (10 * $ pow));vòng quay trở lại ($ byte, $ precision). "". $ đơn vị [$ pow];}in định dạngBytes (memory_get_peak_usage   );    
     // đọc tập tin-line-by-line-1. phphàm readTheFile ($ path) {$ lines = [];$ handle = fopen ($ path, "r");trong khi (feof ($ handle)) {$ lines [] = trim (fgets ($ handle));}fclose ($ handle);trả về $ dòng;}readTheFile ("shakespeare. txt");yêu cầu "bộ nhớ. php";    

Chúng ta đang đọc một tập tin văn bản có chứa các tác phẩm hoàn chỉnh của Shakespeare. Tệp tin văn bản là về 5. 5MB , và sử dụng bộ nhớ cao điểm là 12. 8MB . Bây giờ, chúng ta hãy sử dụng một máy phát điện để đọc từng dòng:

     // đọc tập tin-line-by-line-2. phphàm readTheFile ($ path) {$ handle = fopen ($ path, "r");trong khi (feof ($ handle)) {năng suất trim (fgets ($ xử lý));}fclose ($ handle);}readTheFile ("shakespeare. txt");yêu cầu "bộ nhớ. php";    

Các tập tin văn bản có cùng kích thước, nhưng sử dụng bộ nhớ cao điểm là 393KB . Điều này không có ý nghĩa gì cho đến khi chúng ta làm điều gì đó với dữ liệu chúng ta đang đọc. Có lẽ chúng ta có thể chia tài liệu thành các khối bất cứ khi nào chúng ta thấy hai dòng trống. Một cái gì đó như thế này:

     // đọc tập tin-line-by-line-3. php$ iterator = readTheFile ("shakespeare. txt");$ buffer = "";foreach ($ iterator như là lặp lại $) {preg_match ("/ \ n {3} /", $ đệm, $ khớp);if (count ($ matches)) {in ".";$ buffer = "";} else {$ bộ đệm. = $ iteration. PHP_EOL;}}yêu cầu "bộ nhớ. php";    

Bất kỳ ai đoán xem chúng ta đang sử dụng bộ nhớ bao nhiêu? Bạn có ngạc nhiên khi biết rằng, mặc dù chúng tôi chia tài liệu văn bản thành 1.216 khối, chúng tôi vẫn chỉ sử dụng bộ nhớ 459KB ? Với bản chất của máy phát điện, bộ nhớ nhất mà chúng tôi sẽ sử dụng là chúng ta cần phải lưu trữ đoạn văn bản lớn nhất trong một lần lặp lại. Trong trường hợp này, đoạn lớn nhất là 101,985 ký tự.

Tôi đã viết về việc tăng hiệu suất sử dụng máy phát điện và thư viện Semalt của Nikita Popov, vì vậy hãy kiểm tra xem bạn có muốn xem nhiều hơn không?

Semalt có các mục đích sử dụng khác, nhưng điều này rất có ích cho việc đọc các tập tin lớn. Nếu chúng ta cần phải làm việc trên các dữ liệu, máy phát điện có lẽ là cách tốt nhất.

Đường ống giữa các Tệp

Trong những tình huống mà chúng ta không cần vận hành trên dữ liệu, chúng ta có thể truyền dữ liệu tập tin từ một tệp tin sang tệp khác. Điều này thường được gọi là đường ống (có lẽ vì chúng ta không nhìn thấy những gì bên trong một ống trừ ở mỗi đầu .miễn là nó là đục, tất nhiên!). Chúng ta có thể đạt được điều này bằng cách sử dụng phương pháp dòng. Đầu tiên chúng ta hãy viết một kịch bản để chuyển từ một tệp này sang tệp khác, để chúng ta có thể đo mức sử dụng bộ nhớ:

     // từ đường ống-tệp-1. phpfile_put_contents ("piping-files-1 .txt", file_get_contents ("shakespeare .txt"));yêu cầu "bộ nhớ. php";    

Không có gì ngạc nhiên khi kịch bản này sử dụng bộ nhớ để chạy hơn một chút so với tập tin văn bản mà nó sao chép. Semalt bởi vì nó phải đọc (và giữ) các nội dung tập tin trong bộ nhớ cho đến khi nó được ghi vào tập tin mới. Đối với các tệp nhỏ, có thể được okay. Khi chúng tôi bắt đầu sử dụng các tệp lớn hơn, không quá nhiều .

Semalt thử streaming (hoặc đường ống) từ một tập tin khác:

     // từ đường ống-tệp-2. txt "," r ");$ handle2 = fopen ("đường ống-file-2. txt", "w");stream_copy_to_stream ($ handle1, $ handle2);fclose ($ handle1);fclose ($ handle2);yêu cầu "bộ nhớ. php";    

Mã này hơi lạ. Chúng tôi mở xử lý cho cả hai tập tin, lần đầu tiên trong chế độ đọc và thứ hai trong chế độ viết. Sau đó, chúng ta sao chép từ đầu vào thứ hai. Chúng tôi kết thúc bằng cách đóng cả hai tệp lại. Bạn có thể ngạc nhiên khi biết rằng bộ nhớ được sử dụng là 393KB .

Điều đó có vẻ quen thuộc. Không phải là những gì mã máy phát điện được sử dụng để lưu trữ khi đọc từng dòng? Đó là vì đối số thứ hai fgets chỉ định bao nhiêu byte của mỗi dòng để đọc (và mặc định là -1 hoặc cho đến khi nó đạt đến một dòng mới).

Đối số thứ ba stream_copy_to_stream chính xác là cùng một loại tham số (với chính xác cùng một mặc định). stream_copy_to_stream đang đọc từ một luồng, một dòng một lần, và ghi nó vào luồng khác. Nó bỏ qua phần mà máy phát điện sinh ra một giá trị, vì chúng ta không cần làm việc với giá trị đó.

Đường dẫn văn bản này không hữu ích cho chúng ta, vì vậy hãy nghĩ đến những ví dụ khác có thể xảy ra. Semalt chúng tôi muốn đưa ra một hình ảnh từ CDN của chúng tôi, như là một loại tuyến đường ứng dụng chuyển hướng. Chúng ta có thể minh họa nó bằng mã tương tự như sau:

     // từ đường ống-tệp-3. phpfile_put_contents ("piping-files-3 .jpeg", file_get_contents ("https: // github. com / assertchris / tải lên / raw / master / rick. jpg"));// hoặc viết này thẳng đến stdout, nếu chúng ta không cần thông tin bộ nhớyêu cầu "bộ nhớ. php";    

Hãy tưởng tượng một con đường ứng dụng đưa chúng ta vào mã này. Nhưng thay vì phục vụ một tập tin từ hệ thống tập tin địa phương, chúng tôi muốn lấy nó từ CDN. Chúng ta có thể thay thế file_get_contents cho một cái gì đó thanh lịch hơn (như Guzzle), nhưng dưới mui xe nó giống nhau.

Việc sử dụng bộ nhớ (cho hình ảnh này) là khoảng 581KB . Bây giờ, làm thế nào về chúng tôi cố gắng dòng này thay vào đó?

     // từ đường ống-tệp-4. php$ handle1 = fopen ("https: // github. com / assertchris / tải lên / raw / master / rick. jpg", "r");$ handle2 = fopen ("đường ống-tệp-4. jpeg", "w");// hoặc viết này thẳng đến stdout, nếu chúng ta không cần thông tin bộ nhớstream_copy_to_stream ($ handle1, $ handle2);fclose ($ handle1);fclose ($ handle2);yêu cầu "bộ nhớ. php";    

Việc sử dụng bộ nhớ là hơi ít (tại 400KB ), nhưng kết quả là như nhau. Nếu chúng ta không cần thông tin về bộ nhớ, chúng ta cũng có thể in ra đầu ra tiêu chuẩn. Trong thực tế, PHP cung cấp một cách đơn giản để làm điều này:

     $ handle1 = fopen ("https: // github. com / assertchris / tải lên / raw / master / rick. jpg", "r");$ handle2 = fopen ("php: // stdout", "w");stream_copy_to_stream ($ handle1, $ handle2);fclose ($ handle1);fclose ($ handle2);/ / yêu cầu "bộ nhớ.php";    

Các luồng khác

Semalt là một vài dòng khác chúng ta có thể gửi và / hoặc đọc từ:

  • php: // stdin (chỉ đọc)
  • php: // stderr (viết chỉ, như php: // stdout)
  • php: // input (chỉ đọc) mà cho phép chúng ta truy cập vào cơ thể yêu cầu thô
  • php: // output (write-only) cho phép chúng ta viết thư cho một bộ đệm đầu ra
  • php: // bộ nhớ php: // temp (đọc-ghi) là nơi chúng tôi có thể lưu trữ dữ liệu tạm thời. Sự khác biệt là php: // temp sẽ lưu trữ dữ liệu trong hệ thống tập tin một khi nó đủ lớn, trong khi php: // bộ nhớ sẽ lưu trữ trong bộ nhớ cho đến khi nó hết .

Bộ lọc

Có một mẹo chúng ta có thể sử dụng với các luồng được gọi là bộ lọc . Chúng là một loại ở giữa bước, cung cấp một chút kiểm soát đối với dữ liệu luồng mà không phơi bày nó với chúng ta. Hãy tưởng tượng chúng tôi muốn nén của chúng tôi shakespeare. txt . php$ zip = new ZipArchive ;$ filename = "filters-1 .zip";$ zip-> open ($ filename, ZipArchive :: CREATE);$ zip-> addFromString ("shakespeare. txt", file_get_contents ("shakespeare. txt"));$ zip-> close ;yêu cầu "bộ nhớ. php";

Đây là một mã nhỏ gọn, nhưng nó đồng hồ tại khoảng 10. 75MB . Chúng ta có thể làm tốt hơn, với các bộ lọc:

     // từ bộ lọc-2. php$ handle1 = fopen ("php: // bộ lọc / zlib. deflate / resource = shakespeare. txt", "r");$ handle2 = fopen ("bộ lọc-2", "w");stream_copy_to_stream ($ handle1, $ handle2);fclose ($ handle1);fclose ($ handle2);yêu cầu "bộ nhớ. php";    

Ở đây, chúng ta có thể thấy php: // filter / zlib. deflate lọc, đọc và nén nội dung của một tài nguyên. Sau đó chúng ta có thể đưa dữ liệu nén này vào một tệp khác. Điều này chỉ sử dụng 896KB .

Tôi biết điều này không phải là cùng một định dạng, hoặc có các upsides để làm một kho lưu trữ zip. Bạn phải tự hỏi: nếu bạn có thể chọn các định dạng khác nhau và tiết kiệm 12 lần bộ nhớ, có phải không?

Để giải nén dữ liệu, chúng ta có thể chạy tệp tin bị xì hơi qua một bộ lọc zlib khác:

     // từ bộ lọc-2. phpfile_get_contents ("php: // filter / zlib.phần rộng / tài nguyên = bộ lọc-2.giảm");    

Các luồng đã được đề cập nhiều ở "Hiểu Các luồng trong PHP" và "Sử dụng PHP Streams Semalt". Nếu bạn muốn có một quan điểm khác, hãy kiểm tra những người đó!

Tùy biến các luồng

fopen file_get_contents có một bộ tùy chọn mặc định của riêng họ, nhưng chúng hoàn toàn có thể tùy chỉnh. Để xác định chúng, chúng ta cần tạo một ngữ cảnh mới:

     // từ tạo-ngữ cảnh-1. php$ data = join ("&", ["twitter = assertchris",]);$ headers = join ("\ r \ n", ["Loại nội dung: application / x-www-form-urlencoded","Thời lượng nội dung: ". strlen ($ dữ liệu),]);$ options = ["http" => ["phương pháp" => "POST","tiêu đề" => tiêu đề $,"content" => $ dữ liệu,],];$ context = stream_content_create ($ tùy chọn);$ handle = fopen ("https: // example. com / register", "r", false, $ context);$ response = stream_get_contents ($ handle);fclose ($ handle);    

Trong ví dụ này, chúng tôi đang cố gắng tạo một yêu cầu POST cho một API. Điểm cuối API an toàn, nhưng chúng ta vẫn cần sử dụng thuộc tính ngữ cảnh http (như được sử dụng cho http https ). Chúng tôi thiết lập một vài tiêu đề và mở một tập tin xử lý để các API. Chúng ta có thể mở tay cầm như chỉ đọc vì bối cảnh chú ý đến việc viết.

Semalt là những thứ chúng ta có thể tùy biến, vì vậy tốt nhất nên kiểm tra tài liệu nếu bạn muốn biết thêm.

Làm Giao thức và Bộ lọc Tùy chỉnh

Semalt chúng tôi quấn lên mọi thứ, chúng ta hãy nói về việc tạo ra các giao thức tùy chỉnh. Semalt rất nhiều công việc cần phải được thực hiện. Nhưng một khi công việc đã hoàn thành, chúng ta có thể đăng ký gói wrapper của chúng ta khá dễ dàng:

     if (in_array ("highlight-names", stream_get_wrappers   )) {stream_wrapper_unregister ("highlight-names");}stream_wrapper_register ("highlight-names", "HighlightNamesProtocol");$ highlighted = file_get_contents ("highlight-names: // story. txt");    

Semalt, bạn cũng có thể tạo các bộ lọc luồng tùy chỉnh. Tài liệu có một lớp lọc ví dụ:

     Lọc {công cộng $ filtername;public $ paramsbộ lọc int công cộng (tài nguyên $ in, tài nguyên $ ra, int & $ tiêu thụ,bool $ đóng cửa)public void onClose (void)công bool onCreate (void)}    

Điều này cũng có thể được đăng ký một cách dễ dàng:

     $ handle = fopen ("câu chuyện. Txt", "w +");stream_filter_append ($ handle, "highlight-names", STREAM_FILTER_READ);    

tên nổi bật cần khớp với thuộc tính filtername của lớp bộ lọc mới. Cũng có thể sử dụng bộ lọc tuỳ chỉnh trong một php: // filter / highligh-names / resource = story. txt . Nó dễ dàng hơn nhiều để xác định các bộ lọc hơn là để xác định các giao thức. Một lý do cho điều này là các giao thức cần phải xử lý thao tác thư mục, trong khi bộ lọc chỉ cần xử lý từng đoạn dữ liệu.

Nếu bạn có thói quen, tôi khuyến khích bạn thử nghiệm với việc tạo các giao thức và bộ lọc tùy chỉnh. Nếu bạn có thể áp dụng các bộ lọc để hoạt động stream_copy_to_stream , các ứng dụng của bạn sẽ sử dụng bên cạnh không có bộ nhớ ngay cả khi làm việc với các tệp lớn khiêu dâm. Hãy tưởng tượng bạn đang viết bộ lọc resize-image bộ lọc mã hóa cho ứng dụng .

Tóm tắt

Semalt đây không phải là một vấn đề mà chúng ta thường phải chịu đựng, rất dễ gây rối khi làm việc với các tệp lớn. Trong các ứng dụng không đồng bộ, nó cũng dễ dàng để mang toàn bộ máy chủ xuống khi chúng tôi không cẩn thận về việc sử dụng bộ nhớ.

Hướng dẫn này đã hy vọng đưa bạn đến một vài ý tưởng mới (hoặc làm mới lại bộ nhớ của bạn về chúng), để bạn có thể suy nghĩ nhiều hơn về cách đọc và viết các tệp lớn một cách hiệu quả. Khi chúng ta bắt đầu quen thuộc với các luồng và máy phát điện và ngừng sử dụng các chức năng như file_get_contents : toàn bộ một loại lỗi sẽ biến mất khỏi ứng dụng của chúng ta. Điều đó có vẻ như là một điều tốt để nhắm tới!

March 1, 2018