You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多
文档控制台
免费开始使用

如何通过Node.js跨服务器调用Child Process实现重型任务分流?

这绝对是个非常实用的负载拆分场景——把Web服务器从重型计算任务里解放出来,专注做流量分发,能极大提升系统的稳定性和吞吐量。我来给你详细拆解实现思路,附上手写的代码示例,分分钟就能跑起来。

核心架构思路

服务器A作为前端入口,只负责:

  • 接收用户的上传请求
  • 把任务指令/数据转发给服务器B
  • 接收B返回的处理结果,再分发给用户或存入存储

服务器B作为任务 worker,只负责:

  • 监听来自A的任务请求
  • 启动Child Process执行重型计算(比如多媒体转PDF、视频处理)
  • 把处理后的Buffer结果返回给A
实现方案:从简单到进阶

方案1:HTTP API同步调用(快速上手)

这是最容易实现的方式,直接用HTTP请求在A和B之间通信,适合中小型流量场景。

服务器B(任务处理服务)

我们用Express搭建一个简单的任务接口,接收媒体数据,调用Child Process生成PDF,最后返回Buffer:

const express = require('express');
const { execFile } = require('child_process');
const fs = require('fs');
const path = require('path');
const app = express();

// 配置解析大请求体(处理多媒体文件)
app.use(express.json({ limit: '100mb' }));

// 临时目录存上传的媒体文件(记得提前创建)
const tempDir = path.join(__dirname, 'temp');
if (!fs.existsSync(tempDir)) fs.mkdirSync(tempDir);

// 处理生成PDF的任务接口
app.post('/tasks/generate-pdf', (req, res) => {
  const { mediaBase64 } = req.body;
  const tempMediaPath = path.join(tempDir, `${Date.now()}.html`); // 假设上传的是HTML/媒体页面

  // 把Base64转成临时文件
  fs.writeFileSync(tempMediaPath, mediaBase64, 'base64');

  // 调用wkhtmltopdf生成PDF(需要先在服务器B安装这个工具)
  execFile('wkhtmltopdf', [tempMediaPath, '-'], (error, stdout, stderr) => {
    // 清理临时文件,避免占用空间
    fs.unlinkSync(tempMediaPath);

    if (error) {
      console.error(`任务失败: ${stderr}`);
      return res.status(500).json({ success: false, message: 'PDF生成失败' });
    }

    // stdout就是PDF的Buffer,直接返回给A
    res.setHeader('Content-Type', 'application/pdf');
    res.send(stdout);
  });
});

app.listen(3001, '0.0.0.0', () => {
  console.log('服务器B 任务处理服务启动在3001端口');
});

服务器A(Web前端服务)

用Express+Multer处理用户上传,然后调用服务器B的接口拿到Buffer,返回给用户:

const express = require('express');
const axios = require('axios');
const multer = require('multer');
const app = express();

// 用内存存储上传的文件,避免写本地磁盘
const upload = multer({ storage: multer.memoryStorage() });

// 用户上传并触发PDF生成的接口
app.post('/user/generate-report', upload.single('media'), async (req, res) => {
  try {
    // 把上传的文件转成Base64传给服务器B
    const mediaBase64 = req.file.buffer.toString('base64');

    // 调用服务器B的任务接口
    const bResponse = await axios.post('http://你的服务器B_IP:3001/tasks/generate-pdf', {
      mediaBase64
    }, {
      responseType: 'arraybuffer' // 确保拿到二进制Buffer
    });

    // 把PDF返回给用户(或者存到CDN后返回链接)
    res.setHeader('Content-Type', 'application/pdf');
    res.setHeader('Content-Disposition', 'attachment; filename="report.pdf"');
    res.send(bResponse.data);
  } catch (err) {
    console.error(`调用服务器B失败: ${err.message}`);
    res.status(500).json({ success: false, message: '生成报告失败' });
  }
});

app.listen(3000, () => {
  console.log('服务器A Web服务启动在3000端口');
});

方案2:异步消息队列(高流量场景必备)

如果你的流量很大,同步等待B返回会占用A的连接资源,这时候用消息队列做异步处理更合适:

  1. 服务器A接收用户上传后,把任务信息(比如文件在共享存储的路径、任务类型)放到消息队列
  2. 服务器B监听队列,拿到任务后执行Child Process处理
  3. 处理完成后,B把结果存到共享存储,然后给A发通知(或者用户通过查询接口获取结果)

推荐用bullmq这个Node.js的消息队列库,上手简单,支持重试、延迟任务等特性。

关键优化建议
  • 用共享存储替代直接传数据:如果媒体文件很大,直接在HTTP请求里传Base64会很慢,建议A把上传的文件放到S3/NFS这类共享存储,然后把文件路径传给B,B直接读取处理,效率提升很多。
  • 限制Child Process资源:在B服务器上,给Child Process设置CPU、内存限制(比如用Docker容器隔离,或者用child_process.spawnstdio流处理大输出,避免内存溢出)。
  • 错误重试与监控:给任务加重试机制,同时监控B的任务执行状态,比如用Prometheus+Grafana监控任务成功率、处理时间。
  • 横向扩展服务器B:如果任务量太大,可以启动多个B节点,用消息队列自动分发任务,或者用Nginx做负载均衡。

内容的提问来源于stack exchange,提问作者Chetan Chauhan

火山引擎 最新活动