Node

Node.js 子进程实战:child_process、cluster 与进程间通信的完整指南

✎ -- 字 🕐 -- 分钟
字号

Node.js \u5b50\u8fdb\u7a0b\u5b9e\u6218\uff1achild_process\u3001cluster \u4e0e\u8fdb\u7a0b\u95f4\u901a\u4fe1\u7684\u5b8c\u6574\u6307\u5357

Node.js \u7684\u5355\u7ebf\u7a0b\u4e8b\u4ef6\u5faa\u73af\u8ba9\u5b83\u5728 I/O \u5bc6\u96c6\u573a\u666f\u4e2d\u5982\u9c7c\u5f97\u6c34\uff0c\u4f46\u9047\u5230 CPU \u5bc6\u96c6\u578b\u4efb\u52a1\u6216\u9700\u8981\u8c03\u7528\u5916\u90e8\u547d\u4ee4\u65f6\u5c31\u529b\u4e0d\u4ece\u5fc3\u4e86\u3002\u8fd9\u65f6\u5019 child_process \u6a21\u5757\u5c31\u662f\u4f60\u7684\u5f97\u529b\u52a9\u624b\u2014\u2014\u5b83\u80fd\u8ba9 Node.js \u521b\u5efa\u5b50\u8fdb\u7a0b\u3001\u8c03\u7528\u7cfb\u7edf\u547d\u4ee4\u3001\u5728\u591a\u6838 CPU \u4e0a\u5e76\u884c\u8fd0\u884c\u4efb\u52a1\uff0c\u800c cluster \u6a21\u5757\u5219\u662f\u5b83\u7684 HTTP \u5c01\u88c5\u7248\u672c\u3002\u672c\u6587\u4ece\u96f6\u5f00\u59cb\uff0c\u628a\u56db\u4e2a\u6838\u5fc3 API \u3001IPC \u901a\u4fe1\u3001cluster \u96c6\u7fa4\u548c\u751f\u4ea7\u7ea7\u6700\u4f73\u5b9e\u8df5\u4e00\u6b21\u6027\u8bb2\u900f\u3002

\u4e00\u3001\u4e3a\u4ec0\u4e48\u9700\u8981\u5b50\u8fdb\u7a0b\uff1f

Node.js \u7684 V8 \u5f15\u64ce\u5728\u5355\u7ebf\u7a0b\u4e2d\u8fd0\u884c JavaScript\uff0c\u4e8b\u4ef6\u5faa\u73af\u8d1f\u8d23\u8c03\u5ea6\u3002\u8fd9\u610f\u5473\u7740\u4e00\u4e2a CPU \u5bc6\u96c6\u578b\u64cd\u4f5c\uff08\u5982\u5927\u6570\u636e\u538b\u7f29\u3001\u56fe\u50cf\u5904\u7406\u3001\u52a0\u5bc6\u8ba1\u7b97\uff09\u4f1a\u963b\u585e\u6574\u4e2a\u4e8b\u4ef6\u5faa\u73af\uff0c\u5bfc\u81f4\u6240\u6709\u8bf7\u6c42\u6392\u961f\u7b49\u5f85\u3002\u5b50\u8fdb\u7a0b\u5c31\u662f\u89e3\u51b3\u8fd9\u4e2a\u95ee\u9898\u7684\u539f\u751f\u65b9\u6848\u3002

\u573a\u666f\u5355\u7ebf\u7a0b\u7684\u95ee\u9898\u5b50\u8fdb\u7a0b\u65b9\u6848
CPU \u5bc6\u96c6\u8ba1\u7b97\u963b\u585e\u4e8b\u4ef6\u5faa\u73af\uff0c\u8bf7\u6c42\u8d85\u65f6fork \u5b50\u8fdb\u7a0b\u5904\u7406\uff0c\u4e3b\u8fdb\u7a0b\u4e0d\u53d7\u5f71\u54cd
\u8c03\u7528\u7cfb\u7edf\u547d\u4ee4\u65e0\u6cd5\u76f4\u63a5\u6267\u884c shellexec/spawn \u8c03\u7528 ffmpeg\u3001git \u7b49
\u591a\u6838\u5229\u7528\u5355\u7ebf\u7a0b\u53ea\u7528\u4e00\u4e2a\u6838cluster \u521b\u5efa N \u4e2a worker\uff0c\u5145\u5206\u5229\u7528 CPU
\u4efb\u52a1\u9694\u79bb\u5d29\u6e83\u5f71\u54cd\u5168\u5c40\u5b50\u8fdb\u7a0b\u5d29\u6e83\u53ef\u91cd\u542f\uff0c\u4e0d\u5f71\u54cd\u4e3b\u8fdb\u7a0b

