Làm cách nào để đọc một tệp lớn trong NodeJs?

Trình phân tích cú pháp CSV nhanh và mạnh mẽ dành cho trình duyệt hỗ trợ nhân viên web và truyền phát các tệp lớn. Chuyển đổi CSV sang JSON và JSON sang CSV

xuất bản 5. 3. 2 • 10 tháng trướcxuất bản 5. 3. 2 10 tháng trước

Mô-đun NodeJS giúp bạn đọc các tệp văn bản lớn, từng dòng một mà không lưu các tệp vào bộ nhớ đệm

Nhu cầu đọc một tệp có thể phát sinh trong nhiều trường hợp. Đó có thể là công việc phân tích nhật ký lỗi một lần, chức năng của ứng dụng, tác vụ di chuyển dữ liệu theo lịch trình, một phần của quy trình triển khai, v.v. Bất kể lý do là gì, việc đọc tệp trong Node. js là một nhiệm vụ rất đơn giản và dễ hiểu. Tuy nhiên, một sự cố xảy ra khi kích thước tệp vượt quá dung lượng RAM trong máy của bạn. Ngoài giới hạn phần cứng, RAM có thể bị giới hạn bởi nhà cung cấp VPS của bạn, cài đặt nhóm Kubernetes, v.v.

Một fs.readFile đơn giản sẽ không thực hiện được công việc. Và nhắm mắt lại với hy vọng rằng nó sẽ bằng cách nào đó vượt qua thời gian này sẽ không giúp ích gì

Đã đến lúc thực hiện một số chương trình nhận biết bộ nhớ

Trong bài viết này, tôi sẽ xem xét ba cách đọc tệp trong Node. js. Mục tiêu của tôi là tìm ra cách tiếp cận hiệu quả nhất về mặt sử dụng bộ nhớ. tôi sẽ che

  1. Tích hợp sẵn fs.readFileSync
  2. Lặp đi lặp lại trên fs.createReadStream
  3. fs.read với bộ đệm dùng chung

Cuộc thí nghiệm

Tôi đã triển khai từng cách tiếp cận dưới dạng một Nút nhỏ. js và chạy nó bên trong Docker container. Mỗi ứng dụng được giao nhiệm vụ xử lý tệp 1GB theo khối 10MB

Trong quá trình thực thi chương trình, tôi đã đo mức sử dụng bộ nhớ của bộ chứa Docker nhiều lần. Lưu ý rằng trong các biểu đồ được hiển thị trong bài viết này, bộ nhớ đo được là tổng bộ nhớ được sử dụng bởi Nút. js và tất cả các quy trình trong bộ chứa Docker (bao gồm cả HĐH). 

Bạn có nhận thấy rằng kích thước của một khối khá lớn không? . Nó vượt xa số lượng ký tự trong một dòng của tệp nhật ký thông thường hoặc CSV. Kích thước của một dòng sẽ là kích thước hợp lý trong ứng dụng thực tế. Tôi sử dụng các khối có kích thước tương đương với kích thước của bộ chứa Docker nhàn rỗi. Bằng cách này, bất kỳ sự khác biệt nào giữa các triển khai nhạy cảm với kích thước khối sẽ hiển thị rõ hơn trong biểu đồ

Hãy nhanh chóng xem những gì chúng ta đang giải quyết. Trong biểu đồ bên dưới, chúng ta có thể thấy mức sử dụng bộ nhớ tối đa của từng chương trình

Moving maximum of memory usage of createReadStream, read, and readFileSync.

Di chuyển tối đa mức sử dụng bộ nhớ của createReadStream, read và readFileSync

Chúng tôi thấy rõ rằng điều tồi tệ nhất là readFileSync, đã chiếm hơn 1GB bộ nhớ. Tiếp theo, với dấu chân thấp hơn nhiều, là createReadStream. Tốt nhất là read, sử dụng ít hơn 20 MB (gấp đôi kích thước khối)

