第7章 7.3 流的背压与暂停恢复

📖 本文是系列文章「Node.js 从入门到精通」的第 33 章,点击查看完整目录


🎯 开场 3 分钟:为什么要学这个?

想象一下这个场景:

你家楼下有个快递柜,取件速度是每秒 5 个,但快递员投放速度是每秒 20 个。结果会怎样?

没错,快递柜会爆满,快递员要么停止投放,要么把快递堆在地上乱成一团。

这就是背压(Backpressure)问题。

上一章我们学了 Stream 的基本用法,知道怎么用 pipe 把数据从 A 传到 B。但现实世界有个残酷的真相:数据的生产速度和消费速度往往不匹配。

  • 你从网络下载一个大文件,想同时解压它 → 下载速度 > 解压速度,内存爆了
  • 你从数据库读取 1000 万条数据,想同时写入文件 → 读的速度 > 写的速度,内存爆了
  • 你用 pipe 管道连接多个流 → 某个环节卡住了,整条链都在堆积数据

这一章我们要解决的核心问题就是:当数据来得太快、来不及处理时,怎么办?

学完本文,你就能:
- 理解什么是「背压」,为什么它会拖垮你的程序
- 用 highWaterMark 控制缓冲区大小
- 用 pause() / resume() 让数据流「可快可慢」
- 用异步迭代器优雅地消费流数据


🧱 基础 25 分钟:核心概念(小白视角)

7.3.1 什么是背压?先别急着看定义

生活类比 🎯

想象你在厨房洗碗,水龙头哗哗地开着,下面水池塞子堵住了但没完全堵死,水流得很慢。

如果你不管,继续让水龙头开最大,结果会怎样?水会溢出来,洒一地。

聪明人的做法:先把水龙头关小一点(减慢生产),等水池里的水排掉一些,再把水龙头开大(加快生产)。

这就是背压——当下游处理不过来时,上游要「喘口气」,别一股脑儿往下灌。

7.3.2 highWaterMark:快递柜的容量上限

Node.js 的 Stream 给每个流都设置了一个「缓冲区容量上限」,叫 highWaterMark

是什么:一个数字,表示缓冲区里最多能缓存多少数据(单位是字节或对象数量,取决于流类型)。

为什么要用:防止数据无限堆积,把服务器内存撑爆。你可以理解为快递柜的总格子数。

怎么用

const { Readable, Writable } = require('stream');

// 创建一个读取流,缓冲区最多放 16KB 数据(默认 16KB)
const readable = Readable({
highWaterMark: 16 * 1024  // 16KB
});

// 创建一个写入流,缓冲区最多放 8KB 数据
const writable = Writable({
highWaterMark: 8 * 1024   // 8KB
});

console.log('读取流容量:', readable.readableHighWaterMark);  // 16384
console.log('写入流容量:', writable.writableHighWaterMark);  // 8192

这行在干嘛:创建两个流,并打印它们各自的缓冲区容量上限。

7.3.3 pause() / resume():让数据流「踩刹车」

生活类比 🎯

你在自动售货机前买东西,投币后商品掉出来。如果你接商品的速度太慢,售货机就会「卡住」,等你拿走一个,它才掉下一个。

pause() 就是告诉上游:「慢点发,我还没处理完」
resume() 就是告诉上游:「好了,可以继续发了」

是什么:Readable 流的方法,用来暂停和恢复数据发射。

为什么要用:当你处理数据的速度跟不上数据到来的速度时,需要手动控制节奏。

怎么用

const { Readable } = require('stream');

// 创建一个自定义的读取流,每 100ms 发送一个数字
const myStream = new Readable({
highWaterMark: 2,  // 缓冲区只能放 2 个数字,很小!
read() {
let count = 0;
const interval = setInterval(() => {
  count++;
  console.log(`发送数据: ${count}`);
  this.push(count);  // 往缓冲区放数据

  if (count >= 10) {
    clearInterval(interval);
    this.push(null);  // 告诉消费者:我没数据了
  }
}, 100);
}
});

// 模拟慢速消费者:每处理一个数据要 300ms
myStream.on('data', (chunk) => {
console.log(`开始处理: ${chunk},预计耗时 300ms`);

// 300ms 后才处理完,此时缓冲区已经在堆积数据了
setTimeout(() => {
console.log(`处理完成: ${chunk}`);
}, 300);
});

myStream.on('end', () => {
console.log('数据流结束');
});