\u5f53\u7136\uff0c\u5982\u679c\u4f60\u53ea\u662f\u505c\u7559\u5728\u201c\u5f02\u6b65 I/O\u201d\u7684\u8bed\u5883\u91cc\uff0c\u5355\u7ebf\u7a0b\u8db3\u591f\u5e94\u4ed8\u3002\u4f46\u4e00\u65e6\u6d89\u53ca\u5230 CPU \u8ba1\u7b97\u6216\u7cfb\u7edf\u8c03\u7528\uff0c\u5b50\u8fdb\u7a0b\u5c31\u662f\u4e0d\u53ef\u907f\u514d\u7684\u9009\u9879\u3002

\u4e8c\u3001child_process \u56db\u5251\u5ba2

child_process \u6a21\u5757\u63d0\u4f9b\u4e86\u56db\u4e2a\u6838\u5fc3 API\uff0c\u5b83\u4eec\u7684\u533a\u522b\u5f80\u5f80\u8ba9\u521d\u5b66\u8005\u56f0\u60d1\u3002\u5148\u770b\u4e00\u5f20\u5bf9\u6bd4\u8868\uff1a

API\u662f\u5426\u8d70 shell\u8fd4\u56de\u6570\u636e\u65b9\u5f0f\u662f\u5426\u652f\u6301 IPC\u5178\u578b\u573a\u666f
exec()\u662f\u7f13\u51b2\uff08\u6700\u5927 maxBuffer\uff09\u5426\u6267\u884c\u590d\u6742\u547d\u4ee4\u3001\u7ba1\u9053\u7b26
execFile()\u5426\u7f13\u51b2\u5426\u6267\u884c\u53ef\u6267\u884c\u6587\u4ef6\uff0c\u907f\u514d shell \u5f00\u9500
spawn()\u5426\u6d41\u5f0f\uff08\u9010\u5757\u8f93\u51fa\uff09\u5426\u5927\u6570\u636e\u91cf\u3001\u957f\u65f6\u95f4\u8fd0\u884c\u3001\u5b9e\u65f6\u8f93\u51fa
fork()\u5426IPC \u6d88\u606f\u662fNode.js \u5b50\u8fdb\u7a0b\u3001\u4efb\u52a1\u5206\u53d1

child_process \u56db\u5251\u5ba2\u9009\u578b\u51b3\u7b56\u56fe

2.1 exec\uff1a\u6267\u884c shell \u547d\u4ee4

exec \u4f1a\u542f\u52a8\u4e00\u4e2a shell\uff08\u9ed8\u8ba4 /bin/sh\uff09\uff0c\u5728\u91cc\u9762\u6267\u884c\u4f60\u7684\u547d\u4ee4\u3002\u5b83\u4f1a\u628a stdout \u548c stderr \u5b58\u5165\u7f13\u51b2\u533a\uff0c\u6700\u540e\u4e00\u6b21\u6027\u8fd4\u56de\u3002\u9002\u5408\u6570\u636e\u91cf\u5c0f\u3001\u9700\u8981\u7ba1\u9053\u7b26\u7684\u573a\u666f\uff1a

const { exec } = require('child_process');

// \u57fa\u672c\u7528\u6cd5\uff1a\u6267\u884c\u547d\u4ee4\u5e76\u83b7\u53d6\u8f93\u51fa
exec('git log --oneline -5', (error, stdout, stderr) => {
  if (error) {
    console.error(`\u6267\u884c\u51fa\u9519: ${error.message}`);
    return;
  }
  if (stderr) {
    console.error(`stderr: ${stderr}`);
    return;
  }
  console.log(`\u8f93\u51fa:\n${stdout}`);
});

// \u8bbe\u7f6e\u8d85\u65f6\u548c\u6700\u5927\u7f13\u51b2\u533a
exec('find / -name "*.log"', {
  timeout: 5000,        // 5 \u79d2\u8d85\u65f6
  maxBuffer: 1024 * 1024, // 1MB \u7f13\u51b2\u533a\u4e0a\u9650
  cwd: '/var/log',
  env: { ...process.env, LANG: 'en_US.UTF-8' }
}, (error, stdout, stderr) => {
  if (error && error.killed) {
    console.error('\u8d85\u65f6\u88ab\u6740');
  }
  console.log(stdout.slice(0, 500));
});

