【问题标题】:How to let a single Cloud Run instance (with Gunicorn sync workers) handle multiple POST requests from Pub/Sub?如何让单个 Cloud Run 实例(带有 Gunicorn 同步工作者)处理来自 Pub/Sub 的多个 POST 请求?
【发布时间】:2021-05-18 16:56:06
【问题描述】:

场景

我正在尝试部署一个用 Python 编写的应用程序,该应用程序使用 Flask 和 gunicorn 将电子邮件文件解析为 Google Cloud Run 上的图像。 Cloud Run 实例由来自 Pub/Sub 主题的 POST 请求触发。我试图让我的 Cloud Run 实例通过使用带有 gunicorn 的多个同步工作者来处理多个请求,但我似乎无法实现这一点。在过去的几个小时里,我一直在浏览 SO 和 google,但我就是想不通。我觉得我错过了一些非常简单的东西。

我的管道应该是这样的:

  1. 新电子邮件被放置在存储桶中。
  2. 存储向 Pub/Sub 主题发送通知。
  3. Pub/Sub 向 Cloud Run 实例的 HTTPS 端点执行 POST 请求。
  4. Cloud Run 将电子邮件处理为图像并将结果保存在另一个存储桶中。

设置

我已使用 --concurrency=3--max-instances=10 配置 Cloud Run 服务。 Pub/Sub 使用 10 分钟(600 秒)的 --ack-deadline。这就是我的 Cloud Run 实例的启动方式:

CMD exec gunicorn --bind :$PORT main:app --workers 3 --timeout 0

我的(简体)main.py

import os
import json
import base64
import traceback
from flask import Flask, request
from flask_cors import CORS
from flask_sslify import SSLify

from src.utils.data_utils import images_from_email

app = Flask(__name__)
CORS(app, supports_credentials=True)
sslify = SSLify(app)

@app.route("/", methods=['GET', 'POST'])
def preprocess_emails():
    envelope = request.get_json()
    data = json.loads(base64.b64decode(pubsub_message["data"]).decode())

    try:
        # function that processes email referenced in pubsub message to images
        fn, num_files, img_bucket, processed_eml_bucket = images_from_email(data)
    
        # here I do some logging
        return "", 204

    except Exception as e:
        traceback.print_exception(type(e), e, e.__traceback__)
        return "", 500

    return "", 500

if __name__ == "__main__":
    app.run(ssl_context="adhoc", host="0.0.0.0", port=int(os.environ.get("PORT", 8080)))

问题

通过上述设置,我希望我的 Cloud Run 服务一次可以处理 30 个请求。每个实例应该能够处理 3 个请求(每个 gunicorn worker 1 个)并且最多可以生成 10 个实例。实际发生的情况如下:

只要我在存储桶中放入 15 封新电子邮件,就会通过 Pub/Sub 向我的 Cloud Run 端点发出 15 个 POST 请求。但是,Cloud Run 并没有生成 5 个 Cloud Run 实例,每个实例处理 3 个请求,而是立即尝试为每个(!)请求生成一个实例(带有 3 个工作人员),看起来 3 个工作人员中的每一个都在处理相同的请求。这最终导致HTTP 429 错误“由于没有可用的实例,请求被中止”。我还在日志中注意到,一些电子邮件文件同时由多个 Cloud Run 实例处理。我究竟做错了什么?这是否与必须以某种方式在我的 Python 代码中启用多处理有关,或者这与 gunicorn/Cloud Run/PubSub 相关?

【问题讨论】:

  • Google Cloud Run 实例的生命周期以 HTTP 请求(在您的情况下为 POST)开始,并在请求返回响应时结束。不支持后台处理。本文档将帮助您了解 Cloud Run 提供的功能:cloud.google.com/run/docs/reference/container-contract
  • 如果您只发送一封电子邮件,它是否按预期工作?另外,可不可以尝试增加内存(设置2Gb确定),看看行为是否一样。
  • Cloud Run 设计是每个实例的一对 N 请求。除了为并发设置正确的命令行选项并且不使实例过载之外,您无需执行任何操作来接收 N 个请求。从最简单的角度来看,什么都不做。如果设置为这样做,您的实例将收到多个请求(默认为每个实例 80 个请求)。
  • 不,你不是在正确的路径上创建工作线程。编写一个处理请求并返回响应的函数。这就是您的示例所需的全部内容。工作线程只会浪费资源并被终止,因为请求之间没有后台处理。有重叠请求等例外情况,但您远不需要高级技术。请记住,Cloud Run 前面有一个全局前端 (GFE),用于处理代理、路由请求等。您的代码不会做出这些决定。
  • 您可以在 HTTP 请求和您的 HTTP 响应之间做任何您想做的事情,只要 Cloud Run 支持它。我没有分析你的代码,但这个概念很好。忘记尝试使用工人/线程等创建复杂系统。注意:我不确定“电子邮件到图像”是什么意思。不要回答。删除此问题并重新开始。评论太长了,很少有人看。

标签: python flask google-cloud-platform multiprocessing gunicorn


【解决方案1】:

Google Cloud Run 实例的生命周期从 HTTP 请求开始,在您的情况下为 POST,并在请求返回响应时结束。不支持后台处理。

这就是该设计没有意义的原因。 Cloud Run 是一个 HTTP 请求/响应系统。位于 Cloud Run 前面的 GFE(Goblal FrontEnd)决定了哪个实例接收请求(当前实例或另一个实例)。

Cloud Run 设计是针对每个实例的一对 N 请求。除了为并发设置正确的命令行选项并且不使实例过载之外,您无需执行任何操作来接收 N 个请求。从最简单的角度来看,什么都不做。如果设置为这样,您的实例将收到多个请求(默认为每个实例 80 个请求)。

编写一个处理请求并返回响应的函数。这就是您的示例所需的全部内容。工作线程只会浪费资源并被终止,因为请求之间没有后台处理。

请记住,Cloud Run 前面有一个全局前端 (GFE),用于处理代理、路由请求等。您的代码不会做出这些决定。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-06-15
    • 1970-01-01
    • 2021-12-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多