这行在干嘛:创建一个每秒发送 10 个数据的流,模拟「生产快、消费慢」的场景。运行后你会看到数据堆积,缓冲区很快达到上限(2 个)。

7.3.4 手动实现背压控制

生活类比 🎯

回到洗碗的例子。聪明人会观察水池里的水:快满了就关水龙头,快排空了就开大。

代码来了

const { Readable, Writable } = require('stream');

// 创建一个慢速消费者(每秒只能处理 3 个数据)
const slowConsumer = new Writable({
objectMode: true,
highWaterMark: 2,  // 写缓冲区只能放 2 个对象
write(chunk, encoding, callback) {
console.log(`处理数据: ${chunk}`);
// 模拟耗时操作(300ms)
setTimeout(() => {
  console.log(`处理完成: ${chunk}`);
  callback();  // 告诉上游:我处理完了,可以发下一个
}, 300);
}
});

// 创建一个数据源(每秒发送 10 个数据)
const dataSource = new Readable({
objectMode: true,
highWaterMark: 2,
read() {
let count = 0;
const interval = setInterval(() => {
  count++;
  this.push(count);
  if (count >= 20) {
    clearInterval(interval);
    this.push(null);
  }
}, 100);
}
});

// 手动实现背压控制
let canContinue = true;

dataSource.on('data', (chunk) => {
if (!canContinue) {
// 缓冲区满了,暂停读取
dataSource.pause();
console.log('--- 暂停读取,等待消费 ---');
return;
}

const needToPause = !slowConsumer.write(chunk);  // 写入,返回 false 表示缓冲区满了

if (needToPause) {
canContinue = false;
dataSource.pause();
console.log('--- 缓冲区满,暂停读取 ---');

// 等 drain 事件再恢复
slowConsumer.once('drain', () => {
  console.log('--- 缓冲区已清空,恢复读取 ---');
  canContinue = true;
  dataSource.resume();
});
}
});

dataSource.on('end', () => {
console.log('数据源发送完毕');
});

关键点解释

  • writable.write(chunk) 返回 true 表示「我还能继续接收」,返回 false 表示「我缓冲区满了,你等等」
  • 当返回 false 时,调用 readable.pause() 暂停上游
  • 当缓冲区清空时,会触发 drain 事件,此时调用 readable.resume() 恢复上游

7.3.5 异步迭代器:更优雅的消费方式

生活类比 🎯

传统的 on('data') 方式像是在餐厅门口等位,服务员喊「下一位」你才能进去,容易造成排队混乱。

异步迭代器方式像是取号排队,叫到你的号你再进去,有序且不浪费资源。

是什么:ES2018 引入的 for await...of 语法,可以直接遍历 Stream。

为什么要用:代码更简洁,而且天然支持背压——每迭代一次才处理一个数据块。

怎么用

const { Readable } = require('stream');

// 创建一个数据流,每 200ms 发送一个数字
const myStream = new Readable({
objectMode: true,
highWaterMark: 2,
read() {
let count = 0;
const interval = setInterval(() => {
  count++;
  this.push(count);
  if (count >= 10) {
    clearInterval(interval);
    this.push(null);
  }
}, 200);
}
});

async function consumeStream() {
console.log('开始异步迭代消费...');

// 关键:for await...of 自动处理背压
for await (const chunk of myStream) {
console.log(`收到数据: ${chunk},开始处理...`);
await new Promise(resolve => setTimeout(resolve, 500));  // 模拟耗时 500ms
console.log(`处理完成: ${chunk}`);
}

console.log('消费完毕');
}

consumeStream();

这行在干嘛:用 for await...of 异步迭代消费流数据,每 500ms 才处理一个,而数据是每 200ms 来一个,会自然产生背压。


🔥 实战 35 分钟:3 个递进的小项目

📦 项目 1:实现一个带背压控制的文件复制器(5 分钟)

需求:把一个大文件复制到另一个位置,要求手动实现背压,不让内存爆掉。

完整代码

const fs = require('fs');
const { Readable, Writable } = require('stream');

// 源文件和目标文件(用自己的任意一个大文件测试)
const sourceFile = 'source.txt';
const targetFile = 'target.txt';

// 先创建测试文件(10000 行文本)
const testContent = '这是第 ${i} 行测试数据,用于演示背压控制\n'.repeat(10000);
fs.writeFileSync(sourceFile, testContent);
console.log('测试文件已创建,共 10000 行');