\u6ce8\u610f\uff1aexec \u7684\u9ed8\u8ba4 maxBuffer \u662f 1MB\uff0c\u8d85\u8fc7\u4f1a\u629b\u51fa\u9519\u8bef\u3002\u5982\u679c\u9884\u671f\u8f93\u51fa\u5f88\u5927\uff0c\u8bf7\u6539\u7528 spawn\u3002

2.2 spawn\uff1a\u6d41\u5f0f\u5904\u7406\u5927\u6570\u636e

spawn \u4e0d\u542f\u52a8 shell\uff0c\u76f4\u63a5\u6267\u884c\u53ef\u6267\u884c\u6587\u4ef6\uff0c\u901a\u8fc7\u4e8b\u4ef6\u6d41\u5f0f\u8fd4\u56de\u6570\u636e\u3002\u8fd9\u662f\u5904\u7406\u5927\u6587\u4ef6\u3001\u957f\u65f6\u95f4\u4efb\u52a1\u7684\u9996\u9009\uff1a

const { spawn } = require('child_process');

// \u7528 ffmpeg \u8f6c\u7801\u89c6\u9891\uff0c\u5b9e\u65f6\u76d1\u63a7\u8fdb\u5ea6
const ffmpeg = spawn('ffmpeg', [
  '-i', 'input.mp4',
  '-c:v', 'libx264',
  '-preset', 'fast',
  '-crf', '23',
  '-c:a', 'aac',
  'output.mp4'
]);

let stderrData = '';
ffmpeg.stderr.on('data', (chunk) => {
  stderrData += chunk.toString();
  // \u89e3\u6790 ffmpeg \u7684\u8fdb\u5ea6\u4fe1\u606f
  const match = stderrData.match(/time=(\d+:\d+:\d+\.\d+)/);
  if (match) {
    process.stdout.write(`\r\u8f6c\u7801\u8fdb\u5ea6: ${match[1]}`);
  }
});

ffmpeg.on('close', (code) => {
  console.log(`\nffmpeg \u9000\u51fa\uff0c\u7801: ${code}`);
});

ffmpeg.on('error', (err) => {
  console.error('\u542f\u52a8\u5931\u8d25:', err.message);
});

spawn \u7684\u6838\u5fc3\u4f18\u52bf\u5728\u4e8e\u5b83\u4e0d\u7f13\u51b2\u6570\u636e\uff0c\u53ef\u4ee5\u5904\u7406\u6570 GB \u7ea7\u7684\u8f93\u51fa\u800c\u4e0d\u4f1a\u649e\u5230\u5185\u5b58\u5899\u3002

2.3 fork\uff1aNode.js \u5b50\u8fdb\u7a0b + IPC

fork \u662f spawn \u7684\u7279\u4f8b\uff0c\u4e13\u95e8\u7528\u6765\u521b\u5efa Node.js \u5b50\u8fdb\u7a0b\uff0c\u5e76\u81ea\u52a8\u5efa\u7acb IPC \u901a\u4fe1\u7ba1\u9053\u3002\u8fd9\u662f\u5b9e\u73b0\u4efb\u52a1\u5206\u53d1\u7684\u57fa\u7840\uff1a

// main.js - \u4e3b\u8fdb\u7a0b
const { fork } = require('child_process');
const path = require('path');

const worker = fork(path.join(__dirname, 'worker.js'), [], {
  // \u4f20\u9012\u73af\u5883\u53d8\u91cf
  env: { ...process.env, WORKER_ID: '1' },
  // silent: true \u4f1a\u5c06\u5b50\u8fdb\u7a0b\u7684 stdout/stderr \u8f93\u51fa\u5230\u7ba1\u9053
  // \u4e0d\u8bbe\u7f6e\u5219\u5b50\u8fdb\u7a0b\u7ee7\u627f\u7236\u8fdb\u7a0b\u7684 stdio
  silent: false
});

worker.on('message', (msg) => {
  console.log(`\u4ece\u5b50\u8fdb\u7a0b\u6536\u5230:`, msg);
});

worker.on('exit', (code, signal) => {
  console.log(`\u5b50\u8fdb\u7a0b\u9000\u51fa, code=${code}, signal=${signal}`);
});

// \u53d1\u9001\u4efb\u52a1\u5230\u5b50\u8fdb\u7a0b
worker.send({ type: 'task', payload: { data: [1, 2, 3, 4, 5] } });
// worker.js - \u5b50\u8fdb\u7a0b
process.on('message', (msg) => {
  if (msg.type === 'task') {
    console.log(`Worker ${process.env.WORKER_ID} \u6536\u5230\u4efb\u52a1`);
    
    // \u6267\u884c CPU \u5bc6\u96c6\u578b\u8ba1\u7b97
    const result = msg.payload.data.reduce((sum, n) => {
      // \u6a21\u62df\u91cd\u8ba1\u7b97
      let s = 0;
      for (let i = 0; i < 1e7; i++) s += n * i;
      return sum + s;
    }, 0);
    
    // \u53d1\u9001\u7ed3\u679c\u56de\u4e3b\u8fdb\u7a0b
    process.send({ type: 'result', value: result });
  }
});

