为什么无界缓冲会失败
当生产者生成工作的速度快于消费者处理速度时,差额必须有个去处。无界队列把它放进内存:延迟增长,内存增长,最终进程崩溃。背压让这种不匹配在生产者处可见,而不是藏在队列里。
流中的背压
Node.js 流通过 write() 返回 false 和 drain 事件表示背压。正确的生产者会等待 drain 后再继续写入:
import { createReadStream, createWriteStream } from 'node:fs';
import { pipeline } from 'node:stream/promises';
await pipeline(
createReadStream('large-input.log'),
createWriteStream('copy.log'),
);
pipeline() 会在各阶段之间传播错误并遵守背压。手动循环调用 write() 而不检查返回值,可能把整个文件缓冲进内存。
队列与服务中的背压
有界队列把策略显式化:等待、拒绝或丢弃。下面的例子在接收新元素前等待容量:
class BoundedQueue {
constructor(capacity) {
this.capacity = capacity;
this.items = [];
this.waiters = [];
}
async push(item) {
while (this.items.length >= this.capacity) {
await new Promise((resolve) => this.waiters.push(resolve));
}
this.items.push(item);
}
}
何时使用
在生产者与消费者速率可能分叉的每个边界使用背压:HTTP 上传、数据库批处理、日志传输、UI 事件队列。与限流相比,背压是每个消费者的容量信号,而限流是共享的准入策略。二者可以组合:限流保护服务,背压保护服务内部的内存。
陷阱
没有超时的背压可能在消费者永不推进时死锁。应为有界等待配合截止时间与指标。同时测量队列深度和消费者延迟;背压是安全机制,不能替代足够的容量。