【问题标题】:Problem running Flask App on Google Cloud App Engine在 Google Cloud App Engine 上运行 Flask 应用程序时出现问题
【发布时间】:2021-06-12 19:59:34
【问题描述】:

所以我有一个简单的 Flask 应用程序(未连接数据库),它需要执行一个函数,其参数通过表单解析。这个函数处理大量数据(并且需要几分钟),它被分成几部分,然后产生一些字符串。这个想法是,当函数运行时,这些字符串会动态地出现在模板上。我能够做到这一点,当应用程序托管在本地服务器上时,它可以正常运行。但是,当部署到 Google Cloud App Engine 时,它​​不允许我动态地输出这些字符串,而是等到所有函数都执行后才输出它们。 ma​​in.py

from flask import Flask, render_template, redirect, url_for, request, session, flash, Response
import requests
from jinja2 import Environment
from jinja2.loaders import FileSystemLoader

app = Flask(__name__)
app.secret_key = "123"

def my_function(arg1, arg2, arg3):
   #Does something and defines string1
   yield string1
   #Does something else and defines string2
   yield string2
   #Does something else and defines string3
   yield string3

@app.route('/', methods=['GET', 'POST'])
def home():
    if request.method == 'POST':
       arg1 = request.form.get('arg1')
       arg2 = request.form.get('arg2')
       arg3 = request.form.get('arg3')

       env = Environment(loader=FileSystemLoader('templates'))
       tmpl = env.get_template('result.html')
       return Response(tmpl.generate(result = my_function(arg1, arg2, arg3)))

   return render_template("index.html")

if __name__ == "__main__":
    app.run(debug=True)

index.html

<form method="POST">
   <input type="text" name="arg1">
   <input type="text" name="arg2">
   <input type="text" name="arg3">
   <input type="submit">
</form>

result.html

<div>
    {% for line in result %}
       <p>{{ line }}</p>
    {% endfor %}
</div>

app.yaml

runtime: python37

运行 $ python main.py 并提交表单后的终端输出

"GET / HTTP/1.1" 200
"POST / HTTP/1.1" 200

并在 my_function() 运行时打开 result.html

部署和运行 $ gcloud app logs read 后的终端输出

https://i.stack.imgur.com/6lBIt.png

并停留在 index.html 上,直到 my_function() 结束运行,然后打开 result.html

