真正的 Tornado 異步非阻塞

其中Tornado的定義是 Web 框架和異步網絡庫翅帜,其中他具備有異步非阻塞能力怜浅,能解決他兩個框架請求阻塞的問題,在需要并發(fā)能力時候就應該使用Tornado行楞。

但是在實際使用過程中很容易把Tornado使用成異步阻塞框架攒暇,這樣對比其他兩大框架沒有任何優(yōu)勢而言,本文就如何實現真正的異步非阻塞記錄子房。

以下使用的 Python 版本為 2.7.13

平臺為 Macbook Pro 2016

使用 gen.coroutine 異步編程

在 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 的異步編程

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 模式的庫茴晋,這應該是日后的方向陪捷。

Reference

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/

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市诺擅,隨后出現的幾起案子市袖,更是在濱河造成了極大的恐慌,老刑警劉巖烁涌,帶你破解...
    沈念sama閱讀 221,695評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件苍碟,死亡現場離奇詭異,居然都是意外死亡撮执,警方通過查閱死者的電腦和手機微峰,發(fā)現死者居然都...
    沈念sama閱讀 94,569評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來抒钱,“玉大人蜓肆,你說我怎么就攤上這事∧北遥” “怎么了仗扬?”我有些...
    開封第一講書人閱讀 168,130評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長蕾额。 經常有香客問我早芭,道長,這世上最難降的妖魔是什么诅蝶? 我笑而不...
    開封第一講書人閱讀 59,648評論 1 297
  • 正文 為了忘掉前任退个,我火速辦了婚禮募壕,結果婚禮上,老公的妹妹穿的比我還像新娘语盈。我一直安慰自己舱馅,他們只是感情好,可當我...
    茶點故事閱讀 68,655評論 6 397
  • 文/花漫 我一把揭開白布黎烈。 她就那樣靜靜地躺著习柠,像睡著了一般。 火紅的嫁衣襯著肌膚如雪照棋。 梳的紋絲不亂的頭發(fā)上资溃,一...
    開封第一講書人閱讀 52,268評論 1 309
  • 那天,我揣著相機與錄音烈炭,去河邊找鬼溶锭。 笑死,一個胖子當著我的面吹牛符隙,可吹牛的內容都是我干的趴捅。 我是一名探鬼主播,決...
    沈念sama閱讀 40,835評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼霹疫,長吁一口氣:“原來是場噩夢啊……” “哼拱绑!你這毒婦竟也來了?” 一聲冷哼從身側響起丽蝎,我...
    開封第一講書人閱讀 39,740評論 0 276
  • 序言:老撾萬榮一對情侶失蹤猎拨,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后屠阻,有當地人在樹林里發(fā)現了一具尸體红省,經...
    沈念sama閱讀 46,286評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,375評論 3 340
  • 正文 我和宋清朗相戀三年国觉,在試婚紗的時候發(fā)現自己被綠了吧恃。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,505評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡麻诀,死狀恐怖痕寓,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情蝇闭,我是刑警寧澤呻率,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站丁眼,受9級特大地震影響筷凤,放射性物質發(fā)生泄漏昭殉。R本人自食惡果不足惜苞七,卻給世界環(huán)境...
    茶點故事閱讀 41,873評論 3 333
  • 文/蒙蒙 一藐守、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蹂风,春花似錦卢厂、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,357評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至撵渡,卻和暖如春融柬,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背趋距。 一陣腳步聲響...
    開封第一講書人閱讀 33,466評論 1 272
  • 我被黑心中介騙來泰國打工粒氧, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人节腐。 一個月前我還...
    沈念sama閱讀 48,921評論 3 376
  • 正文 我出身青樓外盯,卻偏偏與公主長得像,于是被迫代替她去往敵國和親翼雀。 傳聞我的和親對象是個殘疾皇子饱苟,可洞房花燭夜當晚...
    茶點故事閱讀 45,515評論 2 359

推薦閱讀更多精彩內容