Python開發【Tornado】:異步Web服務(二)


真正的 Tornado 異步非阻塞

前言:

  其中 Tornado 的定義是 Web 框架和異步網絡庫,其中他具備有異步非阻塞能力,能解決他兩個框架請求阻塞的問題,在需要並發能力時候就應該使用 Tornado。

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

筆記

  • 默認情況下tornado是單線程阻塞模式,如果阻塞所有請求都需要等待
  • tornado.web.asynchronous可以異步使用,得益於AsyncHTTPClient模塊的配合使用,兩者缺一不可
  • tornado.gen.coroutine嚴重依賴第三方庫的使用,如果沒有第三方庫的支持則依然是阻塞模式
  • Tornado 提供了多種的異步編寫形式:回調、Future、協程等,其中以協程模式最是簡單和用的最多
  • Tornado 實現異步的多種方式:coroutine配合第三方庫、啟用多線程、使用celery等

 

1、使用 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 狀態

所以正確是使用方式是使用了 asynchronous 需要手動 finish

class MainHandler(tornado.web.RequestHandler):
    @tornado.web.asynchronous
    def get(self):
        self.write("Hello, world")
        self.finish()

coroutine 裝飾器是指定改請求為協程模式,說明白點就是能使用 yield 配合 Tornado 編寫異步程序。

Tronado 為協程實現了一套自己的協議,不能使用 Python 普通的生成器

在使用協程模式編程之前要知道如何編寫 Tornado 中的異步函數,Tornado 提供了多種的異步編寫形式:回調、Future、協程等,其中以協程模式最是簡單和用的最多。

編寫一個基於協程的異步函數同樣需要 coroutine 裝飾器

@gen.coroutine
def sleep(self):
    yield gen.sleep(10)
    raise gen.Return([1, 2, 3, 4, 5])

 這就是一個異步函數,Tornado 的協程異步函數有兩個特點:

  • 需要使用 coroutine 裝飾器
  • 返回值需要使用 raise gen.Return() 當做異常拋出

返回值作為異常拋出是因為在 Python 3.2 之前生成器是不允許有返回值的。

使用過 Python 生成器應該知道,想要啟動生成器的話必須手動執行 next() 方法才行,所以這里的 coroutine 裝飾器的其中一個作用就是在調用這個異步函數時候自動執行生成器。

使用 coroutine 方式有個很明顯是缺點就是嚴重依賴第三方庫的實現,如果庫本身不支持 Tornado 的異步操作再怎么使用協程也是白搭依然會是阻塞的,放個例子感受一下

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(2)
        self.write('Blocking Request')


class BlockingHnadler(tornado.web.RequestHandler):
    @gen.coroutine
    def get(self):
        time.sleep(2)
        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(8001)
    tornado.ioloop.IOLoop.current().start()

當我們使用 yield gen.sleep(10) 這個異步的 sleep 時候,請求是不阻塞的。  

