在Node.js中编写内存高效的软件应用程序

转到Naren Yellavula的个人资料 Naren Yellavula封锁UnblockFollow继2018年11月14日之后
一个旨在避免气流的建筑物(https://pixelz.cc

软件应用程序在计算机的主存储器中运行,我们称之为随机存取存储器(RAM)。 JavaScript尤其是Node.js(服务器端js)允许我们为最终用户编写小型到大型软件项目。处理程序的内存总是很棘手,因为糟糕的实现可能会阻止在给定服务器或系统上运行的所有其他应用程序。 C和C ++程序员确实负责内存管理,因为那些潜伏在代码各个角落的内存泄漏。但是,js开发者?你在烦吗?

由于js开发人员通常在具有高容量的专用服务器上进行Web服务器编程,因此他们可能感觉不到多任务处理的滞后。即使在Web服务器开发的情况下,我们也会运行多个应用程序,如数据库服务器(MySQL),缓存服务器(Redis)以及我们软件所需的许多其他软件。我们需要意识到它们也消耗了可用的主存储器。如果我们随便编写应用程序,它会降低其他进程的性能或完全拒绝它们的内存分配。在本文中,我们通过解决问题并了解它们如何允许编写内存高效的应用程序,看到Node JS构造如流,缓冲区和管道。

我们使用 Node.js v8.12.0 来运行程序。我们将要提供的所有代码示例都可在此处获得。 narenaryan /节点背压-内部
一个补充代码库,用于说明Medium ... github.com 上的节点背压文章

问题:大量文件复制

如果有人要求在Node.js中编写文件复制程序,他们会快速跳转并创建这个程序。

该程序基本上创建了用于读取文件和使用给定文件名写入文件的句柄,并尝试在读取后将数据写入写入句柄。它适用于小文件。

让我们说我们的应用程序复制一个巨大的文件(> 4GB)作为备份过程的一部分。我有一个7.4 GB大小的超高清4K电影文件。如果我尝试运行上面的程序将这个大文件从我当前的目录复制到Documents。

 `$ node basic_copy.js cartoonMovie.mkv ~/Documents/bigMovie.mkv` 

我在Ubuntu(Linux)上得到了这个漂亮的缓冲区错误。

 `/home/shobarani/Workspace/basic_copy.js:7 if (err) throw err; ^` 
 `RangeError: File size is greater than possible Buffer: 0x7fffffff bytes at FSReqWrap.readFileAfterStat [as oncomplete] (fs.js:453:11)` 

如您所见,读取操作失败,因为Node JS只允许您将2GB数据读入其缓冲区而不会更多。如何克服这一点。当您进行I / O密集型操作(复制,处理,Zip)时,最好考虑系统内存。

Node JS中的Streams和Buffers

为了克服上述问题,我们需要一种将大数据分成多个块的机制,这是一种用于保存这些块的数据结构。缓冲区是存储二进制数据的数据结构。接下来,我们需要一种系统地读/写块的方法。 Streams提供该功能。

缓冲区

我们可以通过初始化Buffer对象轻松创建缓冲区。

 `let buffer = new Buffer(10); # 10 is size of buffer console.log(buffer); # prints <Buffer 00 00 00 00 00 00 00 00 00 00>` 

在较新版本的Node.js(> 8)中,您也可以执行此操作。

 `let buffer = new Buffer.alloc(10); console.log(buffer); # prints <Buffer 00 00 00 00 00 00 00 00 00 00>` 

如果我们有一些数据已经像数组或任何集合,我们可以使用它创建一个缓冲区。

 `let name = 'Node JS DEV'; let buffer = Buffer.from(name); console.log(buffer) # prints <Buffer 4e 6f 64 65 20 4a 53 20 44 45 5>` 

缓冲区几乎没有重要的方法,如buffer.toString()和buffer.toJSON()来查看存储在其中的数据。

我们在优化代码的过程中不会创建原始缓冲区。 Node JS和V8 Engine通过在使用流或网络套接字时创建内部缓冲区(队列)来实现这一点。

简单来说,流就像是Node JS对象上的科幻门户。在计算机网络中,入口是一个传入的动作,出口是传出的。我们在此后使用这些术语。

有四种类型的流可用:

  • 可读流(您可以从中读取数据)
  • 可写流(您可以将数据输入其中)
  • 双工流(对读取和写入都开放)
  • 转换流(用于处理入口/出口的数据的自定义双工流(压缩,有效性检查))

这一行可以准确地说明为什么应该使用流。

流API的一个重要目标,特别是stream.pipe()方法,是将数据缓冲限制在可接受的水平,使得不同速度的源和目标不会阻塞可用内存。

你需要一些方法来操作而不会压倒系统。这就是我们在本文的最初句子中谈到的内容。 礼貌:节点JS文档

在上图中,我们有两种类型的流。可读和可写。 .pipe()方法是一个非常基本的原语,用于将可读流附加到可写流。如果你不理解上面的图表,那很好。看完我们的例子后,你可以回到这里,一切都对你有意义。管道是一种引人注目的机制,下面我们用两个例子说明它。

解决方案1(带流的Naive文件副本)

让我们设计一个解决方案来克服我们之前讨论过的巨大的文件复制问题。为了实现这一点,我们可以创建两个流并实现此过程。

  1. 在可读流上侦听数据块
  2. 在可写流上写下该块
  3. 跟踪复制操作进度

让我们将程序命名为 streams_copy_basic.js Streams,无需管道

在此程序中,我们要求用户输入两个文件(源和目标)并创建两个流以将块从可读源复制到可写目标。我们声明了更多的变量来跟踪进度并将其打印到标准输出(这里是控制台)。我们订阅了以下几个活动:

' data ':在读取数据块时调用

' end ':从可读流中读取块时调用

'error': 如果在阅读过程中有任何问题,则调用

运行此程序,我们可以成功复制一个大文件(在我的情况下为7.4 GB)

 `$ time node streams_copy_basic.js cartoonMovie.mkv ~/Documents/4kdemo.mkv` 

但是,有一个问题。观察计算机上活动/进程监视器中Node.js进程使用的内存。 仅以88%的副本查看Node进程使用的内存

4.6GB?在这种情况下,我们的文件复制程序的RAM使用是疯狂的,可能会阻止其他应用程序

为什么会这样?

如果您在上面的图片中观察到磁盘的读写速率,那么有一些东西可以引起您的注意。

磁盘读取: 53.4 MiB / s

磁盘写入: 14.8 MiB / s

这意味着生产者以更快的速度生产,消费者无法赶上节奏。保存数据块的计算机读取,将多余的数据存储到机器的RAM中。这就是内存飙升的原因。

这个程序在我的机器上运行了3分16秒..

 `17.16s user 25.06s system 21% cpu 3:16.61 total` 

解决方案2(带流和自动背压的文件复制)

为了克服上述问题,我们可以修改程序,自动调整磁盘的读写速度。这种机制是背压。我们不需要做太多。只需将可读流传输到可写流中即可。 Node.js负责对系统进行反压。

让我们将程序命名为 streams_copy_efficient.js

在这里,我们用单个语句替换了块写入操作。

 `readabale.pipe(writeable); // Auto pilot ON!` 

管道 是所有魔法将要发生的原因。它控制磁盘的读写速度,因此不会阻塞内存(RAM)。

现在运行程序:

 `$ time node streams_copy_efficient.js cartoonMovie.mkv ~/Documents/4kdemo.mkv` 

我们这次也在复制相同的大文件(7.4 GB)。让我们看看记忆趋势是怎样的。 管道是Node中的魔杖

哇!现在Node进程只消耗61.9MiB RAM。如果您观察到磁盘的读写速率:

磁盘读取: 35.5 MiB / s

磁盘写入: 35.5 MiB / s

在任何给定时间,由于背压,读写速度相同。奖励是这个优化的程序比前一个程序快13秒。

 `12.13s user 28.50s system 22% cpu 3:03.35 total` 

由于Node JS流和管道,内存负载减少了 98.68% ,执行时间也减少了。这就是为什么我们说 管道 是一个强大的构造。

61.9 MiB是读取流创建的缓冲区大小。我们还可以使用可读流上的 read 方法为该缓冲区块分配自定义大小。

 `const readabale = fs.createReadStream(fileName);` 
 `readable.read(no_of_bytes_size);` 

这种技术可以用于优化处理I / O的许多事情,而不是在本地复制文件:

  • 来自Kafka并进入数据库的数据流。
  • 来自文件系统的数据流,即时压缩并写入磁盘。
  • 还有很多...

源代码(Git)

想在你的机器上测试这整件事吗?您可以在我的git存储库中找到所有上述代码示例。 narenaryan /节点背压-内部
一个补充代码库,用于说明Medium ... github.com 上的节点背压文章

结论:

我写这篇文章的主要动机是展示我们可以多快地编写性能不佳的坏程序,即使NodeJS为我们提供了很好的API。如果我们更多地关注内置工具,我们可以改变软件的运行方式。

你可以在这里找到更多关于背压的信息。 流中的背压| Node.js的
Node.js®是基于Chrome的V8 JavaScript引擎构建的JavaScript运行时。 nodejs.org

希望你喜欢这篇文章。如果您有任何疑问,请在此处或我的推特墙上发表评论。 https://twitter.com/@Narenarya3

祝你今天愉快 :)

参考文献:

流| Node.js v11.1.0文档
Node.js API创建的所有流都只在字符串和Buffer(或Uint8Array)对象上运行。有可能...... nodejs.org

文件系统| Node.js v11.1.0文档
在POSIX系统上,对于每个进程,内核都维护一个当前打开的文件和资源的表。每个打开的文件... nodejs.org
缓冲区| Node.js v11.1.0文档
将一个数字作为第一个参数传递给Buffer()(例如new Buffer(10))分配一个指定的新的Buffer对象... nodejs.org

查看英文原文

查看更多文章

公众号:银河系1号

联系邮箱:public@space-explore.com

(未经同意,请勿转载)