process.on('disconnect', () => {
  console.log('IPC \u7ba1\u9053\u65ad\u5f00\uff0c\u9000\u51fa');
  process.exit(0);
});

\u4e09\u3001IPC \u901a\u4fe1\u673a\u5236\u6df1\u5165

fork \u521b\u5efa\u7684\u5b50\u8fdb\u7a0b\u4f1a\u81ea\u52a8\u5efa\u7acb\u4e00\u6761 IPC \u7ba1\u9053\uff0c\u5e95\u5c42\u4f7f\u7528 Unix domain socket\uff08Windows \u4e0a\u662f named pipe\uff09\u3002\u901a\u4fe1\u901a\u8fc7 process.send() \u548c process.on('message') \u5b9e\u73b0\uff0c\u4f20\u8f93\u7684\u6d88\u606f\u4f1a\u7ecf\u8fc7 JSON \u5e8f\u5217\u5316\u548c\u53cd\u5e8f\u5217\u5316\u3002

\u53cc\u5411\u901a\u4fe1\u5b9e\u6218\uff1a\u4efb\u52a1\u5206\u53d1\u5668

// task-pool.js - \u53ef\u590d\u7528\u7684\u4efb\u52a1\u6c60
const { fork } = require('child_process');
const path = require('path');
const os = require('os');

class TaskPool {
  constructor(workerFile, size = os.cpus().length) {
    this.workers = [];
    this.taskQueue = [];
    this.workerFile = workerFile;
    
    for (let i = 0; i < size; i++) {
      this._createWorker(i);
    }
  }
  
  _createWorker(id) {
    const worker = fork(this.workerFile, [], {
      env: { ...process.env, WORKER_ID: String(id) }
    });
    
    worker.busy = false;
    worker.currentResolve = null;
    
    worker.on('message', (msg) => {
      if (msg.type === 'result' && worker.currentResolve) {
        worker.busy = false;
        worker.currentResolve(msg.value);
        worker.currentResolve = null;
        this._dispatchNext();
      }
    });
    
    worker.on('exit', () => {
      // \u5b50\u8fdb\u7a0b\u5d29\u6e83\u540e\u91cd\u5efa
      console.log(`Worker ${id} \u5d29\u6e83\uff0c\u91cd\u5efa\u4e2d...`);
      this._createWorker(id);
    });
    
    this.workers[id] = worker;
  }
  
  run(taskData) {
    return new Promise((resolve) => {
      this.taskQueue.push({ data: taskData, resolve });
      this._dispatchNext();
    });
  }
  
  _dispatchNext() {
    const idleWorker = this.workers.find(w => w && !w.busy);
    if (!idleWorker || this.taskQueue.length === 0) return;
    
    const task = this.taskQueue.shift();
    idleWorker.busy = true;
    idleWorker.currentResolve = task.resolve;
    idleWorker.send({ type: 'task', payload: task.data });
  }
  
  shutdown() {
    this.workers.forEach(w => w && w.kill());
  }
}

// \u4f7f\u7528\u793a\u4f8b
const pool = new TaskPool(path.join(__dirname, 'worker.js'), 4);
Promise.all([
  pool.run({ compute: 'fibonacci', n: 40 }),
  pool.run({ compute: 'fibonacci', n: 42 }),
  pool.run({ compute: 'fibonacci', n: 45 })
]).then(results => {
  console.log('\u7ed3\u679c:', results);
  pool.shutdown();
});

\u8fd9\u4e2a\u4efb\u52a1\u6c60\u7684\u6838\u5fc3\u8bbe\u8ba1\uff1a\u7a7a\u95f2\u8f6e\u8be2 + \u4efb\u52a1\u961f\u5217\u3002\u5f53\u67d0\u4e2a worker \u5b8c\u6210\u4efb\u52a1\u540e\uff0c\u4e3b\u52a8\u4ece\u961f\u5217\u53d6\u4e0b\u4e00\u4e2a\u4efb\u52a1\u5206\u53d1\u3002\u5982\u679c\u6240\u6709 worker \u90fd\u5fd9\uff0c\u4efb\u52a1\u5728\u961f\u5217\u4e2d\u7b49\u5f85\u3002

