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
- Tích hợp sẵn
fs.readFileSync
- Lặp đi lặp lại trên
fs.createReadStream
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
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
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.readFileSync
0 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.readFileSync
1. 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.readFileSync
3 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.readFileSync
5 để đọ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.readFileSync
6 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.readFileSync
8 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
- Một lời hứa hẹn
fs.read
- Trình tạo không đồng bộ
- 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ì?
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.createReadStream
0, 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
fs.createReadStream
1a đã 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.createReadStream
6 — 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ìnhfs.createReadStream
0 — Bộ đệm mà chúng ta sẽ ghi dữ liệu vàofs.createReadStream
8 — 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ủafs.createReadStream
0fs.read
0 —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ôifs.read
1 — Vị trí trong tệp để bắt đầu đọc từ. Khi vị trí được đặt thànhfs.read
1, 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.read
3 — 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.readFileSync
0. 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.read
3, chúng ta có thể lặp qua tệp trong vòng lặp fs.readFileSync
0
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.read
7. Nó trả về một phần của bộ đệm từ đầu đến fs.read
8, 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.createReadStream
và fs.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.createReadStream
và fs.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