我们的目标是:

  1. 快速(<10s)回复呼叫者,回复200以避免呼叫者重试.
  2. THEN在结束函数之前发出另外两个POST(对时间敏感)请求.

总而言之,处理这个问题的最佳解决方案是什么?我们已经看到:

  1. PubSub以使订阅者在发送200响应后异步完成POST请求.
  2. Enqueue Cloud Tasks添加到一个任务队列.我们不确定这些是否像我们在try 接近时看到的那样受到HTTPS termination的影响.
  3. 使用像Cloud Firestore这样的基于事件的触发器来写入具有某个id的文档,这将触发onCreate事件来完成POST请求.

其他相关细节是,我们现在有较小的流量作为一个较小的应用程序(远低于1000 QPS,这可能会排除Pub Sub),并希望在<1-2秒内创建后台任务的目标仍然有一个良好的用户体验(消息应用程序).

最初,我们的代码如下:

exports.example = functions
  .https
  .onRequest(async (request, response) => {
    const authHeader = request.get("authorization");
    const {statusCode, httpsResponseMessage} =
      verifyAuthHeader(authHeader, token);
    // Send 200 response to our caller within 10 seconds to avoid more retries.
    response.status(statusCode).send(
     {
      message: httpsResponseMessage,
     }
    );
    // do other POST requests
  }

我们还try 了:

exports.example = functions
  .https
  .onRequest(async (request, response) => {
    const authHeader = request.get("authorization");
    const {statusCode, httpsResponseMessage} =
      verifyAuthHeader(authHeader, token);
    // Send 200 response to our caller within 10 seconds to avoid more retries.
    response.write(
      JSON.stringify(
        {
          message: httpsResponseMessage
        }
      )
    );
    // do other POST requests
    res.end()
  }

但注意到我们的代码AFTERresponse.status(statusCode).send()的不可预测行为,如tips & tricks section of Firebase docs中所示,并且使用response.write发送部分结果并不能避免调用者future 的重试.

推荐答案

您需要非常小心地使用HTTP函数,并确保all您的工作已完成before您启动了响应.这在The Node.js Runtime中有记录:

在处理涉及回调或Promise个对象的异步任务时,必须显式通知运行库函数已完成这些任务的执行.您可以通过几种不同的方式完成此操作,如下面的示例所示.关键是,您的代码必须等待异步任务或Promise完成才能返回;否则,函数的异步组件可能会在完成之前终止.

// OK: await-ing a Promise before sending an HTTP response
await Promise.resolve();

// WRONG: HTTP functions should send an
// HTTP response instead of returning.
return Promise.resolve();

// HTTP functions should signal termination by returning an HTTP response.
// This should not be done until all background tasks are complete.
res.send(200);
res.end();

// WRONG: this may not execute since an
// HTTP response has already been sent.
return Promise.resolve();

实际上,这意味着您的两个示例永远不会像预期的那样工作,因为您是在完成// do other POST requests所描述的工作之前启动响应的.

从你的描述来看,这听起来像是:

  1. 您需要对呼叫者做出快速响应
  2. 当调用此函数时,您需要触发另外两个函数
  3. 但来自其他两个函数的响应不会用于对原始调用方的响应

如果是这种情况,我建议您使用发布/订阅触发的后台函数,并按如下方式构造此函数:

const { PubSub } = require('@google-cloud/pubsub')

const pubSubTopic1 = new PubSub('your-project-id').topic('the-first-topic-name')
const pubSubTopic2 = new PubSub('your-project-id').topic('the-second-topic-name')

exports.example = functions
  .https
  .onRequest(async (request, response) => {
    const authHeader = request.get("authorization");
    const {statusCode, httpsResponseMessage} = verifyAuthHeader(authHeader, token);

    // Publish messages to the Pub/Sub topics to trigger those functions
    await Promise.all([
      pubSubTopic1.publishMessage('message-1'),
      pubSubTopic2.publishMessage('message-2')
    ]);

    // Send 200 response to our caller within 10 seconds to avoid more retries.
    response.status(statusCode).send(
     {
      message: httpsResponseMessage,
     }
    );

    // Do nothing after calling .send() because the function has terminated
  }

发布/订阅速度快,延迟低,考虑到您正在处理的并发性数量,您的函数应该保持在热状态并快速启动.

不要将FiRestore用于此功能;创建一个触发函数的文档and会遇到成本问题,而创建大量必须保留或删除的文档会遇到成本问题,并且每个创建/删除操作都会产生成本.无论如何,FiRestore文档触发器的工作方式是通过发送发布/订阅消息,因此在您的流程中引入文档写入只会增加整个流程的延迟.


示例PubSub函数

正如在下面的注释中提到的,使用Firebase-Functions非常笨重,因此这里有一个不需要它的示例PubSub触发函数.

假设该函数期望接收一个JSON字符串作为发送到PubSub主题的消息.例如,在上面的HTTP函数中,您可能会发布如下消息:

const message1 = {
  "foo": "bar",
  "bar": "baz"
}
let statusCode = 202 // Accepted

try {
  await pubSubTopic1.publishMessage({ json: message1 })
} catch (error) {
  statusCode = 502 // Bad Gateway
} finally {
  res.status(statusCode).send({ message: httpsResponseMessage })
}

您的接收PubSub函数可以像下面这样小而简单:

export const examplePubSubFunction = message => {
  const data = JSON.parse(Buffer.from(message.data, 'base64').toString())
  console.log(data.foo) // prints "bar"
  console.log(data.bar) // prints "baz"
}

然后,您可以通过以下方式部署该功能:

gcloud functions deploy examplePubSubFunction --runtime=nodejs18 --trigger-topic=the-first-topic-name

Typescript相关问答推荐

推断类型

Typescript问题和缩减器状态不会在单击时添加用途.属性'状态和调度'不存在于userContextType类型上'|null'

如何在ts中使用函数重载推断参数类型

Angular 17 -如何使用新的@if语法在对象中使用Deliverc值

替代语法/逻辑以避免TS变量被分配之前使用." "

在TypeScript中,除了映射类型之外还使用的`in`二进制运算符?

如何在单击停止录制按钮后停用摄像机?(使用React.js和Reaction-Media-Recorder)

类型脚本中没有接口的中间静态类

TypeScrip原始字符串没有方法

如何判断对象是否有重叠的叶子并产生有用的错误消息

访问继承接口的属性时在html中出错.角形

使用打字Angular 中的通用数据创建状态管理

将具有Readonly数组属性的对象接口转换为数组

为什么我的导航在使用Typescript的React Native中不起作用?

无法将从服务器接收的JSON对象转换为所需的类型,因此可以分析数据

必需的输入()参数仍然发出&没有初始值设定项&q;错误

基于区分的联合键的回调参数缩小

替换typescript错误;X类型的表达式可以';t用于索引类型Y;带有未定义

JSX.Element';不可分配给类型';ReactNode';React功能HOC

在类型{}上找不到带有string类型参数的索引签名 - TypeScript