【问题讨论】:

    标签: python google-app-engine flask gcloud


    【解决方案1】:

    我遇到了同样的问题:我需要在函数运行时使用部署在 Google App Engine (GAE) 中的 Flask 创建一个进度条。我需要处理大量数据,并且希望让用户了解流程的当前状态(减少客户的不确定性)。

    受@Suresh Devalapalli 的Medium post: How to generate multiple progress bars in a Flask app 和他的GIT 的启发,我遵循了相同的步骤,它在本地 Flask 中完美运行,但是当我将它部署到 GAE 时,不允许我动态查看输出,相反,当程序达到 100% 时,所有消息一起显示。

    阅读有关 GAE 限制的更多信息,我发现(如@DazWilkin 所说)App Engine 会等到响应完成后再发送给客户端。此外,GAE 显然每次只能管理一个线程,因此所需的行为是不可能的,因为您至少需要另一个线程不断询问后端的状态。

    然后我在other StackOverflow question 中找到了@Justin Beckwith 的答案,这激发了我阅读更多关于“App Engine 柔性环境”的信息。我从official documentation 学到了更多,在这里你可以看到“标准环境”和“灵活环境”之间的比较。有关操作和成本限制,请阅读官方文档。可以解决此问题的关键功能是后台线程:标准环境为“是,有限制”,灵活环境为“是”。这让我尝试了在 GAE 灵活环境中部署的 @Suresh Devalapalli 解决方案。 令我惊讶的是,它成功了!!在本地 Flask 和 GAE Flex 中的行为相同。

    所以,@matilde,我将展示我的代码,可以帮助你:

    main.py

    # DAVIDRVU - 2021-04-02
    from flask import Flask, render_template, request, session, Response
    import time
    
    app = Flask(__name__)
    app.secret_key = "123"
    
    @app.route('/my_function')
    def my_function():
        arg1 = session.get('arg1')
        arg2 = session.get('arg2')
        arg3 = session.get('arg3')
    
        def generate(arg1, arg2, arg3):
            #Does something and defines string1
            time.sleep(2)
            string1 = "arg1 = " + str(arg1)
            yield "data:" + str(string1) + "\n\n"
        
            #Does something else and defines string2
            time.sleep(2)
            string2 = "arg2 = " + str(arg2)
            yield "data:" + str(string2) + "\n\n"
        
            #Does something else and defines string3
            time.sleep(2)
            string3 = "arg3 = " + str(arg3)
            yield "data:" + str(string3) + "\n\n"
    
            yield "data:END_SIGNAL\n\n"
    
        resp = Response(generate(arg1, arg2, arg3), mimetype= 'text/event-stream', headers={'X-Accel-Buffering': 'no'})
        return resp
    
    @app.route('/flask_post', methods=['POST'])
    def flask_post():
        session['arg1'] = request.form.get('arg1')
        session['arg2'] = request.form.get('arg2')
        session['arg3'] = request.form.get('arg3')
        return render_template('result.html')
    
    @app.route('/')
    def root():
        return render_template('index.html')
    
    if __name__ == "__main__":
        app.run(host='127.0.0.1', port=8080, debug=True)
    

    main.py 的亮点:

    1. 使用会话将变量从一个 Flask 函数传递到另一个函数。
    2. 使用以下命令显式指示事件流类型:mimetype= 'text/event-stream'
    3. 明确表示您不想使用以下方法缓冲输出:headers={'X-Accel-Buffering': 'no'}。如果您不配置标头,则在函数结束之前您不会收到消息。

    index.html

    <!DOCTYPE html>
    <html>
    <head>
    </head>
    
    <body>
        <form action="{{ url_for('flask_post') }}" method="POST">
        <input type="text" name="arg1">
        <input type="text" name="arg2">
        <input type="text" name="arg3">
        <input type="submit">
        </form>
    </body>
    </html>
    

    result.html

    <!DOCTYPE html>
    <html>
    <head>
        <script>
            var source = new EventSource("/my_function");
            document.open();
            source.onmessage = function(event) {
                if(event.data == "END_SIGNAL"){
                    source.close()
                }
                else{
                    console.log(event.data);
                    document.write("<p>" + event.data +"</p>");
                }
            }
            document.close();
        </script>
    </head>
    <body>
    </body>
    </html>
    

    result.html 的亮点: JavaScript 不带参数调用“my_function”,并捕获每条消息,直到数据与“END_SIGNAL”一起出现。这些参数是使用 session.get 获得的,这是一个 Flask 解决方法。

    requirements.txt

    Flask
    gunicorn
    

    app.yaml

    #################################################################################
    # CASE 1: Standard environment AppEngine
    #################################################################################
    #runtime: python37
    
    #entrypoint: gunicorn -b :$PORT main:app
    
    #################################################################################
    # CASE 2: Flexible Environment AppEngine
    #################################################################################
    runtime: python
    env: flex
    runtime_config:
        python_version: 3
    
    manual_scaling:
      instances: 2
    resources:
      cpu: 1
      memory_gb: 4
      disk_size_gb: 10  # Disk size must be between 10GB and 10240GB
    
    entrypoint: gunicorn -b :$PORT main:app
    #################################################################################
    

    app.yaml 的亮点: 这是此实施中最重要的部分。我亲自测试了这两种环境并观察了不同的行为:

    1. 案例 1(注释行),用于标准环境部署:函数结束时,会出现输出。

    2. 案例 2,用于灵活环境部署:我终于获得了所需的行为,在功能仍在工作时“实时”将来自后端的消息显示到 HTML,允许您从后端显示当前状态。请注意,每个环境所需的参数不同,Flex 环境中的“部署时间”更长。

    我将代码上传到我的GIT

    希望对你有帮助!

    【讨论】:

      【解决方案2】:

      正如@DazWilkin 所说 - App Engine 不支持流式传输。此外,App Engine 对每个请求都有 1 分钟的截止时间,即作为标准应用的一部分对 App Engine 的每次调用都必须在一分钟内返回

      关于你的问题,你可以

      1. 对您端的路由进行 ajax 调用,该路由会启动一个函数(在不同的线程上运行)。此函数运行并将输出转储到列表中
      2. 然后,您的 Ajax 调用会启动轮询例程,即每隔 X 秒,调用上面 #1 中的函数并在输出存储桶中返回任何值

      它可以解决您的问题,但结果不是即时的 - 它们取决于您轮询服务器的频率。

      如果您确实需要实时响应并且您的应用程序很大或很大,那么您将不得不研究 PubSub,但这不会在应用引擎上运行(如前所述)。你必须走谷歌计算引擎(GCE)的路线

      【讨论】:

        【解决方案3】:

        App Engine 会等待响应完成,然后再将其发送到客户端。您不能流式传输(使用yield)响应。

        https://cloud.google.com/appengine/docs/standard/python/how-requests-are-handled#Python_Responses

        【讨论】:

        • 那么如何从 my_function() 流式传输这些响应?
        • 这是一个不同的问题。请考虑接受我的回答。
        猜你喜欢
        • 2019-10-08
        • 2014-01-20
        • 2012-11-21
        • 2020-01-22
        • 1970-01-01
        • 2019-04-22
        • 2019-07-18
        • 2013-12-20
        • 2020-02-12
        相关资源
        最近更新 更多