// 创建读取流
const readable = fs.createReadStream(sourceFile, {
highWaterMark: 64 * 1024  // 64KB 缓冲区
});

// 创建写入流
const writable = fs.createWriteStream(targetFile, {
highWaterMark: 32 * 1024  // 32KB 缓冲区
});

let totalBytes = 0;
let canContinue = true;

readable.on('data', (chunk) => {
if (!canContinue) {
readable.pause();
return;
}

const bytesWritten = writable.write(chunk);
totalBytes += chunk.length;

if (!bytesWritten) {
canContinue = false;
readable.pause();

writable.once('drain', () => {
  canContinue = true;
  readable.resume();
});
}
});

readable.on('end', () => {
writable.end(() => {
console.log(`复制完成,共写入 ${totalBytes} 字节`);
console.log(`源文件大小: ${fs.statSync(sourceFile).size} 字节`);
console.log(`目标文件大小: ${fs.statSync(targetFile).size} 字节`);
});
});

预期输出

测试文件已创建,共 10000 行
复制完成,共写入 420000 字节
源文件大小: 420000 字节
目标文件大小: 420000 字节

一句话解释:手动监听 write() 的返回值,当返回 false 时暂停读取,等 drain 事件恢复,实现背压控制。


📦 项目 2:实时处理 CSV 数据流(15 分钟)

需求:从 CSV 文件读取用户数据(姓名、年龄、分数),实时计算平均分,并写入结果文件。要求用背压控制内存使用。

准备一个 CSV 文件 students.csv

name,age,score
张三,18,85
李四,20,92
王五,19,78
赵六,21,88
钱七,17,95
孙八,20,81
周九,18,90
吴十,19,86

完整代码

const fs = require('fs');
const { Readable, Writable } = require('stream');

// ============ 第一部分:创建 CSV 解析流 ============
class CSVParser extends Readable {
constructor(options) {
super({ objectMode: true, ...options });
this.buffer = '';
}

_read() {
// 模拟每 100ms 发送一批数据
const chunk = `name,age,score\n张三,18,85\n李四,20,92\n王五,19,78\n赵六,21,88\n钱七,17,95\n孙八,20,81\n周九,18,90\n吴十,19,86`;

const lines = chunk.split('\n');
let index = 0;

const sendLine = () => {
  if (index >= lines.length) {
    this.push(null);
    return;
  }

  const line = lines[index++];
  const isSent = this.push(line);  // 返回 false 时会暂停

  if (!isSent) {
    // 缓冲区满了,等一会儿再发
    setTimeout(sendLine, 50);
  } else {
    // 正常发送下一行
    setImmediate(sendLine);
  }
};

sendLine();
}
}

// ============ 第二部分:数据处理器 ============
class DataProcessor extends Writable {
constructor(options) {
super({ objectMode: true, ...options });
this.students = [];
this.sum = 0;
this.count = 0;
}

_write(chunk, encoding, callback) {
const line = chunk.toString().trim();
if (!line || line === 'name,age,score') {
  // 跳过表头和空行
  callback();
  return;
}

const [name, age, score] = line.split(',');
this.students.push({ name, age: parseInt(age), score: parseInt(score) });
this.sum += parseInt(score);
this.count++;

console.log(`处理学生: ${name}, 年龄: ${age}, 分数: ${score}`);
console.log(`当前平均分: ${(this.sum / this.count).toFixed(2)}`);

callback();
}
}

// ============ 第三部分:结果写入器 ============
const resultStream = new Writable({
objectMode: true,
highWaterMark: 2,
write(chunk, encoding, callback) {
const result = chunk;
fs.appendFileSync('result.txt', result + '\n');
console.log(`写入结果: ${result}`);
callback();
}
});

// ============ 第四部分:组装背压管道 ============
const csvParser = new CSVParser({ highWaterMark: 2 });
const processor = new DataProcessor({ highWaterMark: 2 });

// 清空结果文件
fs.writeFileSync('result.txt', '=== 学生数据处理结果 ===\n');

let canContinue = true;

csvParser.on('data', (line) => {
if (!canContinue) {
csvParser.pause();
return;
}

const isWritten = processor.write(line);

if (!isWritten) {
canContinue = false;
csvParser.pause();

processor.once('drain', () => {
  canContinue = true;
  csvParser.resume();
});
}
});