Biểu đồ tiếp theo hiển thị cùng một dữ liệu, nhưng chỉ dành cho hai chức năng cuối cùng

Moving maximum of memory usage of createReadStream and read.

Di chuyển mức sử dụng bộ nhớ tối đa của createReadStream và đọc

Vì vậy, bây giờ chúng ta đã có một cái nhìn tổng quan tốt đẹp, hãy bắt đầu thực hiện từng phương pháp

Cách tiếp cận dễ nhất — và nguy hiểm nhất — là readFileSync một tệp

readFileSync, hoặc anh chị em không đồng bộ của nó là readFile, là sự lựa chọn dễ dàng. Chỉ cần một dòng mã để đọc một tệp và sau đó một vòng lặp fs.readFileSync0 duy nhất để lặp lại nội dung

Toàn bộ nội dung của tệp được giữ trong biến fs.readFileSync1. Vì vậy, không có gì ngạc nhiên ở đây. Phải mất ít nhất 1GB RAM. Cách tiếp cận này rõ ràng không phải là tốt nhất khi chúng ta xử lý các tệp lớn. Tuy nhiên, tính đơn giản của nó và thực tế là chúng ta có thể truy cập vào tất cả dữ liệu trong một dòng mã khiến nó đáng để xem xét với các tệp nhỏ hơn

Xả hơi với createReadStream

Xét về mức độ đơn giản của cách sử dụng, fs.readFileSync3 cũng đơn giản như readFile. Vẫn cần một dòng mã để đọc một tệp. Trên thực tế, hai đoạn mã này thậm chí trông giống nhau. Sự khác biệt là phương thức này trả về một luồng — không phải nội dung của tệp. Một luồng cần xử lý bổ sung để truy cập dữ liệu thực tế

Các luồng nghe có vẻ đáng sợ hoặc thậm chí có thể được coi là chương trình "nâng cao". Tuy nhiên, như bạn có thể thấy trong ví dụ mã, chúng ta chỉ có thể sử dụng vòng lặp fs.readFileSync5 để đọc nó. Điều này làm cho nó dễ dàng như lặp qua một mảng

Tùy chọn fs.readFileSync6 yêu cầu Node chỉ đọc số byte được cung cấp cùng một lúc. Điều đó làm cho nó tiết kiệm bộ nhớ hơn vì có một lượng dữ liệu hạn chế được lưu trong bộ nhớ trong một lần lặp

Cách tiếp cận này đã mang lại cho chúng tôi một kết quả tốt hơn nhiều. 90MB. Mức sử dụng bộ nhớ của vùng chứa thấp hơn mười lần so với ví dụ trước. Tuy nhiên, nó lớn hơn chín lần so với kích thước khối

Ngoài ra, độ lệch giữa các phép đo là khá lớn. Tuy nhiên, chúng tôi hướng dẫn Node. js để đọc 10 MB trong một lần lặp, điều đó không đảm bảo rằng sẽ có một đoạn duy nhất được lưu trong bộ nhớ thời gian chạy tại một thời điểm duy nhất. Các khối cũ cuối cùng sẽ bị người thu gom rác loại bỏ, nhưng chúng tôi không kiểm soát được thời điểm điều đó xảy ra

Có một cách để đạt được kết quả tốt hơn cả về mức độ và mức độ sử dụng bộ nhớ không đổi. Hãy xem cách đạt được nó bằng cách sử dụng fs.read với bộ đệm dùng chung

Kiểm soát nhiều hơn với Rfs.readFileSync8 và bộ đệm dùng chung

Cách tiếp cận này phức tạp hơn một chút so với hai cách trước. Tuy nhiên, tôi đã có thể đạt được mức sử dụng và biến thể bộ nhớ thấp nhất theo cách này

Ứng dụng này bao gồm ba phần đơn giản mà tôi sẽ trình bày từng bước

  1. Một lời hứa hẹn fs.read
  2. Trình tạo không đồng bộ
  3. Vòng lặp ứng dụng chính để xử lý dữ liệu