\u56db\u3001cluster\uff1a\u591a\u6838 HTTP \u670d\u52a1

cluster \u6a21\u5757\u662f fork \u7684 HTTP \u5c01\u88c5\u3002\u5b83\u5141\u8bb8\u4f60\u5728\u591a\u6838\u673a\u5668\u4e0a\u521b\u5efa\u591a\u4e2a worker \u5171\u4eab\u540c\u4e00\u4e2a\u7aef\u53e3\uff0c\u7531\u4e3b\u8fdb\u7a0b\u8d1f\u8d23\u63a5\u6536\u8fde\u63a5\u5e76\u5206\u53d1\u7ed9 worker\u3002

const cluster = require('cluster');
const http = require('http');
const os = require('os');

if (cluster.isPrimary) {
  // ====== \u4e3b\u8fdb\u7a0b ======
  const numCPUs = os.cpus().length;
  console.log(`\u4e3b\u8fdb\u7a0b PID: ${process.pid}`);
  console.log(`\u542f\u52a8 ${numCPUs} \u4e2a worker`);
  
  for (let i = 0; i < numCPUs; i++) {
    cluster.fork();
  }
  
  // worker \u5d29\u6e83\u540e\u81ea\u52a8\u91cd\u542f
  cluster.on('exit', (worker, code, signal) => {
    console.log(`Worker ${worker.process.pid} \u6302\u4e86 (code=${code})`);
    console.log('\u91cd\u542f worker...');
    cluster.fork();
  });
  
  // \u4f18\u96c5\u505c\u673a
  process.on('SIGTERM', () => {
    console.log('\u4e3b\u8fdb\u7a0b\u6536\u5230 SIGTERM\uff0c\u901a\u77e5\u6240\u6709 worker \u4f18\u96c5\u9000\u51fa');
    for (const id in cluster.workers) {
      cluster.workers[id].send('shutdown');
    }
  });
  
} else {
  // ====== Worker \u8fdb\u7a0b ======
  const server = http.createServer((req, res) => {
    // \u6a21\u62df CPU \u5bc6\u96c6\u578b\u5de5\u4f5c
    if (req.url === '/heavy') {
      let sum = 0;
      for (let i = 0; i < 5e7; i++) sum += i;
      res.writeHead(200);
      res.end(`Worker ${process.pid}: result=${sum}\n`);
    } else {
      res.writeHead(200);
      res.end(`Hello from worker ${process.pid}\n`);
    }
  });
  
  server.listen(3000, () => {
    console.log(`Worker ${process.pid} \u76d1\u542c 3000 \u7aef\u53e3`);
  });
  
  // \u63a5\u6536\u4f18\u96c5\u505c\u673a\u6307\u4ee4
  process.on('message', (msg) => {
    if (msg === 'shutdown') {
      console.log(`Worker ${process.pid} \u5f00\u59cb\u4f18\u96c5\u9000\u51fa`);
      server.close(() => {
        process.exit(0);
      });
      // \u8d85\u65f6\u5f3a\u5236\u9000\u51fa
      setTimeout(() => process.exit(1), 5000).unref();
    }
  });
}

\u8d1f\u8f7d\u5747\u8861\u7b56\u7565

Node.js \u7684 cluster \u6709\u4e24\u79cd\u8c03\u5ea6\u7b56\u7565\uff0c\u901a\u8fc7 cluster.schedulingPolicy \u8bbe\u7f6e\uff1a

\u7b56\u7565\u5e38\u91cf\u539f\u7406\u9002\u7528\u573a\u666f
Round-robincluster.SCHED_RR\u4e3b\u8fdb\u7a0b\u8f6e\u6d41\u5206\u53d1\u8fde\u63a5\u9ed8\u8ba4\uff08\u9664 Windows\uff09\uff0c\u5747\u5300\u5206\u914d
Shared socketcluster.SCHED_NONE\u6240\u6709 worker \u5171\u4eab\u5957\u63a5\u5b57\uff0cOS \u51b3\u5b9aWindows \u9ed8\u8ba4\uff0c\u53ef\u80fd\u5bfc\u81f4\u8d1f\u8f7d\u4e0d\u5747

\u5728 Linux \u4e0a\uff0cRound-robin \u662f\u9ed8\u8ba4\u7b56\u7565\uff0c\u80fd\u4fdd\u8bc1\u8fde\u63a5\u5747\u5300\u5206\u914d\u3002\u5982\u679c\u4f60\u53d1\u73b0\u67d0\u4e2a worker \u538b\u529b\u660e\u663e\u5927\u4e8e\u5176\u4ed6\uff0c\u68c0\u67e5\u662f\u5426\u88ab\u8bbe\u7f6e\u4e3a\u4e86 SCHED_NONE\u3002

