侧边栏壁纸
博主头像
colo

欲买桂花同载酒

  • 累计撰写 1823 篇文章
  • 累计收到 0 条评论

设计高并发Node.js文件上传服务,支持大文件分片上传和断点续传

2025-12-12 / 0 评论 / 4 阅读

题目

设计高并发Node.js文件上传服务,支持大文件分片上传和断点续传

信息

  • 类型:问答
  • 难度:⭐⭐⭐

考点

Node.js流处理,异步并发控制,文件系统操作,错误处理与恢复,性能优化

快速回答

实现要点:

  • 使用multipart/form-data分片上传,前端将文件切割为固定大小块
  • 服务端通过fs.createWriteStream以流式写入分片文件,避免内存溢出
  • 用Redis记录分片上传状态(文件ID、分片索引、MD5校验值)
  • 合并文件时使用fs.createReadStream管道流按序拼接
  • 通过背压机制控制并发流量,结合p-limit限制并行操作数
## 解析

1. 核心原理

大文件上传需解决内存限制和网络中断问题:

  • 分片上传:将文件切割为2-5MB的块,并行上传提升速度
  • 断点续传:服务端记录已接收分片,客户端中断后仅需重传缺失分片
  • 流式处理:Node.js的Stream API实现非阻塞I/O,处理GB级文件不占内存

2. 代码实现示例

分片上传接口 (Express.js):

const fs = require('fs');
const pipeline = util.promisify(stream.pipeline);

app.post('/upload-chunk', async (req, res) => {
  const { fileId, chunkIndex, totalChunks } = req.body;
  const chunkStream = req.files.chunk.data;

  // 写入分片
  const chunkPath = `./temp/${fileId}_${chunkIndex}`;
  await pipeline(
    chunkStream,
    fs.createWriteStream(chunkPath)
  );

  // 记录到Redis
  await redisClient.hSet(`file:${fileId}`, `chunk_${chunkIndex}`, 'completed');

  res.status(200).json({ status: 'success' });
});

文件合并接口:

app.post('/merge', async (req, res) => {
  const { fileId, fileName } = req.body;
  const finalPath = `./uploads/${fileName}`;

  // 获取所有分片路径并排序
  const chunks = await fsPromises.readdir(`./temp`);
  const targetChunks = chunks
    .filter(name => name.startsWith(fileId))
    .sort((a, b) => {
      return parseInt(a.split('_')[1]) - parseInt(b.split('_')[1]);
    });

  // 流式合并
  const writeStream = fs.createWriteStream(finalPath);
  for (const chunk of targetChunks) {
    const readStream = fs.createReadStream(`./temp/${chunk}`);
    await new Promise((resolve) => {
      readStream.pipe(writeStream, { end: false });
      readStream.on('end', resolve);
    });
  }
  writeStream.end();

  // 清理临时文件
  await Promise.all(targetChunks.map(chunk => 
    fsPromises.unlink(`./temp/${chunk}`)
  ));
});

3. 最佳实践

  • 内存优化:始终使用Stream API,避免fs.readFile加载整个文件
  • 并发控制:使用p-limit限制并行分片处理数量(建议CPU核心数×2)
  • 数据一致性
    • 分片上传前计算MD5,合并时校验
    • 使用数据库事务保证状态一致性
  • 错误恢复
    • 为每个上传任务创建唯一ID
    • Redis存储结构:HSET file:1234 chunk_0 'completed'
    • 客户端查询缺失分片重传

4. 常见错误

  • 内存泄漏:未处理背压导致Buffer堆积,需监听drain事件
  • 文件损坏:分片合并顺序错误,必须按索引排序后拼接
  • 僵尸文件:未清理中断上传的临时分片,应添加定时任务清理超时文件
  • 并发冲突:多进程同时写入同一文件导致崩溃,需用文件锁(fs-extralockfile

5. 扩展知识

  • 流量整形:通过stream.throttle限制上传带宽
  • 分布式存储:分片存储到不同服务器(如MinIO),通过gRPC合并
  • 前端优化:Web Worker计算文件hash,File.slice()切割分片
  • 云原生方案:AWS S3分片上传API(CreateMultipartUpload