Nhưng trước tiên, bộ đệm “dùng chung” là gì?

Diagram comparing separate buffers vs. shared buffer

Bộ đệm riêng so với. bộ đệm dùng chung

Bộ đệm dùng chung là một biến được truyền theo tham chiếu đến tất cả các hàm. Thay vì tạo một bộ đệm mới trong mỗi chức năng, tôi tạo một bộ đệm duy nhất ở đầu chương trình và truyền nó xuống. Trong các ví dụ mã, tôi đề cập đến nó bằng một biến có tên là fs.createReadStream0, vì vậy nó sẽ được hiển thị rõ ràng

Đây là kỹ thuật thực tế cho phép tôi giảm mức sử dụng và biến thể bộ nhớ của mình. Trong biểu đồ dưới đây, có một so sánh giữa hai chương trình. Nếu không có bộ đệm dùng chung, chương trình sẽ tạo nhiều bản sao của cùng một dữ liệu, khiến nó trở nên dư thừa. Như chúng ta có thể thấy từ biểu đồ, nó rất tốn kém. Ngoài ra, mức sử dụng bộ nhớ thay đổi trong khoảng từ 20 MB đến 80 MB, đó là do bộ sưu tập rác

Bộ đệm dùng chung cho phép chúng tôi giảm mức sử dụng bộ nhớ, làm cho nó nhất quán hơn

Sử dụng bộ nhớ có và không có bộ đệm dùng chung

fs.createReadStream1a đã hứa hẹn fs.read

Đầu tiên, chúng tôi tạo một trình bao bọc cho fs.read. Chuyển đổi fs.read tích hợp thành một lời hứa sẽ đơn giản hóa việc sử dụng nó. Chúng tôi gọi fs.read với các đối số sau, mỗi

  • fs.createReadStream6 — Một số nguyên đại diện cho bộ mô tả tệp. Nó sẽ được tạo sau này trong chương trình
  • fs.createReadStream0 — Bộ đệm mà chúng ta sẽ ghi dữ liệu vào
  • fs.createReadStream8 — Phần bù trong bộ đệm để bắt đầu ghi tại. Chúng tôi luôn ghi dữ liệu vào đầu của fs.createReadStream0
  • fs.read0 —Số byte cần đọc. Trong trường hợp của chúng tôi, nó sẽ luôn là độ dài của bộ đệm của chúng tôi
  • fs.read1 — Vị trí trong tệp để bắt đầu đọc từ. Khi vị trí được đặt thành fs.read1, tệp sẽ được đọc từ byte đầu tiên và sau đó vị trí sẽ tự động cập nhật
  • Đối số cuối cùng là một hàm gọi lại

fs.read3 — máy phát điện không đồng bộ

Trình tạo trong JavaScript trả về trình vòng lặp — một đối tượng đặc biệt mà chúng ta có thể sử dụng trong vòng lặp fs.readFileSync0. Chúng ta có thể coi chúng là các mảng được cập nhật động. Trong mỗi bước của một lần lặp, trình tạo có thể nối thêm phần tử tiếp theo. Đó là những gì chúng tôi đang thực sự làm. Trong mỗi bước, chúng tôi đọc phần byte tiếp theo và biến nó thành mục tiếp theo của “mảng ảo” này. ”

Với trình vòng lặp được trả về bởi trình tạo fs.read3, chúng ta có thể lặp qua tệp trong vòng lặp fs.readFileSync0

Chúng ta phải hết sức thận trọng trong bước lặp lại cuối cùng. Phần cuối cùng của dữ liệu có thể ngắn hơn kích thước bộ đệm của chúng tôi. Tuy nhiên, kích thước của bộ đệm là không đổi. Điều này có nghĩa là bộ đệm có thể bao gồm các khối mới ở đầu và các khối trước đó ở cuối

