Express.js使用Throng集群时出现EADDRINUSE端口占用错误
解决Express+Throng集群模式下EADDRINUSE端口占用错误
你遇到的Error: bind EADDRINUSE null:3000确实是多进程抢占同一端口导致的——当前代码中app.listen()在throng()调用之外执行,主进程先启动Express绑定端口,随后Throng创建的每个worker进程又会重复执行这段代码,尝试绑定同一个端口,最终引发冲突。
错误信息
node:events:498 throw er; // Unhandled 'error' event ^ Error: bind EADDRINUSE null:3000 at listenOnPrimaryHandle (node:net:1397:18) at shared (node:internal/cluster/child:149:3) at Worker.<anonymous> (node:internal/cluster/child:106:7) at process.onInternalMessage (node:internal/cluster/utils:49:5) at process.emit (node:events:532:35) at emit (node:internal/child_process:938:14) at processTicksAndRejections (node:internal/process/task_queues:84:21) Emitted 'error' event on Server instance at: at listenOnPrimaryHandle (node:net:1398:21) at shared (node:internal/cluster/child:149:3) [... lines matching original stack trace ...] at processTicksAndRejections (node:internal/process/task_queues:84:21) { errno: -4091, code: 'EADDRINUSE', syscall: 'bind', address: null, port: 3000 }
修复方案
方案1:让Worker共享HTTP端口
将Express初始化、路由定义、服务启动全部移入Throng的start函数,Node集群模式会自动将端口连接分发给各个Worker,避免重复绑定:
const express = require('express') const Queue = require('bull') const throng = require('throng') const {getScrapedTikTokData} = require('./local') const {Webhook} = require('discord-webhook-node') const hook = new Webhook(process.env.WEBHOOK_CHANNEL || '') const REDIS_URL = process.env.REDIS_URL || 'redis://127.0.0.1:6379' const workers = process.env.WEB_CONCURRENCY || 1 const maxJobsPerWorker = 50 function start(id) { // 每个Worker创建独立的Express实例 const app = express() const workQueue = new Queue('work', REDIS_URL, { defaultJobOptions: { removeOnComplete: true, removeOnFail: true } }) // 定义路由 app.get('/discord', async (req, res) => { const url = req.query.url console.log(`Worker ${id} 接收请求:${url}`) let job = await workQueue.add({url: url}) res.json({id: job.id, workerId: id}) }) // 处理队列任务 workQueue.process(maxJobsPerWorker, async job => { const url = job.data.url console.log(`Worker ${id} 处理任务:${url}`) await getScrapedTikTokData(url) return {value: 'working'} }) // 监听任务完成事件 workQueue.on('global:completed', async (jobId, result) => { console.log(`Worker ${id}:任务 ${jobId} 完成,结果:${result}`) try { await hook.send(result) } catch (e) { console.log(e.message) } }) // 启动服务 app.listen(process.env.PORT || 3000, () => { console.log(`Worker ${id} 运行在端口 ${process.env.PORT || 3000}`) }) } throng({workers, start})
方案2:主进程处理HTTP,Worker专注队列任务
更高效的方式是让主进程单独启动Express接收请求,Worker进程仅负责处理队列任务,彻底避免端口冲突:
const express = require('express') const Queue = require('bull') const throng = require('throng') const {getScrapedTikTokData} = require('./local') const {Webhook} = require('discord-webhook-node') const hook = new Webhook(process.env.WEBHOOK_CHANNEL || '') const REDIS_URL = process.env.REDIS_URL || 'redis://127.0.0.1:6379' const workers = process.env.WEB_CONCURRENCY || 1 const maxJobsPerWorker = 50 // 主进程:负责接收HTTP请求、管理队列事件 function startMaster() { const app = express() const workQueue = new Queue('work', REDIS_URL, { defaultJobOptions: { removeOnComplete: true, removeOnFail: true } }) app.get('/discord', async (req, res) => { const url = req.query.url console.log(`主进程接收请求:${url}`) let job = await workQueue.add({url: url}) res.json({id: job.id}) }) workQueue.on('global:completed', async (jobId, result) => { console.log(`任务 ${jobId} 完成,结果:${result}`) try { await hook.send(result) } catch (e) { console.log(e.message) } }) app.listen(process.env.PORT || 3000, () => { console.log(`主进程服务运行在端口 ${process.env.PORT || 3000}`) }) } // Worker进程:仅处理队列任务 function startWorker(id) { const workQueue = new Queue('work', REDIS_URL) workQueue.process(maxJobsPerWorker, async job => { const url = job.data.url console.log(`Worker ${id} 处理任务:${url}`) await getScrapedTikTokData(url) return {value: 'working'} }) console.log(`Worker ${id} 启动,等待处理任务`) } throng({ workers, master: startMaster, start: startWorker })
修改核心点
- 移除全局作用域的
app和workQueue实例,避免Worker重复初始化资源 - 将HTTP服务启动逻辑限制在单个进程(主进程)或让Worker通过Node集群机制共享端口
- 拆分职责,让主进程专注请求分发,Worker专注任务处理,提升资源利用率
内容的提问来源于stack exchange,提问作者Alaa
相关产品推荐
相关产品推荐

