You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.28 02:06:07