Chúng tôi xử lý tình huống này bằng cách truy cập dữ liệu bằng fs.read7. Nó trả về một phần của bộ đệm từ đầu đến fs.read8, bằng với số byte được đọc trong bước hiện tại

Mạnh mẽ và mạo hiểm…

Mặc dù fs.read với bộ đệm dùng chung là cách tiếp cận thân thiện với bộ nhớ nhất, nhưng phần bộ đệm dùng chung hơi phức tạp một chút. Kỹ thuật này, khi sử dụng không cẩn thận, có thể dẫn đến

  • Rò rỉ dữ liệu — Khi bộ đệm chứa các byte từ các lần lặp lại trước đó hoặc trong tương lai và được xử lý sai như hiện tại. Điều này có thể xảy ra đặc biệt là khi đọc phần cuối của tệp
  • Dữ liệu sai lệch — Khi bộ đệm dùng chung bị sửa đổi ngoài ý muốn trong một phần khác của chương trình

Thận trọng hơn khi sử dụng phương pháp này

Tóm lược

Cả fs.createReadStreamfs.read đều có thể giảm mức sử dụng bộ nhớ khi đọc tệp trong Node. js. Nếu cấu trúc dữ liệu cho phép nó được truyền trực tuyến, bất kỳ phương pháp nào trong hai phương pháp đó sẽ phù hợp. Dung lượng bộ nhớ được ứng dụng của chúng tôi sử dụng trực tiếp phụ thuộc vào kích thước khối, nên được đặt chú ý đến các tính năng như cấu trúc dữ liệu hoặc bộ nhớ khả dụng

Sự khác biệt giữa fs.createReadStreamfs.read được nhấn mạnh khi kích thước khối tăng lên. Mức sử dụng bộ nhớ thấp hơn khoảng 10 lần đối với fs.read. Trong điểm chuẩn này, tôi đã sử dụng một đoạn 10 MB, do đó, mức sử dụng bộ nhớ chênh lệch gấp 10 lần mang lại 100 MB. Trong một ứng dụng thực tế, các khối có thể sẽ nhỏ hơn nhiều, do đó, sự khác biệt về dung lượng bộ nhớ đã sử dụng có thể trở nên không đáng kể. Trong trường hợp này, tính đơn giản và bảo mật của phương pháp có thể là yếu tố quyết định

Làm cách nào để đọc các tệp lớn trong nodejs?

luồng js. .
Bước 1. Tạo nút. ứng dụng js. .
Bước 2. Cài đặt phụ thuộc. Tiếp theo, cài đặt các gói fs và readline. .
Bước 3. Đọc tệp. .
Bước 4. Phân tích tệp. .
Bước 5. Xuất dữ liệu được phân tích cú pháp

Làm cách nào để đọc các tệp lớn trong JavaScript?

Đối với tệp lớn (trong trường hợp của bạn là 2GB), bạn có thể sử dụng hàm/phương thức FileReader. readAsArrayBuffer() để đọc một kích thước khối nhất định của tệp trong bộ nhớ do đó điều này sẽ không làm hỏng trình duyệt của bạn, blog này là một ví dụ điển hình. Phần quan trọng ở đây là phương thức slice() của Blob.

Nodejs có tốt cho các ứng dụng lớn không?

Với kiến ​​trúc đơn luồng, Node. js rất phù hợp để xử lý lượng lớn dữ liệu và lưu lượng truy cập . Ngay cả khi có sự tham gia của các thiết bị phân tán, Node. js hoạt động ở tốc độ cực cao và đồng bộ hóa nhanh chóng giữa phía máy khách và máy chủ.

Nút js CÓ THỂ xử lý lưu lượng truy cập cao không?

Vì nút. js sử dụng non-blocking IO, máy chủ có thể xử lý nhiều yêu cầu mà không cần đợi từng yêu cầu hoàn thành, điều đó có nghĩa là Node. js có thể xử lý lưu lượng truy cập web cao hơn nhiều so với các ngôn ngữ truyền thống khác .