// processor 处理完的数据写入文件
processor.on('finish', () => {
const avg = processor.sum / processor.count;
const summary = `班级平均分: ${avg.toFixed(2)}`;
resultStream.write(summary, () => {
resultStream.end();
console.log('\n处理完成!结果已写入 result.txt');
});
});

csvParser.on('end', () => {
processor.end();
});

预期输出

处理学生: 张三, 年龄: 18, 分数: 85
当前平均分: 85.00
处理学生: 李四, 年龄: 20, 分数: 92
当前平均分: 88.50
处理学生: 王五, 年龄: 19, 分数: 78
当前平均分: 85.00
...
班级平均分: 86.88
处理完成!结果已写入 result.txt

一句话解释:CSV 解析流每行数据都要经过处理器计算,处理器写入文件时如果缓冲区满,会通过背压机制暂停解析流。


📦 项目 3:做一个「网站日志实时分析器」(15 分钟)

需求:实时分析网站访问日志,统计每个小时的访问量、热门页面、错误率。模拟 1000 条日志数据流。

完整代码

const { Readable, Writable } = require('stream');

// ============ 第一部分:日志生成器 ============
class LogGenerator extends Readable {
constructor(options) {
super({ objectMode: true, ...options });
this.count = 0;
this.total = 1000;
}

_read() {
if (this.count >= this.total) {
  this.push(null);
  return;
}

// 生成随机日志
const hour = String(Math.floor(Math.random() * 24)).padStart(2, '0');
const minute = String(Math.floor(Math.random() * 60)).padStart(2, '0');
const second = String(Math.floor(Math.random() * 60)).padStart(2, '0');
const time = `${hour}:${minute}:${second}`;

const pages = ['/home', '/about', '/product', '/contact', '/api/data'];
const page = pages[Math.floor(Math.random() * pages.length)];

const statusCodes = [200, 200, 200, 200, 404, 500];  // 80% 成功率
const status = statusCodes[Math.floor(Math.random() * statusCodes.length)];

const ip = `192.168.${Math.floor(Math.random()*255)}.${Math.floor(Math.random()*255)}`;

const log = `[${time}] "${ip}" "${page}" ${status}`;
this.count++;

// 控制发送速度,模拟真实场景
const delay = Math.random() * 10;  // 0-10ms 随机延迟
setTimeout(() => {
  this.push(log);
}, delay);
}
}

// ============ 第二部分:日志分析器 ============
class LogAnalyzer extends Writable {
constructor(options) {
super({ objectMode: true, ...options });
this.hourlyStats = {};
this.pageStats = {};
this.errorCount = 0;
this.totalCount = 0;
}

_write(log, encoding, callback) {
this.totalCount++;

// 解析日志
const match = log.match(/\[(\d+):\d+:\d+\] "[\d.]+" "([^"]+)" (\d+)/);
if (!match) {
  callback();
  return;
}

const hour = match[1];
const page = match[2];
const status = parseInt(match[3]);

// 统计每小时访问量
this.hourlyStats[hour] = (this.hourlyStats[hour] || 0) + 1;

// 统计页面访问量
this.pageStats[page] = (this.pageStats[page] || 0) + 1;

// 统计错误
if (status >= 400) {
  this.errorCount++;
}

// 每 100 条输出一次进度
if (this.totalCount % 100 === 0) {
  console.log(`[进度] 已处理 ${this.totalCount} 条日志,错误率 ${(this.errorCount/this.totalCount*100).toFixed(1)}%`);
}

callback();
}

getReport() {
const topPages = Object.entries(this.pageStats)
  .sort((a, b) => b[1] - a[1])
  .slice(0, 3);

return {
  total: this.totalCount,
  errorRate: (this.errorCount / this.totalCount * 100).toFixed(2),
  hourlyStats: this.hourlyStats,
  topPages: topPages
};
}
}

// ============ 第三部分:带背压的数据管道 ============
const logGen = new LogGenerator({ highWaterMark: 10 });
const analyzer = new LogAnalyzer({ highWaterMark: 5 });

let paused = false;
let pendingData = null;

logGen.on('data', (log) => {
if (paused) {
// 缓冲区满了,先存着,等 drain 再处理
pendingData = log;
logGen.pause();
return;
}

const canContinue = analyzer.write(log);

if (!canContinue) {
paused = true;
logGen.pause();

analyzer.once('drain', () => {
  paused = false;
  if (pendingData) {
    const data = pendingData;
    pendingData = null;
    logGen.resume();
    analyzer.write(data);
  } else {
    logGen.resume();
  }
});
}
});