\u4e94\u3001stdin/stdout \u7ba1\u9053\u901a\u4fe1

\u9664\u4e86 fork \u7684 IPC\uff0c\u4f60\u8fd8\u53ef\u4ee5\u7528 spawn \u7684 stdin/stdout \u7ba1\u9053\u5b9e\u73b0\u8de8\u8bed\u8a00\u8fdb\u7a0b\u901a\u4fe1\u3002\u8fd9\u79cd\u65b9\u5f0f\u9002\u5408\u4e3b\u8fdb\u7a0b\u662f Node.js\u3001\u5b50\u8fdb\u7a0b\u662f Python \u7684\u573a\u666f\uff1a

// \u8c03\u7528 Python \u811a\u672c\u505a\u6570\u636e\u5904\u7406
const { spawn } = require('child_process');

const py = spawn('python3', ['-u', 'processor.py'], {
  stdio: ['pipe', 'pipe', 'inherit']  // -u \u7981\u7528 Python \u7f13\u51b2
});

const results = [];

// \u53d1\u9001\u6570\u636e\u5230 Python \u7684 stdin
const data = [
  { id: 1, value: 42 },
  { id: 2, value: 99 },
  { id: 3, value: 77 }
];

data.forEach(d => {
  py.stdin.write(JSON.stringify(d) + '\n');
});
py.stdin.end();

// \u8bfb\u53d6 Python \u7684 stdout
py.stdout.on('data', (chunk) => {
  chunk.toString().trim().split('\n').forEach(line => {
    try {
      results.push(JSON.parse(line));
    } catch(e) {
      console.error('\u89e3\u6790\u5931\u8d25:', line);
    }
  });
});

py.on('close', (code) => {
  console.log(`Python \u9000\u51fa (${code})\uff0c\u7ed3\u679c:`, results);
});
# processor.py - Python \u5b50\u8fdb\u7a0b
import sys
import json

for line in sys.stdin:
    try:
        data = json.loads(line.strip())
        # \u6a21\u62df\u5904\u7406
        result = {"id": data["id"], "squared": data["value"] ** 2}
        sys.stdout.write(json.dumps(result) + "\n")
        sys.stdout.flush()
    except Exception as e:
        sys.stderr.write(f"Error: {e}\n")

\u8fd9\u79cd\u201c\u6362\u884c\u5206\u9694 JSON\u201d\u7684\u534f\u8bae\u7b80\u5355\u5b9e\u7528\uff0c\u4f46\u5982\u679c\u5355\u884c\u6570\u636e\u5f88\u5927\uff0c\u8981\u6ce8\u610f stdin/stdout \u7684\u7ba1\u9053\u7f13\u51b2\u533a\u6709 64KB \u9650\u5236\uff0c\u5199\u5165\u592a\u5feb\u53ef\u80fd\u5bfc\u81f4 backpressure\u3002

\u516d\u3001\u5b9e\u6218\u573a\u666f\uff1a\u89c6\u9891\u8f6c\u7801\u670d\u52a1

\u628a\u524d\u9762\u7684\u77e5\u8bc6\u7ec4\u5408\u8d77\u6765\uff0c\u5199\u4e00\u4e2a\u5b9e\u9645\u7684\u89c6\u9891\u8f6c\u7801\u5fae\u670d\u52a1\uff1aHTTP \u63a5\u6536\u4efb\u52a1 \u2192 fork worker \u6267\u884c ffmpeg \u2192 \u5b9e\u65f6\u8fd4\u56de\u8fdb\u5ea6\u3002

// transcode-service.js
const { fork } = require('child_process');
const http = require('http');
const path = require('path');

const workers = [];
const MAX_WORKERS = 4;
const taskQueue = [];

function getOrCreateWorker() {
  const idle = workers.find(w => !w.busy);
  if (idle) return idle;
  if (workers.length < MAX_WORKERS) {
    const w = fork(path.join(__dirname, 'transcode-worker.js'));
    w.busy = false;
    w.taskId = null;
    workers.push(w);
    return w;
  }
  return null;
}

