Node.JS TCP上的无界并发/stream反压

据我了解,Node的IO模型的后果之一是无法告诉Node进程(例如)通过TCP套接字接收数据,阻塞,一旦你连接你的接收事件处理程序(或否则开始监听数据)。

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

接收者不能告诉节点减慢读取速度,否则TCP将会允许TCP内置的stream量控制机制启动,并向发送者表明需要减慢速度。

首先,我所描述的到目前为止是准确的? 有什么我错过了,允许节点避免这种情况?

节点stream的特点之一是自动处理背压。

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

那么其次,Node Streams自动背压能以某种方式在这种情况下工作来处理一个跟不上的接收器?

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

是唯一的解决这个问题的节点(和浏览器使用websockets)在应用程序级别实现手动stream量控制机制,明确地告诉发送进程减慢?

为了回答你的第一个问题,我相信你的理解是不准确的,至less在stream之间传输数据的时候是这样。 事实上,如果你阅读了pipe()函数的文档,你会发现它明确地说它会自动pipe理stream,以便“目标不会被快速可读的stream所淹没”。

pipe()的底层实现正在为您处理所有繁重的工作。 inputstream(一个可读stream)将继续发出数据事件,直到输出stream(一个可写stream)已满。 顺便说一句,如果我没有记错的话,那么当你试图写入当前不能处理的数据的时候,这个stream将返回false。 此时,pipe道将暂停() Readablestream,这将阻止它发送进一步的数据事件。 因此,事件循环不会填满和耗尽你的记忆,也不会发射简单地丢失的事件。 相反,Readable将保持暂停状态,直到Writablestream发出排放事件。 此时,pipe道将恢复()可读stream。

秘密的酱汁是把一条小溪stream到另一条小溪,这是自动pipe理背压的。 这有希望回答你的第二个问题,那就是Node可以通过简单的pipe道stream来自动pipe理这个问题。

最后,实际上不需要手动实现(除非您正在从头开始编写新的stream),因为它已经为您提供了。 🙂

处理所有这些并不容易,正如Node上发布Node2中的streams2 API所承认的一样 。 这是一个很好的资源,当然提供了比我在这里更多的信息。 一个小问题,不是很明显,你应该知道,从这里的文档和向后兼容的原因:

如果你附加一个数据事件监听器,那么它将把stream切换到stream模式,数据将尽快传递给你的处理程序。

所以只需要注意,附加数据事件监听器试图观察stream中的某些内容将从根本上改变stream处理方式。 问我怎么知道 。