Google Cloud Tasks如何更新已有任务的存活时间(TTL)
场景说明
我搭建了一款Firebase应用,支持用户创建Firestore文档。文档创建时会写入时间戳字段,用于通过云函数在指定时长后自动删除对应文档。
文档创建成功后,会正常触发onCreate云函数,由该函数创建Cloud Task,在预设调度时间到达时执行文档删除操作。相关实现代码如下:
export const onCreatePost = functions .region(region) .firestore.document('/boxes/{id}') .onCreate(async (snapshot) => { const data = snapshot.data() as ExpirationDocData; // Box creation timestamp. const { timestamp } = data; // The path of the firebase document('/myCollection/{docId}'). const docPath = snapshot.ref.path; await scheduleCloudTask(timestamp, docPath) .then(() => { console.log('onCreate: cloud task created successfully.'); }) .catch((error) => { console.error(error); }); });
export const scheduleCloudTask = async (timestamp: number, docPath: string) => { // Convert timestamp to seconds. const timestampToSeconds = timestamp / 1000; // Doc time to live in seconds const documentLifeTime = 20; const expirationAtSeconds = timestampToSeconds + documentLifeTime; // The Firebase project ID. const project = 'my-project'; // Cloud Tasks -> firestore time to life queue. const queue = 'my-queue'; const queuePath: string = tasksClient.queuePath(project, region, queue); // The url to the callback function. // That gets envoked by Google Cloud tasks when the deadline is reached. const url = `https://${region}-${project}.cloudfunctions.net/callbackFn`; const payload: ExpirationTaskPayload = { docPath }; // Google cloud IAM & ADMIN principle account. const serviceAccountEmail = 'myServiceAccount@appspot.gserviceaccount.com'; // Configuration for the Cloud Task const task = { httpRequest: { httpMethod: 'POST', url, oidcToken: { serviceAccountEmail, }, body: Buffer.from(JSON.stringify(payload)).toString('base64'), headers: { 'Content-Type': 'application/json', }, }, scheduleTime: { seconds: expirationAtSeconds, }, }; await tasksClient.createTask({ parent: queuePath, task, }); };
export const callbackFn = functions .region(region) .https.onRequest(async (req, res) => { const payload = req.body as ExpirationTaskPayload; try { await admin.firestore().doc(payload.docPath).delete(); res.sendStatus(200); } catch (error) { console.error(error); res.status(500).send(error); } });
问题描述
应用支持用户延长文档的存活时间,该操作触发后,Firestore文档中的时间戳会正常更新,onUpdate云函数也会按预期执行。
最初尝试在时间戳更新时重复调用scheduleCloudTask函数,以此更新Cloud Task的存活时间,但该方案无效,仅会为同一文档重复创建新的任务。相关实现代码如下:
export const onDocTimestampUpdate = functions .region(region) .firestore.document('/myCollection/{docId}') .onUpdate(async (change, context) => { const before = change.before.data() as ExpirationDocData; const after = change.after.data() as ExpirationDocData; if (before.timestamp < after.timestamp) { const docPath = change.before.ref.path; await scheduleCloudTask(after.timestamp, docPath) .then((res) => { console.log('onUpdate: cloud task created successfully.'); return; }) .catch((error) => { console.error(error); }); } else return; });
目前未查询到使用updateTask()或同类方法更新已有任务的官方文档或示例,特此咨询:是否需要先调用deleteTask()方法删除原有任务,再在文档时间戳更新后调用createTask()方法创建新任务?
解答
Cloud Tasks 本身没有提供直接修改已有任务调度时间的API,你想到的「先删除旧任务再创建新任务」是可行方案,但不是实现成本最低的选择。
目前常用的两种落地方式:
- 方案1:删旧建新
用这个方案需要你在Firestore文档里额外加一个字段,存当前绑定的Cloud Task的唯一资源名——创建任务时接口返回结果里的name字段就是这个标识。用户延长文档存活时间触发onUpdate时,先取出旧任务名调用deleteTask()删掉旧任务,再调用scheduleCloudTask()生成对应新过期时间的任务,同时把新任务的资源名更新回文档字段。
要注意给删除操作加异常捕获:如果旧任务已经执行完成、或者已经被删除,接口会抛错,直接忽略这类错误即可,不要触发云函数重试。 - 方案2:回调加幂等校验(推荐)
这个方案完全不需要处理旧任务,改下回调函数逻辑就行:任务触发时不要直接删除文档,先读取对应Firestore文档的最新timestamp,计算当前时间是否已经达到文档约定的过期时间,如果没到就直接返回200,不执行删除操作。
哪怕之前创建的旧任务提前触发,因为校验不通过不会误删文档,只有最后一次创建的、匹配最新过期时间的任务触发时,校验通过才会真正删除文档。多生成的旧任务只会触发一次空的函数调用,Cloud Tasks和云函数的调用成本极低,几乎不会产生额外开销,比维护任务ID、处理删除异常的逻辑简单很多,稳定性也更高。
内容的提问来源于stack exchange,提问作者GRos
相关产品推荐
相关产品推荐

