据我所知,Node的事件IO模型的后果之一是,一旦连接了接收事件处理程序(或以其他方式开始侦听数据),无法告诉正在(例如)通过TCP套接字接收数据的Node进程阻塞。
如果接收方不能足够快地处理传入的数据,就会导致"无界并发",即底层节点继续尽可能快地从套接字读取数据,在事件循环中调度新的数据事件,而不是在套接字上阻塞,直到进程最终耗尽内存并死亡。
接收端不能告诉节点减慢读取速度,否则TCP内置的流量控制机制就会启动,并向发送端指示它需要减慢读取速度。
首先,到目前为止我所描述的是准确的吗?我是否遗漏了一些东西,使node可以避免这种情况?
Node Streams最受欢迎的特性之一是自动处理背压。
AFAIK,一个可写流(tcp套接字)可以判断它是否需要减速的唯一方法是查看socket.bufferSize
(指示写入套接字但尚未发送的数据量)。鉴于接收端的Node总是尽可能快地读取,这只能表明发送方和接收方之间的网络连接速度较慢,而不能表明接收方是否跟不上。
那么第二,节点流自动反压是否可以在这种情况下以某种方式工作来处理无法跟上的接收器?
似乎这个问题也影响了通过websockets接收数据的浏览器,出于类似的原因,websockets API没有提供一种机制来告诉浏览器减慢从套接字读取的速度。
对于Node(和使用websockets的浏览器)来说,解决这个问题的唯一方法是在应用程序级别实现手动流量控制机制,明确地告诉发送进程减速吗?
回答你的第一个问题,我认为你的理解是不准确的——至少在流之间传输数据时是不准确的。实际上,如果您阅读pipe()函数的文档,就会看到它显式地说它会自动管理流,以便"目的地不会被快速可读流淹没"。
pipe()的底层实现为您处理了所有繁重的工作。输入流(可读流)将继续发出数据事件,直到输出流(可写流)满为止。顺便说一句,如果我没记错的话,当您试图写入当前无法处理的数据时,流将返回false。此时,管道将暂停()可读流,这将防止它发出进一步的数据事件。因此,事件循环不会填满和耗尽您的内存,也不会发出简单丢失的事件。相反,可读流将保持暂停状态,直到可写流发出一个drain事件。此时,管道将恢复()可读流。
秘诀是用管道将一股水流连接到另一股水流,这样就可以自动管理背压。这有望回答您的第二个问题,即Node可以并且确实通过简单的管道流来自动管理这些。
最后,真的没有必要手动实现它(除非你从头开始编写一个新的流),因为它已经为你提供了。:)
处理所有这些并不容易,正如Node博客文章中宣布Node中的streams2 API所承认的那样。这是一个很好的资源,当然提供了比我在这里多得多的信息。有一个不太明显的小问题,你应该知道,从这里的文档和向后兼容性的原因:
如果您附加了一个数据事件侦听器,那么它将把流切换到流动模式,并且数据将在可用时立即传递给您的处理程序。
因此,请注意,在试图观察流中的某些内容时附加数据事件侦听器将从根本上改变流以旧的方式处理事情。