Firebase匹配云函数如何避免同一开放对局被多名玩家同时抢占
解决方案
问题本质
你遇到的是典型的读写竞态条件问题:现有代码的「查询待匹配对局」和「更新对局信息」是两个独立的非原子操作,当两个请求同时发起时,会先后查询到同一条player2=-1的开放对局记录,之后分别执行更新操作,导致两个玩家都被写入同一个对局的player2字段。
修复方案
使用Firestore自带的事务(Transaction) 封装整个匹配逻辑,事务可以保证操作的原子性:如果事务执行过程中目标对局的数据已经被其他请求修改,事务会自动重试,最终只会有一个请求成功更新该对局,另一个请求会自动fallback到新建开放对局的逻辑。
修改后的代码示例
exports.findGame = functions.https.onCall(async (data, context) => { const uid = context.auth.uid; const db = admin.firestore(); // 用事务封装整个匹配逻辑 const result = await db.runTransaction(async (tx) => { // 事务内查询待匹配对局 const gameQuery = db.collection('games') .where("player2", "==", "-1") .limit(1); const gameSnapshot = await tx.get(gameQuery); if (gameSnapshot.empty) { // 没有待匹配对局,返回标记需要新建 return { needCreate: true }; } const gameDoc = gameSnapshot.docs[0]; const gameData = gameDoc.data(); // 二次校验对局确实还是开放状态(避免事务重试期间的异常) if (gameData.player2 !== "-1") { return { needCreate: true }; } // 事务内查询玩家名称 const userDoc = await tx.get(db.collection('users').doc(uid)); const username = userDoc.data().Username; // 事务内更新对局信息 tx.update(gameDoc.ref, { player2: uid, player2Name: username }); return { needCreate: false, gameId: gameDoc.id }; }); // 事务执行成功后,如果需要新建对局则执行 if (result.needCreate) { await createNewGame(uid); } return { success: true, gameId: result.gameId || null }; });
注意事项
- 事务内的所有读写操作都必须使用事务提供的
tx.get/tx.update/tx.set方法,不能直接调用Firestore的原生读写方法,否则事务无法保证原子性 - 事务默认会自动重试最多5次,如果匹配请求并发量极高,可以根据业务需求调整重试配置
- 如果后续需要增加匹配规则(例如段位匹配、等级匹配),只需要修改事务内的查询条件即可,依然可以保证并发安全
内容的提问来源于stack exchange,提问作者Daniel Brunner
相关产品推荐
相关产品推荐

