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

Node.js环境下如何检测Firestore集合中是否有新文档正在写入

实现方案

1. 新增互斥锁集合做写入控制

专门创建一个名为write_lock的Firestore集合,集合内只保留一个固定ID的锁文档,比如文档ID设为a_collection_write_lock,文档包含三个字段:is_locking(布尔值,标记是否有写入操作正在执行)、lock_owner(字符串,标记当前持有锁的接口标识,可选)、expire_at(时间戳,锁自动过期时间,避免异常场景下锁永远不释放)。

2. 统一接口执行流程

endpoint_A1和endpoint_A2两个接口收到请求后,都按照以下流程执行:

  • 第一步:通过Firestore事务抢占锁,事务内先读取锁文档,判断is_locking为false或者当前时间已经超过expire_at。如果满足条件,就更新锁文档为持有状态:设置is_locking = true,lock_owner为当前接口标识,expire_at为当前时间加30秒(可根据业务写入最长耗时调整)。如果抢锁失败就等待100ms后重试,最多重试5次,超过重试次数直接返回繁忙提示。
  • 第二步:拿到锁之后,先校验另一个集合的最近写入情况,比如endpoint_A1就查询CollectionA2的最新文档创建时间,确认锁持有期间没有新的写入记录,避免抢锁前刚好有未完成的写入操作。
  • 第三步:执行当前接口自身的集合写入逻辑。
  • 第四步:写入完成后更新锁文档,把is_locking改回false,完成整个流程。

3. 核心代码示例(Node.js)

const { Firestore } = require('@google-cloud/firestore');
const db = new Firestore();
const LOCK_DOC_PATH = 'write_lock/a_collection_write_lock';

// 封装抢锁逻辑
async function acquireLock(ownerFlag) {
  const maxRetries = 5;
  let retryCount = 0;
  while(retryCount < maxRetries) {
    try {
      const lockResult = await db.runTransaction(async (transaction) => {
        const lockDoc = await transaction.get(db.doc(LOCK_DOC_PATH));
        const now = Date.now();
        // 锁不存在/未被持有/已过期 都可以抢占
        if (!lockDoc.exists || !lockDoc.data().is_locking || lockDoc.data().expire_at < now) {
          transaction.set(db.doc(LOCK_DOC_PATH), {
            is_locking: true,
            lock_owner: ownerFlag,
            expire_at: now + 30 * 1000
          }, { merge: true });
          return true;
        }
        return false;
      });
      if (lockResult) return true;
    } catch (err) {
      console.error('抢锁异常', err);
    }
    retryCount++;
    // 指数退避等待
    await new Promise(resolve => setTimeout(resolve, 100 * retryCount));
  }
  return false;
}

// 封装释放锁逻辑
async function releaseLock() {
  await db.doc(LOCK_DOC_PATH).update({ is_locking: false });
}

// endpoint_A1接口示例逻辑
async function endpointA1Handler(req, res) {
  const gotLock = await acquireLock('A1');
  if (!gotLock) {
    return res.status(503).send('服务繁忙,请稍后重试');
  }
  try {
    // 校验CollectionA2最近10秒无写入
    const a2LatestSnapshot = await db.collection('CollectionA2')
      .orderBy('create_time', 'desc')
      .limit(1)
      .get();
    if (!a2LatestSnapshot.empty) {
      const latestA2WriteTime = a2LatestSnapshot.docs[0].data().create_time.toMillis();
      if (Date.now() - latestA2WriteTime < 10 * 1000) {
        await releaseLock();
        return res.status(409).send('存在并发写入,请稍后重试');
      }
    }
    // 执行CollectionA1写入逻辑
    await db.collection('CollectionA1').add({
      // 你的业务字段
      create_time: Firestore.FieldValue.serverTimestamp()
    });
    // 其余后续业务逻辑
    res.send('操作成功');
  } catch (err) {
    console.error('A1接口执行异常', err);
    res.status(500).send('服务内部异常');
  } finally {
    await releaseLock();
  }
}

注意事项

  • 锁文档的操作必须用Firestore事务执行,避免并发抢锁时出现竞态问题
  • 锁必须设置超时时间,避免接口异常崩溃导致锁永远不释放,后续请求全部卡住
  • 两个集合的所有写入入口都必须接入这套锁逻辑,不能有遗漏,否则互斥控制会失效

内容的提问来源于stack exchange,提问作者KK2491

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.09 15:54:02