其中Tornado的定義是 Web 框架和異步網絡庫翅帜,其中他具備有異步非阻塞能力怜浅,能解決他兩個框架請求阻塞的問題,在需要并發(fā)能力時候就應該使用Tornado行楞。
但是在實際使用過程中很容易把Tornado使用成異步阻塞框架攒暇,這樣對比其他兩大框架沒有任何優(yōu)勢而言,本文就如何實現真正的異步非阻塞記錄子房。
以下使用的 Python 版本為 2.7.13
平臺為 Macbook Pro 2016
在 Tornado 中兩個裝飾器:
tornado.web.asynchronous
tornado.gen.coroutine
asynchronous 裝飾器是讓請求變成長連接的方式形用,必須手動調用self.finish()才會響應
class MainHandler(tornado.web.RequestHandler):
????@tornado.web.asynchronous
????def get(self):# bad?
????????self.write("Hello, world")
asynchronous 裝飾器不會自動調用self.finish(),如果沒有沒有指定結束证杭,該長連接會一直保持直到 pending 狀態(tài)田度。
所以正確是使用方式是使用了 asynchronous 需要手動 finish
class MainHandler(tornado.web.RequestHandler):
????@tornado.web.asynchronous????
????def get(self):
????????self.write("Hello, world")
????????self.finish()
coroutine 裝飾器是指定改請求為協(xié)程模式,說明白點就是能使用yield配合 Tornado 編寫異步程序解愤。
Tronado 為協(xié)程實現了一套自己的協(xié)議镇饺,不能使用 Python 普通的生成器。
在使用協(xié)程模式編程之前要知道如何編寫 Tornado 中的異步函數送讲,Tornado 提供了多種的異步編寫形式:回調奸笤、Future、協(xié)程等哼鬓,其中以協(xié)程模式最是簡單和用的最多监右。
編寫一個基于協(xié)程的異步函數同樣需要 coroutine 裝飾器
@gen.coroutine
def sleep(self):
????yield gen.sleep(10)
????raise gen.Return([1,2,3,4,5])
這就是一個異步函數,Tornado 的協(xié)程異步函數有兩個特點:
需要使用 coroutine 裝飾器
返回值需要使用raise gen.Return()當做異常拋出
返回值作為異常拋出是因為在 Python 3.2 之前生成器是不允許有返回值的异希。
使用過 Python 生成器應該知道健盒,想要啟動生成器的話必須手動執(zhí)行next()方法才行,所以這里的 coroutine 裝飾器的其中一個作用就是在調用這個異步函數時候自動執(zhí)行生成器称簿。
使用 coroutine 方式有個很明顯是缺點就是嚴重依賴第三方庫的實現扣癣,如果庫本身不支持 Tornado 的異步操作再怎么使用協(xié)程也是白搭依然會是阻塞的,放個例子感受一下憨降。
import time
import logging
import tornado.ioloop
import tornado.web
import tornado.options
from tornado import gen
tornado.options.parse_command_line()
class MainHandler(tornado.web.RequestHandler):
? ? @tornado.web.asynchronous
? ? def get(self):
? ? ? ? self.write("Hello, world")
? ? ? ? self.finish()
class NoBlockingHnadler(tornado.web.RequestHandler):
? ? @gen.coroutine
? ? def get(self):
? ? ? ? yield gen.sleep(10)
? ? ? ? self.write('Blocking Request')
class BlockingHnadler(tornado.web.RequestHandler):
? ? def get(self):
? ? ? ? time.sleep(10)
? ? ? ? self.write('Blocking Request')
def make_app():
? ? return tornado.web.Application([
? ? ? ? (r"/", MainHandler),
? ? ? ? (r"/block", BlockingHnadler),
? ? ? ? (r"/noblock", NoBlockingHnadler),
? ? ], autoreload=True)
if __name__ == "__main__":
? ? app = make_app()
? ? app.listen(8000)
? ? tornado.ioloop.IOLoop.current().start()
為了顯示更明顯設置了 10 秒
當我們使用yield gen.sleep(10)這個異步的 sleep 時候其他請求是不阻塞的父虑。
當使用time.sleep(10)時候會阻塞其他的請求。
這里的異步非阻塞是針對另一請求來說的授药,本次的請求該是阻塞的仍然是阻塞的频轿。
gen.coroutine在 Tornado 3.1 后會自動調用self.finish()結束請求垂涯,可以不使用asynchronous裝飾器。
所以這種實現異步非阻塞的方式需要依賴大量的基于 Tornado 協(xié)議的異步庫航邢,使用上比較局限耕赘,好在還是有一些可以用的異步庫
使用gen.coroutine裝飾器編寫異步函數,如果庫本身不支持異步膳殷,那么響應任然是阻塞的操骡。
在 Tornado 中有個裝飾器能使用ThreadPoolExecutor來讓阻塞過程編程非阻塞,其原理是在 Tornado 本身這個線程之外另外啟動一個線程來執(zhí)行阻塞的程序赚窃,從而讓 Tornado 變得阻塞册招。
futures 在 Python3 是標準庫,但是在 Python2 中需要手動安裝
pip install futures
import logging
import tornado.ioloop
import tornado.web
import tornado.options
from tornado import gen
from tornado.concurrent import run_on_executor
from concurrent.futures import ThreadPoolExecutor
tornado.options.parse_command_line()
class MainHandler(tornado.web.RequestHandler):
? ? @tornado.web.asynchronous
? ? def get(self):
? ? ? ? self.write("Hello, world")
? ? ? ? self.finish()
class NoBlockingHnadler(tornado.web.RequestHandler):
? ? executor = ThreadPoolExecutor(4)
? ? @run_on_executor
? ? def sleep(self, second):
? ? ? ? time.sleep(second)
? ? ? ? return second
? ? @gen.coroutine
? ? def get(self):
? ? ? ? second = yield self.sleep(5)
? ? ? ? self.write('noBlocking Request: {}'.format(second))
def make_app():
? ? return tornado.web.Application([
? ? ? ? (r"/", MainHandler),
? ? ? ? (r"/noblock", NoBlockingHnadler),
? ? ], autoreload=True)
if __name__ == "__main__":
? ? app = make_app()
? ? app.listen(8000)
? ? tornado.ioloop.IOLoop.current().start()
ThreadPoolExecutor是對標準庫中的 threading 的高度封裝勒极,利用線程的方式讓阻塞函數異步化是掰,解決了很多庫是不支持異步的問題。
但是與之而來的問題是辱匿,如果大量使用線程化的異步函數做一些高負載的活動键痛,會導致該 Tornado 進程性能低下響應緩慢,這只是從一個問題到了另一個問題而已匾七。
所以在處理一些小負載的工作絮短,是能起到很好的效果,讓 Tornado 異步非阻塞的跑起來昨忆。
但是明明知道這個函數中做的是高負載的工作丁频,那么你應該采用另一種方式,使用 Tornado 結合 Celery 來實現異步非阻塞邑贴。
Celery 是一個簡單席里、靈活且可靠的,處理大量消息的分布式系統(tǒng)拢驾,專注于實時處理的任務隊列奖磁,同時也支持任務調度。
Celery 并不是唯一選擇独旷,你可選擇其他的任務隊列來實現,但是 Celery 是 Python 所編寫寥裂,能很快的上手嵌洼,同時 Celery 提供了優(yōu)雅的接口,易于與 Python Web 框架集成等特點封恰。
與 Tornado 的配合可以使用tornado-celery麻养,該包已經把 Celery 封裝到 Tornado 中,可以直接使用诺舔。
實際測試中鳖昌,由于 tornado-celery 很久沒有更新备畦,導致請求會一直阻塞,不會返回
解決辦法是:
把 celery 降級到 3.1pip install celery==3.1
把 pika 降級到 0.9.14pip install pika==0.9.14
import time
import logging
import tornado.ioloop
import tornado.web
import tornado.options
from tornado import gen
import tcelery, tasks
tornado.options.parse_command_line()
tcelery.setup_nonblocking_producer()
class MainHandler(tornado.web.RequestHandler):
? ? @tornado.web.asynchronous
? ? def get(self):
? ? ? ? self.write("Hello, world")
? ? ? ? self.finish()
class CeleryHandler(tornado.web.RequestHandler):
? ? @gen.coroutine
? ? def get(self):
? ? ? ? response = yield gen.Task(tasks.sleep.apply_async, args=[5])
? ? ? ? self.write('CeleryBlocking Request: {}'.format(response.result))
def make_app():
? ? return tornado.web.Application([
? ? ? ? (r"/", MainHandler),
? ? ? ? (r"/celery-block", CeleryHandler),
? ? ], autoreload=True)
if __name__ == "__main__":
? ? app = make_app()
? ? app.listen(8000)
? ? tornado.ioloop.IOLoop.current().start()
import os
import time
from celery import Celery
from tornado import gen
celery = Celery("tasks", broker="amqp://")
celery.conf.CELERY_RESULT_BACKEND = os.environ.get('CELERY_RESULT_BACKEND', 'amqp')
@celery.task
def sleep(seconds):
? ? time.sleep(float(seconds))
? ? return seconds
if __name__ == "__main__":
? ? celery.start()
Celery 的 Worker 運行在另一個進程中许昨,獨立于 Tornado 進程懂盐,不會影響 Tornado 運行效率,在處理復雜任務時候比進程模式更有效率糕档。
方法優(yōu)點缺點可用性
gen.coroutine簡單莉恼、優(yōu)雅需要異步庫支持★★☆☆☆
線程簡單可能會影響性能★★★☆☆
Celery性能好操作復雜、版本低★★★☆☆
目前沒有找到最佳的異步非阻塞的編程模式速那,可用的異步庫比較局限俐银,只有經常用的,個人編寫異步庫比較困難端仰。
推薦使用線程和 Celery 的模式進行異步編程捶惜,輕量級的放在線程中執(zhí)行,復雜的放在 Celery 中執(zhí)行荔烧。當然如果有異步庫使用那最好不過了吱七。
Python 3 中可以把 Tornado 設置為 asyncio 的模式,這樣就使用 兼容 asyncio 模式的庫茴晋,這應該是日后的方向陪捷。
http://www.tornadoweb.org/en/stable/
https://github.com/mher/tornado-celery
https://github.com/tornadoweb/tornado/wiki/Links
轉自:https://hexiangyu.me/2017/01/29/real-tornado-async-noblocking/