如何在Node.js中正确处理并发问题?附多端点共享逻辑示例
如何在Node.js中正确处理并发问题(结合你的Express多端点场景)
嘿,我来帮你梳理下这个问题——你的代码里多个Express端点重复了几乎一样的逻辑,而且还要处理并发安全,咱们先从代码复用入手,再搞定并发问题,这样代码既好维护又安全。
第一步:提取通用逻辑,干掉重复代码
你现在的每个端点都在重复调用asyncFunction和后续的任务逻辑,这完全可以封装成一个独立函数或者Express中间件,以后改逻辑只需要改一处就行:
const express = require('express'); const router = express.Router(); // 把重复的逻辑封装成通用函数 async function handleCommonTask(something, req, res) { try { // 先把callback风格的asyncFunction转成Promise(用await更顺手) const userId = await new Promise((resolve, reject) => { asyncFunction(something, (err, result) => { if (err) reject(err); else resolve(result); }); }); // 这里写你的通用任务逻辑 console.log(`正在处理用户ID: ${userId}`); // ... 比如数据库操作、业务计算之类的 res.status(200).json({ success: true, userId }); } catch (err) { // 统一处理错误,不用每个端点都写一遍 console.error('任务处理出错:', err); res.status(500).json({ success: false, error: err.message }); } } // 现在每个端点只需要调用这个通用函数就行,清爽多了 router.post('/test1', (req, res) => { handleCommonTask(something1, req, res); }); router.post('/test2', (req, res) => { handleCommonTask(something2, req, res); }); router.post('/test3', (req, res) => { handleCommonTask(something3, req, res); });
第二步:针对性处理并发安全问题
Node.js是单线程事件循环,但异步操作(比如数据库请求、IO)是并发执行的,所以如果你的通用任务涉及共享资源操作(比如改全局变量、更新同一条数据库记录、读写同一个文件),就会出现竞争条件,得针对性解决:
场景1:操作共享内存(比如全局变量)
如果多个请求同时修改同一个全局变量(比如计数):
let globalCounter = 0; // 通用任务里的逻辑 globalCounter += 1;
这种情况并发下计数肯定不准,你可以用互斥锁保证同一时间只有一个请求执行这段代码。先安装async-mutex库:
npm install async-mutex
然后修改通用函数:
const { Mutex } = require('async-mutex'); // 创建一个锁实例,专门保护这个计数器 const counterMutex = new Mutex(); async function handleCommonTask(something, req, res) { try { const userId = await new Promise((resolve, reject) => { asyncFunction(something, (err, result) => { if (err) reject(err); else resolve(result); }); }); // 加锁,确保这段代码同一时间只有一个请求在执行 await counterMutex.runExclusive(async () => { globalCounter += 1; console.log(`当前计数: ${globalCounter}, 处理用户: ${userId}`); }); res.status(200).json({ success: true, userId, counter: globalCounter }); } catch (err) { console.error('任务处理失败:', err); res.status(500).json({ success: false, error: err.message }); } }
场景2:操作数据库(更新同一条记录)
如果你的通用任务是更新用户余额、库存这类数据,直接并发更新会导致数据不一致,这时候用数据库事务或者乐观锁就很靠谱:
比如用MySQL事务的例子:
async function handleCommonTask(something, req, res) { try { const userId = await new Promise((resolve, reject) => { asyncFunction(something, (err, result) => { if (err) reject(err); else resolve(result); }); }); // 开启事务,确保一系列操作要么全成功,要么全回滚 await db.beginTransaction(); // 先查询当前值 const [user] = await db.query('SELECT balance FROM users WHERE id = ?', [userId]); // 更新余额(比如扣除10元) await db.query('UPDATE users SET balance = ? WHERE id = ?', [user.balance - 10, userId]); // 提交事务 await db.commit(); res.status(200).json({ success: true, userId, newBalance: user.balance - 10 }); } catch (err) { // 出错就回滚,避免脏数据 await db.rollback(); console.error('任务处理失败:', err); res.status(500).json({ success: false, error: err.message }); } }
或者用乐观锁(通过版本号控制,避免并发覆盖):
UPDATE users SET balance = balance - 10, version = version + 1 WHERE id = ? AND version = ?
这样只有当版本号和查询时一致才会更新,完美避免并发覆盖问题。
场景3:需要同时执行多个异步任务
如果你的通用任务里要同时调用多个API、查询多个数据库表,用Promise.all来并发执行,效率会高很多,同时要注意错误处理:
async function handleCommonTask(something, req, res) { try { const userId = await new Promise((resolve, reject) => { asyncFunction(something, (err, result) => { if (err) reject(err); else resolve(result); }); }); // 并发执行多个异步任务,比逐个等待快很多 const [userInfo, orderList] = await Promise.all([ db.query('SELECT * FROM users WHERE id = ?', [userId]), db.query('SELECT * FROM orders WHERE user_id = ?', [userId]) ]); res.status(200).json({ success: true, userId, userInfo: userInfo[0], orders: orderList }); } catch (err) { console.error('任务处理失败:', err); res.status(500).json({ success: false, error: err.message }); } }
要是想让某个任务失败不影响其他任务,就用Promise.allSettled,它会返回所有任务的结果(成功或失败),你可以自己逐个处理。
最后总结下
- 先把重复逻辑抽出来,代码维护起来轻松太多;
- 根据不同并发场景选对应方案:
- 共享内存:用互斥锁;
- 数据库操作:事务或乐观锁;
- 多异步任务:
Promise.all/Promise.allSettled;
- 错误处理一定要做好,别让一个请求的问题连累其他请求。
内容的提问来源于stack exchange,提问作者James Yoo
相关产品推荐
相关产品推荐

