12

据我了解,Node 事件 IO 模型的后果之一是,一旦您连接了接收事件处理程序(或否则开始监听数据)。

如果接收方不能足够快地处理传入的数据,则可能会导致“无限并发”,即引擎盖下的节点继续尽可能快地从套接字读取数据,在事件循环上调度新的数据事件而不是阻塞套接字,直到进程最终耗尽内存并死亡。

接收方不能告诉节点放慢它的读取速度,否则会允许 TCP 的内置流量控制机制启动并向发送方指示它需要放慢速度。

首先,到目前为止我所描述的是否准确?有什么我错过的东西可以让节点避免这种情况吗?

Node Streams 备受吹捧的功能之一是自动处理背压。

AFAIK,(tcp 套接字的)可写流可以判断它是否需要减速的唯一方法是查看socket.bufferSize(指示写入套接字但尚未发送的数据量)。鉴于接收端的 Node 总是尽可能快地读取,这只能表明发送方和接收方之间的网络连接速度较慢,而不是接收方是否跟不上。

其次,Node Streams 自动背压能否在这种情况下以某种方式工作以处理无法跟上的接收器?

这个问题似乎也影响了通过 websockets 接收数据的浏览器,原因类似,websockets API 没有提供一种机制来告诉浏览器减慢它从套接字读取的速度。

对于 Node(和使用 websockets 的浏览器)来说,这个问题的唯一解决方案是在应用程序级别实现手动流控制机制,明确告诉发送进程放慢速度吗?

4

1 回答 1

7

为了回答您的第一个问题,我相信您的理解并不准确——至少在流之间传输数据时不准确。事实上,如果您阅读pipe() 函数的文档,您会看到它明确表示它会自动管理流,因此“目标不会被快速可读的流淹没”。

pipe() 的底层实现为您处理所有繁重的工作。输入流(可读流)将继续发出数据事件,直到输出流(可写流)已满。顺便说一句,如果我没记错的话,当您尝试写入当前无法处理的数据时,流将返回 false。此时,管道将暂停()可读流,这将阻止它发出进一步的数据事件。因此,事件循环不会填满和耗尽您的内存,也不会发出简单丢失的事件。相反,Readable 将保持暂停,直到 Writable 流发出耗尽事件。此时,管道将resume()可读流。

秘诀是将一股水流输送到另一股水流中,它会自动为您管理背压。这有望回答您的第二个问题,即 Node 可以并且确实通过简单的管道流来自动管理此问题。

最后,实际上没有必要手动实现它(除非您从头开始编写新流),因为它已经为您提供了。:)

处理所有这一切并不容易,正如在 Node.js 中宣布Stream2 API 的 Node 博客文章中所承认的那样。这是一个很好的资源,当然比我在这里提供的信息要多得多。但是,出于向后兼容性的原因,您应该从此处的文档中知道一个并不完全显而易见的小问题:

如果您附加一个数据事件侦听器,那么它将把流切换到流动模式,并且数据将在可用时立即传递给您的处理程序。

因此请注意,附加数据事件侦听器以尝试观察流中的某些内容将从根本上将流更改为旧的做事方式。问我怎么知道的。

于 2015-06-29T22:25:08.533 回答