analyzer.on('finish', () => {
const report = analyzer.getReport();
console.log('\n========== 日志分析报告 ==========');
console.log(`总访问量: ${report.total}`);
console.log(`错误率: ${report.errorRate}%`);
console.log('各小时访问量:', report.hourlyStats);
console.log('热门页面 TOP3:', report.topPages);
console.log('===================================');
});

logGen.on('end', () => {
analyzer.end();
});

console.log('开始生成并分析 1000 条日志...');

预期输出

开始生成并分析 1000 条日志...
[进度] 已处理 100 条日志,错误率 18.0%
[进度] 已处理 200 条日志,错误率 16.5%
[进度] 已处理 300 条日志,错误率 17.3%
...
========== 日志分析报告 ==========
总访问量: 1000
错误率: 16.80%
各小时访问量: { '02': 45, '08': 38, '14': 52, ... }
热门页面 TOP3: [ [ '/home', 245 ], [ '/api/data', 203 ], [ '/product', 188 ] ]
===================================

一句话解释:日志生成器快速产生数据,分析器缓慢处理,通过背压机制协调速度差,保证数据不丢失、内存不爆。


💪 进阶 20 分钟:常见坑 + 性能小贴士

❌ 坑 1:忘记调用 callback()push(null)

// ❌ 错误:write 方法里忘了调用 callback
_writ(chunk, encoding, callback) {
processData(chunk);
// 忘记 callback() 了!
// 这会导致流永远卡住,不继续接收数据
}

// ✅ 正确:一定要调用 callback
_writ(chunk, encoding, callback) {
processData(chunk);
callback();  // 处理完毕,通知上游可以发下一个了
}

❌ 坑 2:highWaterMark 设置过大或过小

// ❌ 错误:设置太小,频繁触发背压,性能差
const slowStream = new Readable({
highWaterMark: 1  // 只能缓存 1 个数据,几乎没有缓冲作用
});

// ❌ 错误:设置太大,内存占用过高
const hugeStream = new Readable({
highWaterMark: 1024 * 1024 * 1024  // 1GB!小心内存爆炸
});

// ✅ 正确:根据实际场景设置合理值
const normalStream = new Readable({
highWaterMark: 16 * 1024  // 16KB,适合大多数场景
});

❌ 坑 3:在 for await...of 里忘记处理异步

// ❌ 错误:用了同步的 setTimeout,不起作用
for await (const chunk of myStream) {
setTimeout(() => console.log(chunk), 1000);  // 这不会等 1 秒!
// 循环会立即继续,不会暂停
}

// ✅ 正确:用 await 包装 Promise
for await (const chunk of myStream) {
await new Promise(resolve => setTimeout(resolve, 1000));  // 这里会真的暂停
console.log(chunk);
}

❌ 坑 4:混用 pipe() 和手动背压控制

// ❌ 错误:同时使用 pipe 和手动 pause/resume
source.pipe(dest);
source.pause();  // 混用会导致不可预期的行为

// ✅ 正确:二选一
// 方式 1:用 pipe(自动背压)
source.pipe(dest);

// 方式 2:手动控制背压
source.on('data', (chunk) => {
if (!dest.write(chunk)) {
source.pause();
dest.once('drain', () => source.resume());
}
});

❌ 坑 5:忘记处理 drain 事件

// ❌ 错误:没有监听 drain,直接 resume
readable.on('data', (chunk) => {
if (!writable.write(chunk)) {
readable.pause();
// 没有等 drain 就 resume,等于没暂停
readable.resume();
}
});

// ✅ 正确:一定要等 drain 事件
readable.on('data', (chunk) => {
if (!writable.write(chunk)) {
readable.pause();
writable.once('drain', () => readable.resume());  // 等缓冲区清了再恢复
}
});

💡 性能小贴士:对象模式 vs 字节模式

// 对象模式:highWaterMark 是对象数量
const objectStream = new Readable({
objectMode: true,
highWaterMark: 100  // 最多缓存 100 个对象
});

// 字节模式:highWaterMark 是字节数(更节省内存)
const byteStream = new Readable({
objectMode: false,  // 默认是字节模式
highWaterMark: 64 * 1024  // 64KB
});

