【问题标题】:Python serving html asyncPython 服务 html 异步
【发布时间】:2016-05-04 22:05:42
【问题描述】:

我有一个使用 nginx、uwsgi 和烧瓶提供 HTML 的服务器。这是一个简单的网络应用程序。当您刷新页面时,webapp 在其他地方进行一些 API 调用,然后处理从 API 返回的结果并生成 HTML,然后返回。

现在,大部分时间都花在等待 API 响应上。正因为如此,我使用 gevent 进行 API 调用和猴子补丁等,以使其全部并发。如果每次页面刷新都需要进行 3 次 API 调用,则无需等待第一次调用返回来发起第二次调用。您启动所有三个,等待他们的响应,然后生成要返回的 HTML。

但是,一个单独的问题是页面从头到尾的刷新本身是否是并发的。为了测试这一点,我编写了一个小的并发脚本来刷新我的服务器:

from gevent import monkey
monkey.patch_all()

import requests, datetime

import gevent

def call_func(n):
    before = datetime.datetime.now()
    print 'initiating call '+str(n),before
    requests.get('http://examplemyserver.com')
    after = datetime.datetime.now()
    print 'finishing call '+str(n),after, after-before

gevent_list = [ gevent.spawn(call_func, n=n) for n in range(10) ]

gevent.joinall(gevent_list)

结果如下:

initiating call 0 2016-05-04 23:03:49.437540
initiating call 1 2016-05-04 23:03:49.446304
initiating call 2 2016-05-04 23:03:49.447911
initiating call 3 2016-05-04 23:03:49.450232
initiating call 4 2016-05-04 23:03:49.451774
initiating call 5 2016-05-04 23:03:49.453331
initiating call 6 2016-05-04 23:03:49.454759
initiating call 7 2016-05-04 23:03:49.456301
initiating call 8 2016-05-04 23:03:49.457663
initiating call 9 2016-05-04 23:03:49.458981
finishing call 0 2016-05-04 23:03:51.270078 0:00:01.832538
finishing call 6 2016-05-04 23:03:52.169842 0:00:02.715083
finishing call 3 2016-05-04 23:03:52.907892 0:00:03.457660
finishing call 1 2016-05-04 23:03:53.498008 0:00:04.051704
finishing call 8 2016-05-04 23:03:54.150188 0:00:04.692525
finishing call 4 2016-05-04 23:03:55.032089 0:00:05.580315
finishing call 7 2016-05-04 23:03:55.994570 0:00:06.538269
finishing call 2 2016-05-04 23:03:56.659146 0:00:07.211235
finishing call 9 2016-05-04 23:03:57.149156 0:00:07.690175
finishing call 5 2016-05-04 23:03:58.002210 0:00:08.548879

所以你看,十个页面刷新实际上是同时启动的,但是它们是按顺序返回的,相差大约 1 秒。这表明虽然在内部同时进行外部 API 调用,但在给出 HTTP 响应之前,不会接受新的 HTTP 响应。

那么,鉴于 webapp 的大部分时间都花在等待 API 响应上,我怎样才能让整个过程并发呢?我需要用nginx改变什么吗?用 uwsgi?用烧瓶?我不知道这些东西的架构。

谢谢。

编辑 1: 按照 Rob 的想法,我将要调用的服务器替换为 'http://httpbin.org/delay/1'。这是我得到的结果:

initiating call 0 2016-05-04 23:36:18.697813
initiating call 1 2016-05-04 23:36:18.706510
initiating call 2 2016-05-04 23:36:18.708645
initiating call 3 2016-05-04 23:36:18.711055
initiating call 4 2016-05-04 23:36:18.712679
initiating call 5 2016-05-04 23:36:18.714051
initiating call 6 2016-05-04 23:36:18.715471
initiating call 7 2016-05-04 23:36:18.717015
initiating call 8 2016-05-04 23:36:18.718412
initiating call 9 2016-05-04 23:36:18.720193
finishing call 0 2016-05-04 23:36:20.599483 0:00:01.901670
finishing call 2 2016-05-04 23:36:20.600829 0:00:01.892184
finishing call 8 2016-05-04 23:36:20.611292 0:00:01.892880
finishing call 5 2016-05-04 23:36:20.618842 0:00:01.904791
finishing call 7 2016-05-04 23:36:20.620065 0:00:01.903050
finishing call 6 2016-05-04 23:36:20.621344 0:00:01.905873
finishing call 1 2016-05-04 23:36:20.622407 0:00:01.915897
finishing call 4 2016-05-04 23:36:20.623392 0:00:01.910713
finishing call 9 2016-05-04 23:36:20.624236 0:00:01.904043
finishing call 3 2016-05-04 23:36:20.625075 0:00:01.914020

这是您对可以同时接受 HTTP 调用的服务器所期望的。所以这说明我上面用来测试的代码是正确的,问题确实出在服务器上。它不会同时进行 HTTP 调用。

EDIT2:我在玩耍并检查了最简单的烧瓶应用程序是否与我的服务器具有相同的行为,所以我将其作为我的“最小示例”

考虑以下服务器:

import time

from flask import Flask, jsonify
app = Flask(__name__)

@app.route("/")
def hello():
    time.sleep(2)
    return ''

if __name__ == "__main__":
    app.run(host='0.0.0.0', port=80, debug=True)

现在通过获取前面的代码并将其指向http://localhost 来连接它。结果如下:

