多进程Node.js中Mutex跨进程竞争问题及解决方案咨询
解决Node.js多进程(PM2集群)下的跨进程竞争条件问题
一、PM2集群部署下的跨进程锁解决方案
PM2集群模式会启动多个内存隔离的独立Node.js进程,进程内的内存级Mutex完全无效,必须依赖外部共享存储实现分布式锁,常用方案有两种:
1. Redis分布式锁
利用Redis的原子性命令实现跨进程/跨服务器的锁机制:
- 核心逻辑:通过
SET命令的NX(仅当键不存在时设置)和EX(自动过期)选项抢占锁,示例代码:const redis = require('ioredis'); const client = new redis(); async function acquireLock(lockKey, expire = 10) { return await client.set(lockKey, 'locked', 'NX', 'EX', expire); } async function releaseLock(lockKey) { return await client.del(lockKey); } // 下单扣库存示例 async function placeOrder(productId) { const lockKey = `lock:product:${productId}`; try { const lockAcquired = await acquireLock(lockKey); if (!lockAcquired) { throw new Error('当前下单人数过多,请稍后重试'); } // 查询当前库存 const stock = await getProductStock(productId); if (stock <= 0) { throw new Error('库存不足'); } // 扣减库存 await updateProductStock(productId, stock - 1); } finally { await releaseLock(lockKey); } } - 注意事项:设置合理的锁过期时间避免死锁;高并发场景需添加锁续期逻辑,防止锁提前过期导致竞争。
2. 数据库行级锁
直接在数据库层面实现资源锁定,适合依赖数据库的业务场景:
- 以MySQL为例,使用
SELECT ... FOR UPDATE语句锁定目标库存行,确保同一时间仅一个进程能修改数据:async function placeOrder(productId) { const connection = await getDbConnection(); try { await connection.beginTransaction(); // 锁定指定商品的库存行 const [rows] = await connection.execute( 'SELECT stock FROM products WHERE id = ? FOR UPDATE', [productId] ); const stock = rows[0].stock; if (stock <= 0) { throw new Error('库存不足'); } // 扣减库存 await connection.execute( 'UPDATE products SET stock = stock - 1 WHERE id = ?', [productId] ); await connection.commit(); } catch (err) { await connection.rollback(); throw err; } finally { connection.release(); } } - 注意事项:必须在事务中使用行级锁,且锁的粒度要尽可能小,避免锁定整张表影响并发性能。
二、无需PM2的替代部署方案
如果不想处理跨进程锁的复杂度,可选择以下部署模式:
1. 单进程+反向代理扩容
放弃PM2集群模式,每个Node.js实例以单进程运行,通过Nginx等反向代理工具部署多个单进程实例做负载均衡。这种模式下每个实例内部可用内存级Mutex,但跨实例的竞争问题仍需靠Redis/数据库锁解决。
2. 基于Worker Threads的单进程多线程
使用Node.js的worker_threads模块在单进程内创建多线程,线程间可通过SharedArrayBuffer或MessageChannel共享内存,此时可使用async-mutex等库的内存级Mutex实现线程安全。该模式更适合CPU密集型场景,IO密集型场景优势不明显。
3. Serverless函数部署
使用AWS Lambda、阿里云函数计算等Serverless平台,由平台自动根据请求量扩容函数实例,无需手动管理进程。但跨实例的竞争问题仍需依赖分布式锁,同时要注意函数实例的冷启动问题。
内容的提问来源于stack exchange,提问作者bbusdriver
相关产品推荐
相关产品推荐