// 如果处理的是字符串/Buffer 数据,用字节模式更高效
// 如果处理的是 JavaScript 对象,用对象模式更方便

🔍 调试技巧:用 inspect 查看流状态

const { Readable } = require('stream');

const myStream = new Readable({
objectMode: true,
highWaterMark: 3
});

console.log('流状态:', {
readableHighWaterMark: myStream.readableHighWaterMark,
readableLength: myStream.readableLength,  // 当前缓冲区有多少数据
readableFlowing: myStream.readableFlowing
});

// 发几个数据看看变化
myStream.push(1);
myStream.push(2);
console.log('发送 2 个数据后:', {
readableLength: myStream.readableLength
});

myStream.push(3);
console.log('发送 3 个数据后:', {
readableLength: myStream.readableLength
});

myStream.push(4);  // 超过 highWaterMark,看看会怎样
console.log('发送 4 个数据后:', {
readableLength: myStream.readableLength
});

✏️ 练习题 + 作业题

练习题(5 道,10 分钟内完成)

练习 1(2 分钟):调整缓冲区大小
- 输入:把项目 1 的 highWaterMark64 * 1024 改成 1024
- 预期输出:程序仍然正常运行,但会触发更多次 drain 事件
- 提示:观察控制台输出的 drain 次数变化

练习 2(2 分钟):添加暂停条件
- 输入:在项目 1 的基础上,当 totalBytes > 200000 时暂停 1 秒
- 预期输出:复制过程中会看到「暂停 1 秒」的日志
- 提示:在 data 事件里加一个 if 判断

练习 3(2 分钟):用异步迭代器重写项目 2
- 输入:用 for await...of 替代手动 on('data') 方式
- 预期输出:代码更简洁,功能相同
- 提示:for await...of 自动处理背压,不需要手动 pause/resume

练习 4(3 分钟):统计项目 3 中的错误日志
- 输入:在 LogAnalyzer 中额外统计 404 和 500 分别出现了多少次
- 预期输出:报告里多显示「404 次数: X」和「500 次数: Y」
- 提示:在 LogAnalyzer._write 方法里加判断

练习 5(3 分钟):分析这个报错
- 输入:运行以下代码,观察报错

const { Readable, Writable } = require('stream');
const r = new Readable({ highWaterMark: 2 });
const w = new Writable({ highWaterMark: 1 });

r.push('a');
r.push('b');
r.push('c');  // 这里会发生什么?

r.pipe(w);
  • 预期输出:观察 r.readableLength 的变化,理解缓冲区溢出
  • 提示:push() 超过 highWaterMark 时数据不会丢失,只是 read() 会被暂停

📝 作业题:做一个「实时股票行情处理器」

需求描述
模拟接收股票实时行情数据(股票代码、价格、成交量),实时计算并输出每个股票的最新均价、总成交额。

功能点
1. StockGenerator 类:每秒生成 10 条随机股票行情
2. StockProcessor 类:计算每个股票的均价和总成交额
3. StockDisplay 类:每秒输出一次所有股票的当前状态
4. 用背压机制协调三个组件的速度差

加分项
1. 当某股票价格波动超过 5% 时,高亮显示「⚠️ 剧烈波动」
2. 支持动态添加/删除关注的股票

验收标准
- 能持续运行 30 秒以上不崩溃
- 内存占用保持稳定(不随时间增长)
- 输出格式清晰,每秒刷新一次

提交方式:评论区贴代码或 GitHub 链接


📚 总结 + 资源

本文学到的 3 个核心点
1. 背压原理:当下游处理不过来时,上游要「踩刹车」暂停发送数据
2. highWaterMark:控制缓冲区容量上限,防止内存爆炸
3. pause/resume + drain:实现背压控制的三板斧

延伸学习资源
- Node.js 官方文档 - Stream API(英文)
- 《深入浅出 Node.js》- 第 5 章「内存控制」,有 Stream 内存管理的深入讲解
- 视频:What is Backpressure? - YouTube(英文,有动画演示)


🎬 互动钩子

你在项目中遇到过内存暴涨的问题吗?是用什么方法定位和解决的?评论区聊聊你的经历,老粉优先回复!


下章预告

学会了背压控制,你的流已经能「快慢自如」了。但光能传数据还不够——下一章我们要解决一个更实际的问题:大文件怎么分片上传和下载?想像一下 5GB 的视频文件,怎么切成小块并行传输,还能断点续传……敬请期待第 7.4 章!

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。