Express.js如何使用多线程/异步处理并发HTTP请求及扩容
核心结论先给你
- Node.js 13+ 稳定的
worker_threads特性默认完全不会自动生效,Node不会因为请求并发高就主动开新线程处理请求,所有Express路由的回调默认都跑在主线程的事件循环里。 - 10000个并发请求同时打过来,只要你的接口逻辑没有阻塞主线程的CPU密集操作,默认单线程的异步IO模型就能正常调度处理,根本不需要额外开线程;一旦有CPU重逻辑堵了事件循环,哪怕100个并发也会把整个服务卡到超时。
落地worker_threads的正确姿势
首先先搞清楚适用场景,别上来就瞎用:
- 如果你的接口是常规CRUD(查库、读缓存、调第三方接口这类IO密集逻辑),完全不需要用
worker_threads,Node默认的异步IO模型扛几万QPS都没问题,瓶颈一般在下游依赖,不在Node本身。 - 只有当接口里存在CPU密集逻辑(大文件加解密、复杂计算、大体积JSON序列化/反序列化、图片处理、容易触发回溯的正则匹配等),才需要用
worker_threads把这部分逻辑剥离到子线程,避免堵死主线程。
具体实现注意这几点:
- 绝对不要请求来了才临时创建worker:创建线程的开销非常大,高并发下临时创建线程会直接打满内存把服务搞崩,服务启动时就要预先创建固定大小的线程池,线程数一般和服务器CPU核心数对齐就行,开多了会因为CPU上下文切换反而降低性能。
- 线程和主线程内存隔离,不能直接共享变量,只能通过
postMessage传递数据,默认传参会做结构化克隆,大对象传输开销不低,如果传超大Buffer可以用转移所有权的方式避免拷贝。 - 不要在worker线程里起Express/HTTP服务,worker只用来跑剥离出来的CPU密集任务,多进程起HTTP服务的活交给
cluster模块或者PM2集群模式做。
给个最小可运行的示例:
首先是主线程Express服务逻辑:
const express = require('express'); const { Worker } = require('worker_threads'); const os = require('os'); const app = express(); // 初始化固定大小线程池 const WORKER_POOL = []; const POOL_SIZE = os.cpus().length; for (let i = 0; i < POOL_SIZE; i++) { const worker = new Worker('./cpu-task.js'); WORKER_POOL.push({ instance: worker, busy: false }); } // 取空闲线程 const getIdleWorker = () => WORKER_POOL.find(w => !w.busy); app.get('/heavy-calc', async (req, res) => { const worker = getIdleWorker(); // 没有空闲线程直接返回繁忙,不要无限排队把进程拖死 if (!worker) return res.status(429).send('Server busy, try later'); worker.busy = true; worker.instance.postMessage({ calcNum: Number(req.query.n) }); // 监听子线程返回结果 worker.instance.once('message', (calcResult) => { worker.busy = false; res.send({ code: 0, data: calcResult }); }); }); app.listen(3000, () => console.log('Service running on :3000'));
然后是子线程任务文件cpu-task.js:
const { parentPort } = require('worker_threads'); // 接收主线程派发的任务 parentPort.on('message', (task) => { // 这里放CPU密集逻辑,示例为大循环累加 let sum = 0; for (let i = 0; i < task.calcNum * 10_000_000; i++) { sum += i; } // 计算完把结果发回主线程 parentPort.postMessage(sum); });
大规模请求的扩容方案
worker_threads只能解决单进程内CPU阻塞事件循环的问题,扛大规模流量要做多层扩容:
- 单机层面:用Node自带的
cluster模块或者PM2集群模式启动服务,按照CPU核心数起对应数量的Node进程,充分利用多核性能,避免单进程跑满一个核心其他核心闲置的问题。 - 多机层面:前面挂负载均衡(比如Nginx),把流量分发到多台部署了服务的机器,做水平扩容,扛住超过单机上限的流量。
- 运维层面:给接口加限流、降级逻辑,避免流量突增把整个集群打垮。
内容的提问来源于stack exchange,提问作者efoc
相关产品推荐
相关产品推荐