const server = http.createServer((req, res) => {
  if (req.method !== 'POST') {
    res.writeHead(405);
    return res.end('Method Not Allowed');
  }
  
  let body = '';
  req.on('data', c => body += c);
  req.on('end', () => {
    const { input, output } = JSON.parse(body);
    const taskId = Date.now().toString(36);
    
    const worker = getOrCreateWorker();
    if (!worker) {
      taskQueue.push({ taskId, input, output, res });
      return res.writeHead(202).end(JSON.stringify({ taskId, status: 'queued' }));
    }
    
    worker.busy = true;
    worker.taskId = taskId;
    
    // SSE \u63a8\u9001\u8fdb\u5ea6
    res.writeHead(200, {
      'Content-Type': 'text/event-stream',
      'Cache-Control': 'no-cache'
    });
    
    worker.on('message', (msg) => {
      if (msg.taskId !== taskId) return;
      res.write(`data: ${JSON.stringify(msg)}\n\n`);
      if (msg.type === 'done' || msg.type === 'error') {
        res.end();
        worker.busy = false;
        worker.taskId = null;
        // \u5904\u7406\u961f\u5217\u4e2d\u7684\u4efb\u52a1
        if (taskQueue.length > 0) {
          const next = taskQueue.shift();
          // \u91cd\u65b0\u5206\u53d1...
        }
      }
    });
    
    worker.send({ taskId, input, output });
  });
});

server.listen(3000, () => {
  console.log('\u8f6c\u7801\u670d\u52a1\u542f\u52a8 :3000');
});

\u4e03\u3001\u5e38\u89c1\u9677\u9631\u4e0e\u6700\u4f73\u5b9e\u8df5

\u9677\u9631\u75c7\u72b6\u89e3\u51b3\u65b9\u6848
\u50f5\u5c38\u8fdb\u7a0b\u5b50\u8fdb\u7a0b\u4e0d\u9000\u51fa\uff0c\u5360\u7528\u5185\u5b58\u8bbe\u7f6e timeout\uff0c\u76d1\u542c exit \u4e8b\u4ef6\uff0c\u8d85\u65f6\u540e worker.kill('SIGKILL')
\u7ba1\u9053\u963b\u585e\u5b50\u8fdb\u7a0b\u505c\u6ede\uff0c\u4e3b\u8fdb\u7a0b\u4e5f\u5361\u4f4fstdin/stdout \u5199\u5165\u540e\u53ca\u65f6 drain\uff0c\u4e0d\u8981\u540c\u6b65\u5199\u5165\u5927\u91cf\u6570\u636e
IPC \u6d88\u606f\u8fc7\u5927process.send \u629b\u9519IPC \u6709 2MB \u6d88\u606f\u5927\u5c0f\u9650\u5236\uff0c\u5927\u6570\u636e\u7528\u6587\u4ef6\u6216 SharedArrayBuffer
\u5b50\u8fdb\u7a0b\u672a\u6355\u83b7\u5f02\u5e38worker \u9759\u9ed8\u5d29\u6e83\u76d1\u542c error \u548c exit \u4e8b\u4ef6\uff0c\u5b9e\u73b0\u81ea\u52a8\u91cd\u542f
\u4fe1\u53f7\u5904\u7406\u4e0d\u5f53SIGTERM \u540e\u5b50\u8fdb\u7a0b\u4ecd\u5728\u5904\u7406\u6355\u83b7 SIGTERM\uff0c\u5b8c\u6210\u5f53\u524d\u4efb\u52a1\u540e\u518d\u9000\u51fa\uff0c\u8bbe\u7f6e\u5f3a\u5236\u8d85\u65f6
exec \u547d\u4ee4\u6ce8\u5165\u7528\u6237\u8f93\u5165\u88ab\u62fc\u63a5\u5230 shell\u6c38\u8fdc\u4e0d\u8981\u7528 exec \u6267\u884c\u62fc\u63a5\u7684\u7528\u6237\u8f93\u5165\uff0c\u6539\u7528 execFile + \u53c2\u6570\u6570\u7ec4
Windows \u4e0d\u517c\u5bb9shell \u547d\u4ee4\u5728 Windows \u62a5\u9519\u8de8\u5e73\u53f0\u811a\u672c\u7528 cross-env \u6216\u68c0\u6d4b process.platform

