Node.js PM2多集群环境下Mutex锁实现求助(PostgreSQL+Prisma)
解决PM2多集群下的库存超卖问题(PostgreSQL + Prisma)
单进程的async-mutex在PM2多集群环境下完全无效——每个Worker进程都是独立的,各自维护自己的Mutex实例,进程间无法共享锁状态,竞态条件依然存在。必须依赖数据库层面的锁机制来保证跨进程的并发安全,以下是基于PostgreSQL和Prisma的两种可靠实现方案:
方案一:悲观锁(排它行锁)
通过PostgreSQL的SELECT ... FOR UPDATE语法,在查询库存时对目标行加排它锁,确保同一时间只有一个事务能修改该记录,彻底避免竞态。
1. Prisma Schema 定义
假设你的商品模型包含库存字段:
model Product { id Int @id @default(autoincrement()) name String stock Int @default(0) // 库存数量 // 其他业务字段... }
2. 业务代码实现
async function buyItem(productId) { // 用Prisma事务包裹所有操作,保证原子性 return await prisma.$transaction(async (tx) => { // 查询商品并加排它锁,阻塞其他事务修改该记录 const product = await tx.product.findUnique({ where: { id: productId }, lock: { mode: 'update' } // 对应PostgreSQL的SELECT ... FOR UPDATE }); if (!product || product.stock <= 0) { throw new Error('库存不足'); } // 执行用户支付逻辑(替换为你的实际支付代码) await userPay(); // 更新库存 await tx.product.update({ where: { id: productId }, data: { stock: product.stock - 1 } }); return { success: true, remainingStock: product.stock - 1 }; }); }
说明
$transaction确保支付和库存更新要么全部成功,要么全部回滚,避免数据不一致- 排它锁会阻塞其他试图修改该商品的事务,直到当前事务提交/回滚,严格保证库存不会超卖
- 适合高并发但对数据一致性要求极高的场景,缺点是极端高并发下可能出现锁等待,但属于可接受的性能损耗
方案二:乐观锁(版本号机制)
不主动加锁,而是通过版本号校验来检测并发冲突,冲突时自动重试。适合并发极高但冲突概率较低的场景,性能更优。
1. Prisma Schema 改造
添加版本号字段用于冲突校验:
model Product { id Int @id @default(autoincrement()) name String stock Int @default(0) version Int @default(1) // 乐观锁版本号,每次更新自增 // 其他业务字段... }
2. 业务代码实现
async function buyItem(productId, retryCount = 3) { try { return await prisma.$transaction(async (tx) => { const product = await tx.product.findUnique({ where: { id: productId } }); if (!product || product.stock <= 0) { throw new Error('库存不足'); } // 执行用户支付逻辑 await userPay(); // 更新库存时校验版本号,只有版本匹配才允许更新 const updatedProduct = await tx.product.update({ where: { id: productId, version: product.version // 确保没有其他事务修改过该记录 }, data: { stock: product.stock - 1, version: product.version + 1 // 版本号自增 } }); // 如果更新返回为空,说明版本号不匹配,触发重试 if (!updatedProduct) { throw new Error('库存更新冲突'); } return { success: true, remainingStock: updatedProduct.stock }; }); } catch (error) { // 冲突时自动重试,最多重试3次 if (error.message === '库存更新冲突' && retryCount > 0) { return await buyItem(productId, retryCount - 1); } throw error; } }
说明
- 乐观锁不会阻塞其他事务,只有当多个事务同时修改同一商品时才会触发冲突
- 通过版本号校验确保只有"读取时的版本"和"更新时的版本"一致才会成功,避免超卖
- 重试次数可根据业务场景调整,既保证成功率,又避免无限重试
为什么不推荐跨进程Mutex?
跨进程Mutex需要依赖共享内存、Redis等第三方服务,实现复杂度高,且依赖额外组件的可用性。而你已经在使用PostgreSQL,直接利用数据库原生的锁机制更可靠、更简单,不需要额外引入依赖。
内容的提问来源于stack exchange,提问作者bbusdriver
相关产品推荐
相关产品推荐

