V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
param
V2EX  ›  Python

最近用 Python 的 asyncio,有好多不懂。。

  •  1
     
  •   param · 2017-03-14 17:25:45 +08:00 · 13297 次点击
    这是一个创建于 2571 天前的主题,其中的信息可能已经有所发展或是发生改变。

    我想实现的一个需求是这样的,我写一个 http 服务端(正在用 Flask ),接到一个用户浏览器发来的 request 之后,同时向多个上游服务器发送 request ,哪个上游服务器的 response 最快就拿谁的结果,把处理一下响应给用户浏览器。 有点类似 HTTP proxy ,只不过是多个上游服务器。由于需要同时向多个上游服务器发请求,我尝试了用 asyncio+aiohttp 。 可是这用在 Flask 里面应该怎么用啊? 难道我要在每个 route 里面写 loop.run_until_complete 吗?

    app = Flask(__name__)
    loop = asyncio.get_event_loop()
    
    @app.route("/list1/")
    def filelist1():
    	async def get_file_list1():
        	await dosomething1()
    	loop.run_until_complete(get_file_list1())
    
    
    @app.route("/list2/")
    def filelist2():
    	async def get_file_list2():
        	await dosomething2()
    	loop.run_until_complete(get_file_list2())
    

    另外有一个 https://github.com/Hardtack/Flask-aiohttp ,不知道有没有用,不知道这个插件解决了什么问题。。还是说要用tornado?也不知道tornado解决了什么问题。。

    第 1 条附言  ·  2017-03-14 18:37:30 +08:00
    同时向多个上游服务器发送请求,收到其中的一个 response 之后,就把其它的 IO cancel 掉。用 asyncio 的好处是可以 cancel 掉 IO 。
    之前曾经试过用多线程+requests ,但这种做法,当一个请求发出之后,还没收到 response 之前,线程就会被阻塞住,没法取消掉。于是了解了异步 IO ,随时可以 cancel 。
    第 2 条附言  ·  2017-03-14 21:24:19 +08:00
    非常感谢 @youyongsong 耐心的分析。
    我看完后的理解是, tornado 用一个 loop 来处理所有接收到的 http 请求,如果服务器收到非常频繁而密集的请求,这种模型是很高效的。

    而我的使用场景,并不是频繁接收请求,而是频繁发出请求。如果我也用传统方式,每接收到一个请求后, fork 出一个进程 /线程来处理该请求,同时在该进程 /线程中建立一个 loop ,并用这个 loop 来管理频繁发出的请求,是否可行?突然想到,如果所有进程 /线程都共用一个 loop 的话,会产生线程安全问题。
    第 3 条附言  ·  2017-03-14 22:00:43 +08:00
    还有一点就是,使用协成,出现异常调试起来麻烦一点
    23 条回复    2019-07-27 11:35:33 +08:00
    yongzhong
        1
    yongzhong  
       2017-03-14 17:57:41 +08:00   ❤️ 1
    u need tornado
    param
        2
    param  
    OP
       2017-03-14 18:05:41 +08:00 via Android
    @yongzhong 原因是什么。。 tornado 解决了什么问题?
    cljnnn
        3
    cljnnn  
       2017-03-14 18:09:55 +08:00
    你可以试看看 sanic 。
    a87150
        4
    a87150  
       2017-03-14 18:13:03 +08:00
    @param 大概是指异步
    param
        5
    param  
    OP
       2017-03-14 18:15:14 +08:00 via Android
    @a87150 我知道 tornado 跟异步有关。那么我在 Flask+uwsgi 里面用异步又有什么不一样呢?我想搞清楚。
    param
        6
    param  
    OP
       2017-03-14 18:16:18 +08:00 via Android
    @a87150 另外, gevent 用在 flask 上又扮演什么角色呢?
    director
        7
    director  
       2017-03-14 18:24:12 +08:00
    你可以看看 https://zhuanlan.zhihu.com/p/25530067 里面有很多对 asyncio 和 sanic 的内容。强烈关注作者的微信公众号
    param
        8
    param  
    OP
       2017-03-14 18:29:53 +08:00 via Android
    @cljnnn @director 谢谢,感觉 sanic 很棒。
    param
        9
    param  
    OP
       2017-03-14 18:31:04 +08:00 via Android
    感觉,好像要用异步的话,所有的库都要换成异步了。。还有,异步有异常的话不好调试。
    xyjtou
        10
    xyjtou  
       2017-03-14 18:32:43 +08:00 via Android
    从不同的上游服务器拿到的 response 相同吗?还是要再处理后返回给浏览器?
    param
        11
    param  
    OP
       2017-03-14 18:33:57 +08:00
    @xyjtou 不同的,要经过处理后再返回。
    justfly
        12
    justfly  
       2017-03-14 18:35:14 +08:00   ❤️ 2
    这个问题涉及的东西比较多 你要了解:

    1. 基于 epoll 等实现的 event loop
    2. 然后在此基础上理解类似 nodejs 的基于回调函数的异步处理
    3. 然后再了解 python 的 cotoutine 如何基于生成器实现的
    4. 然后理解 Python 的 Future 和 js 的 Promise 等如何解决基于回调的异步带来的回调嵌套问题
    5. 最后 async await 只是一个生成器协程的语法糖

    对比下 gevent, nodejs tornado asyncio golang erlang 和 线程模型,然后你就全都明白了。
    justfly
        13
    justfly  
       2017-03-14 18:37:15 +08:00
    @justfly cotoutine -> coroutine
    lbp0200
        14
    lbp0200  
       2017-03-14 19:00:46 +08:00
    youyongsong
        15
    youyongsong  
       2017-03-14 19:35:58 +08:00   ❤️ 17
    说下我对这 python 这几种 web 模型的理解吧:

    首先是 http server + wsgi server(container) + wsgi application 这种传统模型吧:
    http server 指的是类似于 nginx 或 apache 的服务
    wsgi server 指的是类似 gunicorn 和 uwsgi 这样的服务
    wsgi application 指的是 flask django 这样的基于 wsgi 接口的框架运行起来的实例
    最初这种模型只是为了方便 web 框架的开发者,不需要每个框架层面都去实现一遍 http server ,就增加了一个 WSGI 中间层协议,框架只要实现这个协议的客户端就可以,然后用 wsgi server 去实现 http 协议的解析并去调用客户端(wsgi application)。

    为了方便开发,每个框架都内置了一个简易的 wsgi server ,为什么还要用专门的 wsgi server 呢?
    wsgi 除了解析 http 协议以及 http 端口侦听外,还负责了流量转发以及 wsgi application 进程管理的功能。一般 wsgi 框架内置的 wsgi server 都是一个单进程,一次只能处理一个请求。而目的通用的 wsgi server(gunicorn, uwsgi)都至少支持 pre fork 模型,这种模型会起一个 master 来侦听请求,并启动多个 slave(每个 slave 是一个 wsgi application), master 负责把请求转发到空闲的 slave 上。除了这种传统的基于进程的 pre fork 同步模型,不同的 wsgi server 也会支持一些其它模型,有基于线程的同步模型,也有基于 asyncio 的异步模型。

    这种模型下怎样写异步代码呢?
    1. 直接用传统的异步编程(进程,线程,协程),虽然有些 wsgi server 支持 asynio 模型,但是这也需要用户所写的代码做相应的支持。这就导致了如果我们在 wsgi application 的时候不能随便使用线程和异步 IO ,如果用了就需要配置 wsgi server 使其支持我们自己的写法。因此为了使得我们缩写的 application 能部署在任意的 wsgi server(container)中,我们就只能写同步代码了。
    2. 使用分布式异步编程,使用类似 celery 的方式,将需要异步处理的东西发送到 worker 去处理。

    既然有了 wsgi server ,为什么还要有一个 http server 呢?
    主要是因为 wsgi server 支持的并发量比较低,一般会用一个专门的 http server 来做一层缓冲,避免并发量过大时直接服务挂掉。


    python 传统的这种 wsgi 模型,主要是为了方便框架开发者只需要专注框架层面,而非 http 处理层面。但这样却增加了服务部署的复杂度,需要同时部署和配置 http server 和 wsgi server ,如果想支持异步还要部署 worker ,而使用 tornado 或 go 开发的应用因为自己实现了高效 http 处理的应用只需要部署自己就可以了。


    接下来是 tornado 和 twisted 这种模型:
    这种模型和上面的传统模型处于一个时期,这种模型和 nodejs 差不多,都是基于回调的模型,适用于高 IO 低 CPU 的场景。这种模型自己实现了一个基于回调 http server(event loop),每一个请求都被注册成一个异步函数来处理,然后主循环来不断的循环这些函数。这样就和 pre fork 模型有了区别, pre fork 模型中每一个 slave 都是一个 wsgi application ,一个 wsgi application 都只能处理一个请求,而回调模型只有一个线程,不仅极大的减少了内存的分配还减小了进城以及线程间的切换开销,从而可以支持高 IO 并发。但是这种模型也有很明显的缺点,就是一旦应用程序有大量的 CPU 计算,就会让这个线程堵住,所有的请求都会收到影响,如果应用在处理一个请求时崩溃,所有的请求也都会收到影响。


    接下来时 aiohttp/sanic 这种模型:
    这种模型和 tornada 模型的改进,但实质上是一样的,因为回调的写法不易读也容易出错,于是将回调的写法改成了同步的写法。这种模型和 koa2 和 go net/http 查不多, asyncio 提供了类似 go coroutine 的功能和写法,而 aiohttp 则提供了类似 go 中的 net/http 的 http 处理库。
    eloah
        16
    eloah  
       2017-03-14 19:41:35 +08:00
    怎么说呢......Flask 是个 web 框架,但是并不是一个 webserver,这也就是你的前面还要加 nginx/unicorn 的原因(当然一般还会有 uwsgi)......简单但是不准确的来说 flask 只负责处理请求,而不负责收发请求......
    而楼上提到的 tornado 有 web 框架,也有 webserver.
    另外,楼主这个应该使用负载均衡更好一点吧,这种竞争式的其实一般来说效率反而底下.
    Kilerd
        17
    Kilerd  
       2017-03-14 20:05:43 +08:00
    @youyongsong 好久没有见过那么仔细地分析的了。
    param
        18
    param  
    OP
       2017-03-14 21:05:26 +08:00
    @eloah nginx flask uwsgi 的关系我理解。只是关于 tornado 的东西还不理解
    AlisaDestiny
        19
    AlisaDestiny  
       2017-03-15 10:55:40 +08:00
    @youyongsong 厉害呀。理解这么透彻。服。虽然我看不懂。但是感觉很厉害。
    Damnever
        20
    Damnever  
       2017-03-19 20:29:54 +08:00
    param
        21
    param  
    OP
       2017-03-19 20:35:21 +08:00
    @Damnever 我把 flask 换成了 sanic ,然后发现 aiohttp 也可以构建异步 http 的服务端
    thechosenone
        22
    thechosenone  
       2018-09-10 14:56:34 +08:00
    好贴好回复,mark
    tmackan
        23
    tmackan  
       2019-07-27 11:35:33 +08:00
    @youyongsong
    感谢
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   5389 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 28ms · UTC 06:59 · PVG 14:59 · LAX 23:59 · JFK 02:59
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.