\u751f\u4ea7\u7ea7\u68c0\u67e5\u6e05\u5355

  • \u2705 \u6240\u6709 spawn/exec \u8c03\u7528\u90fd\u6709 timeout \u53c2\u6570
  • \u2705 \u5b50\u8fdb\u7a0b\u76d1\u542c error \u548c exit \u4e8b\u4ef6\uff0c\u5d29\u6e83\u540e\u81ea\u52a8\u91cd\u542f
  • \u2705 \u4f18\u96c5\u505c\u673a\uff1a\u6355\u83b7 SIGTERM/SIGINT\uff0c\u7b49\u5f85\u5b50\u8fdb\u7a0b\u5b8c\u6210\u5f53\u524d\u4efb\u52a1
  • \u2705 worker \u6570\u91cf\u4e0d\u8d85\u8fc7 CPU \u6838\u6570\uff0c\u907f\u514d\u4e89\u62a2 CPU
  • \u2705 \u4f7f\u7528 execFile \u4ee3\u66ff exec \u6267\u884c\u53ef\u4fe1\u8f93\u5165\u7684\u547d\u4ee4
  • \u2705 IPC \u6d88\u606f\u63a7\u5236\u5728 2MB \u4ee5\u5185\uff0c\u5927\u6570\u636e\u8d70\u6587\u4ef6\u6216\u5171\u4eab\u5185\u5b58
  • \u2705 \u5b50\u8fdb\u7a0b\u7684 stdout/stderr \u6709\u65e5\u5fd7\u6536\u96c6\uff0c\u4e0d\u8981\u9ed8\u8ba4\u8f93\u51fa\u5230\u63a7\u5236\u53f0
  • \u2705 \u5b9a\u671f\u68c0\u67e5 worker \u5065\u5eb7\u72b6\u6001\uff0c\u53d1\u9001 heartbeat \u6d88\u606f
  • \u2705 \u751f\u4ea7\u73af\u5883\u7528 PM2 cluster \u6a21\u5f0f\u7ba1\u7406\uff0c\u4e0d\u8981\u624b\u5199 cluster \u811a\u672c

\u516b\u3001cluster vs PM2 vs Worker Threads

\u5f88\u591a\u4eba\u4f1a\u95ee\uff1a\u65e2\u7136 cluster \u80fd\u591a\u6838\uff0c\u90a3\u8fd8\u9700\u8981 PM2 \u548c Worker Threads \u5417\uff1f\u4e09\u8005\u5404\u6709\u5b9a\u4f4d\uff1a

\u65b9\u6848\u9002\u7528\u573a\u666f\u4f18\u52bf\u5c40\u9650
clusterHTTP \u670d\u52a1\u591a\u6838\u62d3\u5c55\u539f\u751f\u652f\u6301\uff0c\u7aef\u53e3\u5171\u4eab\u9700\u624b\u5199\u7ba1\u7406\u903b\u8f91
PM2 cluster\u751f\u4ea7\u90e8\u7f72\u81ea\u52a8\u91cd\u542f\u3001\u65e5\u5fd7\u3001\u76d1\u63a7\u989d\u5916\u8fdb\u7a0b\u5f00\u9500
Worker Threads\u540c\u8fdb\u7a0b\u5185\u591a\u7ebf\u7a0b\u5171\u4eab\u5185\u5b58\uff0c\u65e0 IPC \u5e8f\u5217\u5316\u4ecd\u5728\u5b9e\u9a8c\u9636\u6bb5\uff0c\u751f\u6001\u4e0d\u5982 cluster \u6210\u719f

\u5efa\u8bae\uff1a\u751f\u4ea7 HTTP \u670d\u52a1\u7528 PM2 cluster \u6a21\u5f0f\uff1bCPU \u5bc6\u96c6\u578b\u540e\u53f0\u4efb\u52a1\u7528 fork + \u4efb\u52a1\u6c60\uff1b\u9700\u8981\u5171\u4eab\u5185\u5b58\u7684\u573a\u666f\u8003\u8651 Worker Threads\u3002

\u603b\u7ed3

Node.js \u7684\u5b50\u8fdb\u7a0b\u4f53\u7cfb\u4e0d\u590d\u6742\uff0c\u4f46\u9009\u5bf9\u5de5\u5177\u5f88\u91cd\u8981\u3002\u6838\u5fc3\u539f\u5219\uff1a\u9700\u8981 shell \u7528 exec\uff0c\u9700\u8981\u6d41\u5f0f\u8f93\u51fa\u7528 spawn\uff0c\u9700\u8981 Node.js \u5b50\u8fdb\u7a0b\u7528 fork\uff0c\u9700\u8981 HTTP \u591a\u6838\u7528 cluster\u3002\u638c\u63e1 IPC \u901a\u4fe1\u548c\u4f18\u96c5\u505c\u673a\uff0c\u518d\u914d\u5408 PM2 \u7684\u751f\u4ea7\u7ba1\u7406\uff0c\u5c31\u80fd\u7a33\u5065\u5730\u8dd1\u5728\u751f\u4ea7\u73af\u5883\u4e2d\u3002

Node.js \u5b50\u8fdb\u7a0b\u5b9e\u6218