【发布时间】:2021-05-18 16:56:06
【问题描述】:
场景
我正在尝试部署一个用 Python 编写的应用程序,该应用程序使用 Flask 和 gunicorn 将电子邮件文件解析为 Google Cloud Run 上的图像。 Cloud Run 实例由来自 Pub/Sub 主题的 POST 请求触发。我试图让我的 Cloud Run 实例通过使用带有 gunicorn 的多个同步工作者来处理多个请求,但我似乎无法实现这一点。在过去的几个小时里,我一直在浏览 SO 和 google,但我就是想不通。我觉得我错过了一些非常简单的东西。
我的管道应该是这样的:
- 新电子邮件被放置在存储桶中。
- 存储向 Pub/Sub 主题发送通知。
- Pub/Sub 向 Cloud Run 实例的 HTTPS 端点执行 POST 请求。
- 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