[root@localhost ~]# siege http://192.168.0.2:8001/noblock -c100 -t10s
** SIEGE 3.0.8
** Preparing 100 concurrent users for battle.
The server is now under siege...
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.83 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.85 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.85 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.85 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.85 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.85 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.85 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.85 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.85 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.85 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.85 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.85 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.85 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.85 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.85 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.85 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.85 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.85 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.85 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.85 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.85 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.85 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.85 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.85 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.85 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.85 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.85 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.85 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.85 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.85 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.85 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.85 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.86 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.86 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.86 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.86 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.86 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.86 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.86 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.86 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.86 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.86 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.86 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.86 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.86 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.86 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.86 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.86 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.86 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.86 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.86 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.86 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.87 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.86 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.86 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.86 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.87 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.87 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.86 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.86 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.87 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.86 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.86 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.87 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.86 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.86 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.87 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.86 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.86 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.86 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.85 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.85 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.85 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.85 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.85 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.83 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.83 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.83 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.85 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.86 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.86 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.85 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.85 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.85 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.85 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.85 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.85 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.86 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.87 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.87 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.87 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.88 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.87 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.87 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.86 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.86 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.86 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.86 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.87 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.87 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.87 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.87 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.87 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.87 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.87 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.87 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.87 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.86 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.86 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.86 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.87 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.87 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.87 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.87 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.87 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.87 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.87 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.86 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.86 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.83 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.85 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.85 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.85 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.83 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.83 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.83 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.83 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.83 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.83 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.83 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.83 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.83 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.83 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.83 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.83 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.83 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.83 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.83 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.85 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.85 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.85 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.85 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.85 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.87 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.85 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.85 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.85 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.85 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.83 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.83 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.83 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.83 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.83 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.83 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.85 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.85 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.85 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.85 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.85 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.85 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.85 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.85 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.88 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.88 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.85 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.87 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.87 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.85 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.85 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.85 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.85 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.85 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.86 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.83 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.83 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.83 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.83 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.83 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.83 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.83 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.83 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.83 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.83 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.83 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.83 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.83 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.83 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.83 secs:      16 bytes ==> GET  /noblock
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /noblock

Lifting the server siege...      done.

Transactions:                 337 hits
Availability:              100.00 %
Elapsed time:                9.35 secs
Data transferred:            0.01 MB
Response time:                1.85 secs
Transaction rate:           36.04 trans/sec
Throughput:                0.00 MB/sec
Concurrency:               66.55
Successful transactions:         337
Failed transactions:               0
Longest transaction:            1.88
Shortest transaction:            1.83
 
FILE: /root/siege.log
You can disable this annoying message by editing
the .siegerc file in your home directory; change
the directive 'show-logfile' to false.
基准測試輸出

測試結果:100並發,請求10s,共發送337次請求,平均每秒處理36.04次請求,很明顯程序是非阻塞的

當使用 time.sleep(10) 時候請求時阻塞的。

[root@localhost ~]# siege http://192.168.0.2:8001/block -c100 -t10s
** SIEGE 3.0.8
** Preparing 100 concurrent users for battle.
The server is now under siege...
HTTP/1.1 200   1.84 secs:      16 bytes ==> GET  /block
HTTP/1.1 200   3.68 secs:      16 bytes ==> GET  /block
HTTP/1.1 200   5.51 secs:      16 bytes ==> GET  /block
HTTP/1.1 200   7.34 secs:      16 bytes ==> GET  /block
HTTP/1.1 200   9.17 secs:      16 bytes ==> GET  /block

Lifting the server siege...      done.

Transactions:                   5 hits
Availability:              100.00 %
Elapsed time:                9.23 secs
Data transferred:            0.00 MB
Response time:                5.51 secs
Transaction rate:            0.54 trans/sec
Throughput:                0.00 MB/sec
Concurrency:                2.98
Successful transactions:           5
Failed transactions:               0
Longest transaction:            9.17
Shortest transaction:            0.00
 
FILE: /root/siege.log
You can disable this annoying message by editing
the .siegerc file in your home directory; change
the directive 'show-logfile' to false.
基准測試輸出

測試結果:100並發,請求10s,共發送5次請求,平均每秒處理0.5次請求,和time.sleep 2秒鍾預先的效果是一致的。

gen.coroutine 在 Tornado 3.1 后會自動調用 self.finish() 結束請求,可以不使用 asynchronous 裝飾器。

所以這種實現異步非阻塞的方式需要依賴大量的基於 Tornado 協議的異步庫,使用上比較局限,好在還是有一些可以用的異步庫

 

2、啟用線程的處理異步編程

注:使用 gen.coroutine 裝飾器編寫異步函數,如果庫本身不支持異步,那么響應任然是阻塞的。

 在 Tornado 中有個裝飾器能使用 ThreadPoolExecutor 來讓阻塞過程編程非阻塞,其原理是在 Tornado 本身這個線程之外另外啟動一個線程來執行阻塞的程序,從而讓 Tornado 變得非阻塞,根本原理就是啟用多個線程處理,處理后的線程並不會自動關閉