initiating call 0 2016-05-04 23:49:12.629250
initiating call 1 2016-05-04 23:49:12.638554
initiating call 2 2016-05-04 23:49:12.643844
initiating call 3 2016-05-04 23:49:12.645630
initiating call 4 2016-05-04 23:49:12.647866
initiating call 5 2016-05-04 23:49:12.649332
initiating call 6 2016-05-04 23:49:12.650853
initiating call 7 2016-05-04 23:49:12.652448
initiating call 8 2016-05-04 23:49:12.653865
initiating call 9 2016-05-04 23:49:12.655348
finishing call 0 2016-05-04 23:49:14.671281 0:00:02.042031
finishing call 1 2016-05-04 23:49:16.673649 0:00:04.035095
finishing call 2 2016-05-04 23:49:18.676576 0:00:06.032732
finishing call 3 2016-05-04 23:49:20.679746 0:00:08.034116
finishing call 4 2016-05-04 23:49:22.681615 0:00:10.033749
finishing call 5 2016-05-04 23:49:24.683817 0:00:12.034485
finishing call 6 2016-05-04 23:49:26.686309 0:00:14.035456
finishing call 7 2016-05-04 23:49:28.688079 0:00:16.035631
finishing call 8 2016-05-04 23:49:30.691382 0:00:18.037517
finishing call 9 2016-05-04 23:49:32.696471 0:00:20.041123

所以基本上一个简单的烧瓶 webapp 不会同时接受 HTTP 调用。我该如何更改?

【问题讨论】:

  • examplemyserver.com 也有可能是单线程的。它是“examplemyserver.com”一个生产服务器还是你为这个实验准备的东西?
  • 可能重复,当然相关:stackoverflow.com/questions/9501663/…
  • 使用 Python2.7.6,我无法重现您的结果。我将 URL 替换为 http://httpbin.org/delay/1,但除此之外我的代码与您的相同。对我来说,所有请求行或多或少同时发生,有 1 秒的延迟,所有响应行或多或少同时发生。
  • @Robᵩ 我替换了我的服务器的 url 以保护隐私。这不是真正的网址。
  • @Robᵩ 另外,它与您链接的问题无关。在那个问题中,OP 在通话中做错了什么。在我的情况下,每个调用都有预期的行为,但我认为服务器有问题。

标签: python nginx concurrency flask uwsgi


【解决方案1】:

简答:在启动 uwsgi 时使用--processes=N 和/或--threads=M

长答案:您描述的堆栈涉及三个组件: nginx 充当反向代理 Web 服务器; uwsgi 扮演应用服务器的角色;和烧瓶作为应用程序框架。数据流按该顺序发生,响应按相反顺序流动。

nginx 本质上是多任务的,flask 本质上是单任务的。 uwsgi 可以配置为两者之一。默认情况下,uwsgi 是单任务的。

这是我用来调查此问题的设置。我的操作系统是 Ubuntu 14.04。

/etc/nginx/sites-available/myapp:

server {
        listen 8100;
        location / {
                include uwsgi_params;
                uwsgi_pass unix:/tmp/uwsgi.sock;
        }
}

uwsgi 命令行:

One of:
  uwsgi -s /tmp/uwsgi.sock -w app:app
  uwsgi -s /tmp/uwsgi.sock -w app:app --processes=4
  uwsgi -s /tmp/uwsgi.sock -w app:app --threads=4

Followed by:
  chmod 777 /tmp/uwsgi.sock

app.py:

import time
from flask import Flask, render_template_string
app = Flask(__name__)

@app.route('/')
@app.route('/<name>')
def hello(name='World'):
    time.sleep(1)
    return render_template_string('''
        <html><body><p>
        Hello, <b>{{ name }}</b>!
        </p></body></html>''', name=name)

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

客户端:我从上面使用了您的 gevent/requests 客户端,替换为以下 url:http://localhost:8100/World

典型结果:

initiating call 0 2016-05-04 21:08:16.473978
initiating call 1 2016-05-04 21:08:16.485589
initiating call 2 2016-05-04 21:08:16.487258
initiating call 3 2016-05-04 21:08:16.490140
initiating call 4 2016-05-04 21:08:16.493158
initiating call 5 2016-05-04 21:08:16.494417
initiating call 6 2016-05-04 21:08:16.495620
initiating call 7 2016-05-04 21:08:16.497180
initiating call 8 2016-05-04 21:08:16.498413
initiating call 9 2016-05-04 21:08:16.499528
finishing call 3 2016-05-04 21:08:17.512949 0:00:01.022809
finishing call 6 2016-05-04 21:08:17.515944 0:00:01.020324
finishing call 7 2016-05-04 21:08:17.517655 0:00:01.020475
finishing call 4 2016-05-04 21:08:17.519555 0:00:01.026397
finishing call 2 2016-05-04 21:08:18.520249 0:00:02.032991
finishing call 0 2016-05-04 21:08:18.522902 0:00:02.048924
finishing call 5 2016-05-04 21:08:18.524902 0:00:02.030485
finishing call 1 2016-05-04 21:08:18.526682 0:00:02.041093
finishing call 9 2016-05-04 21:08:19.526909 0:00:03.027381
finishing call 8 2016-05-04 21:08:19.528926 0:00:03.030513

参考资料:

【讨论】:

  • 我认为您在几个组件之间进行了区分。非常感谢。
  • 嗨,我刚刚测试了这个,它完全有效。虽然我没有使用你的配置。因为我已经有了自己的配置,皇帝。我唯一需要做的就是在 uwsgi .ini 文件中添加行 processes = 4,就这么简单。就像你说的最佳数量取决于很多事情。就我而言,由于 99% 的时间都花在等待外部 API 上,我怀疑我的最佳进程数将是一个很大的数字。再次感谢。
猜你喜欢
  • 2014-06-18
  • 1970-01-01
  • 1970-01-01
  • 2014-04-25
  • 2016-05-12
  • 1970-01-01
  • 2017-08-16
  • 1970-01-01
  • 2016-09-29
相关资源
最近更新 更多