【问题标题】:Long running script from flask endpoint来自烧瓶端点的长时间运行脚本
【发布时间】:2019-02-23 03:19:39
【问题描述】:

我一直在努力解决这个问题,希望其他人已经遇到过这个问题并且知道如何解决它:)

我正在尝试构建一个非常简单的 Flask 端点,它只需要调用一个长时间运行的阻塞 php 脚本(想想 while true {...})。我尝试了几种不同的方法来异步启动脚本,但问题是我的浏览器实际上从未收到响应,即使执行了运行脚本后生成响应的代码。

我尝试过同时使用multiprocessingthreading,但似乎都不起作用:

# multiprocessing attempt
@app.route('/endpoint')
def endpoint():
  def worker():
    subprocess.Popen('nohup php script.php &', shell=True, preexec_fn=os.setpgrp)

  p = multiprocessing.Process(target=worker)
  print '111111'
  p.start()
  print '222222'
  return json.dumps({
    'success': True
  })

# threading attempt
@app.route('/endpoint')
def endpoint():
  def thread_func():
    subprocess.Popen('nohup php script.php &', shell=True, preexec_fn=os.setpgrp)

  t = threading.Thread(target=thread_func)
  print '111111'
  t.start()
  print '222222'
  return json.dumps({
    'success': True
  })

在这两种情况下,我都看到了111111222222,但我的浏览器仍然挂起来自端点的响应。我已经尝试过p.daemon = True 以及p.terminate(),但没有运气。我曾希望在不同的 shell 和单独的进程/线程中启动一个带有 nohup 的脚本可以正常工作,但不知何故 Flask 或 uWSGI 会受到它的影响。

更新

由于当我直接使用python app.py 启动我的 Flask 应用程序并直接点击它而不通过我的 Nginx 代理和 uWSGI 时,这确实在我的 Mac 上本地工作,我开始相信它可能不是代码本身有问题。而且因为我的 Nginx 只是将请求转发给 uWSGI,所以我相信它可能是导致它的原因。

这是我在帝王模式下运行的 uWSGI 域的 ini 配置:

[uwsgi]
protocol = uwsgi
max-requests = 5000
chmod-socket = 660
master = True
vacuum = True
enable-threads = True
auto-procname = True
procname-prefix = michael-
chdir = /srv/www/mysite.com
module = app
callable = app
socket = /tmp/mysite.com.sock

【问题讨论】:

  • 在flask中甚至可以运行php吗?
  • 是的,你只需运行一个 shell 命令 :)
  • 我不得不说我有点困惑。我在本地复制了您的设置,一切看起来都很好。发生的唯一 奇怪 事情是 threading 尝试只启动被调用两次的进程,否则 HTTP 服务器总是在最短的时间内响应......你试过 curl ?
  • 是的,我也尝试过本地卷曲。你确定你调用的php脚本是无限的while (true) {}
  • 你试过用 uWSGI 和至少 2 个线程运行烧瓶吗?

标签: python python-2.7 flask uwsgi


【解决方案1】:

这种东西是Python Celery (https://docs.celeryproject.org/) 的实际用例,也可能是主要用例。作为一般规则,不要在wsgi 进程中运行受 CPU 限制的长时间运行的作业。这很棘手,效率低下,最重要的是,它比在 celery worker 中设置 async 任务更复杂。如果您只想制作原型,您可以将代理设置为memory,而不使用外部服务器,或者在同一台机器上运行单线程redis

这样你可以启动任务,调用 task.result() 阻塞,但它以 IO-bound 方式阻塞或者更好的是你可以通过检索立即返回task_id 并构建第二个端点 /result?task_id=<task_id> 检查结果是否可用:

result = AsyncResult(task_id, app=app)
if result.state == "SUCCESS":
   return result.get()
else:
   return result.state  # or do something else depending on the state

这样你就有了一个非阻塞的wsgi 应用程序,它可以做最适合的事情:短时间的 CPU 非绑定调用,最多有操作系统级调度的 IO 调用,那么你可以直接依赖@987654331 @ server workers|processes|threads 或任何你需要在任何 wsgi 服务器(如 uwsgi、gunicorn 等)中扩展 API 的任何东西,因为 celery 通过增加工作进程的数量来水平扩展 99% 的工作负载。

【讨论】:

  • 我对工作非常熟悉,但我试图在不引入更多依赖项的情况下做到这一点。不过谢谢你的建议!
【解决方案2】:

这种方法对我有用,它在命令行中调用超时命令(睡眠 10 秒)并让它在后台工作。它立即返回响应。

@app.route('/endpoint1')
def endpoint1():
    subprocess.Popen('timeout 10', shell=True)
    return 'success1'

但是,不是在 WSGI 服务器上测试,而是在本地测试。

【讨论】:

  • 是的,事实上我在本地运行也没有问题,但挑战是在我的服务器上运行 uWSGI 似乎有问题。万一你错过了,我在我的问题更新以及问题下的聊天线程中提到了这一点:)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-03-29
  • 2016-12-12
  • 2019-02-12
  • 2015-06-17
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多