import time
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)              # 啟動4個線程處理阻塞請求

    def sleep(self, second):
        time.sleep(second)
        return second

    @run_on_executor
    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()

hreadPoolExecutor 是對標准庫中的 threading 的高度封裝,利用線程的方式讓阻塞函數異步化,解決了很多庫是不支持異步的問題。

但是與之而來的問題是,如果大量使用線程化的異步函數做一些高負載的活動,會導致該 Tornado 進程性能低下響應緩慢,這只是從一個問題到了另一個問題而已。

所以在處理一些小負載的工作,是能起到很好的效果,讓 Tornado 異步非阻塞的跑起來。

但是明明知道這個函數中做的是高負載的工作,那么你應該采用另一種方式,使用 Tornado 結合 Celery 來實現異步非阻塞。

[root@localhost ~]# siege http://192.168.0.2:8001/noblock -c10 -t10s
** SIEGE 3.0.8
** Preparing 10 concurrent users for battle.
The server is now under siege...
HTTP/1.1 200   4.59 secs:      21 bytes ==> GET  /noblock
HTTP/1.1 200   4.59 secs:      21 bytes ==> GET  /noblock
HTTP/1.1 200   4.60 secs:      21 bytes ==> GET  /noblock
HTTP/1.1 200   4.60 secs:      21 bytes ==> GET  /noblock
HTTP/1.1 200   9.18 secs:      21 bytes ==> GET  /noblock
HTTP/1.1 200   8.18 secs:      21 bytes ==> GET  /noblock
HTTP/1.1 200   8.18 secs:      21 bytes ==> GET  /noblock
HTTP/1.1 200   8.18 secs:      21 bytes ==> GET  /noblock

Lifting the server siege...      done.

Transactions:                   8 hits
Availability:              100.00 %
Elapsed time:                9.42 secs
Data transferred:            0.00 MB
Response time:                6.51 secs
Transaction rate:            0.85 trans/sec
Throughput:                0.00 MB/sec
Concurrency:                5.53
Successful transactions:           8
Failed transactions:               0
Longest transaction:            9.18
Shortest transaction:            0.00
 
FILE: /root/siege.log
You can disable this annoying message by editing
the .siegerc file in your home directory; change
the directive 'show-logfile' to false.
基准測試輸出

 

3、基於 Celery 的異步編程

Celery 是一個簡單、靈活且可靠的,處理大量消息的分布式系統,專注於實時處理的任務隊列,同時也支持任務調度。

Celery 並不是唯一選擇,你可選擇其他的任務隊列來實現,但是 Celery 是 Python 所編寫,能很快的上手,同時 Celery 提供了優雅的接口,易於與 Python Web 框架集成等特點。

與 Tornado 的配合可以使用 tornado-celery ,該包已經把 Celery 封裝到 Tornado 中,可以直接使用(本節暫未測試,文章轉載

實際測試中,由於 tornado-celery 很久沒有更新,導致請求會一直阻塞,不會返回

解決辦法是:

把 celery 降級到 3.1 pip install celery==3.1
把 pika 降級到 0.9.14 pip install pika==0.9.14

Tornado服務端:

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()

celery調度端:

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 運行效率,在處理復雜任務時候比進程模式更有效率。

 

總結

推薦使用線程和 Celery 的模式進行異步編程,輕量級的放在線程中執行,復雜的放在 Celery 中執行。當然如果有異步庫使用那最好不過了。目前沒有找到最佳的異步非阻塞的編程模式,可用的異步庫比較局限,只有經常用的,個人編寫異步庫比較困難。

Python 3 中可以把 Tornado 設置為 asyncio 的模式,這樣就使用 兼容 asyncio 模式的庫,這應該是日后的方向。

Reference

 

Tornado異步一》------跳轉


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM