2015年8月23日

Tornado 教學 (9) - 使用非同步處理機制 ( Use Coroutine with Tornado Web Framework )

前面關於 Tornado 的文章主要介紹 Web Framework 的基礎。接下來,本篇將深入 Tornado 的特點:非同步處理機制。( 其他 Tornado 相關教學可以參考本篇整理 )



暸解非同步機制:
Tornado 受歡迎的原因,在於它的設計著眼於處理 C10K 問題。舉例來說,當 Server 端接收到查詢資料庫的 Request,有許多時間是在等待資料庫的查詢結果而呈現 Blocking 的狀態。若我們可以利用這些等待的時間來處理其他的 Request,就可以增加整體的效能。所以同樣的 Request,在使用 Tornado 的非同步機制處理下,系統不會苦苦等到資料庫回應才繼續工作,而是將先前的請求暫存起來,先服務其他的 Request,等到資料庫查詢回應後繼續處理剛剛未完成的工作。


實作 Asynchronous :
Tornado 歷經多次重大更新,初期版本中實作非同步需要撰寫 callback 來達成目的,而這樣的方式也讓人詬病。因為你如果需要比較複雜的邏輯處理,可能需要多次的 callback 也造成閱讀、理解上的困難度。因此 Tornado 開發團隊提出了方便的 @gen.coroutine Decorator,接下來就直接使用它來示範,就不再示範前面版本的寫法。

開始實作,假設我們今天提供一個 RESTful 服務,內容包含擷取解析其他網站資訊,這時候我們就可以使用非同步機制來提升效能,程式內容參考如下:
#!/usr/bin/env python
import os
import tornado.web
import tornado.gen
import tornado.ioloop
import tornado.options
import tornado.httpclient

tornado.options.define("ip", default='1.1.1.x', help="message...")
tornado.options.define("port", default=8888, help="message...")

url='http://....'

class Async(tornado.web.RequestHandler):
    # 加入 Coroutine,宣告此函式為 Asynchronous
    # 使用時必須搭配 yield,Python3.5 可以使用 async 與 await
    @tornado.gen.coroutine
    def get(self):
        client = tornado.httpclient.AsyncHTTPClient()
        response = yield client.fetch(url)
        # 加入處理邏輯 ...
        self.write("...")

settings= {
    "debug": True,
    "autoreload": True
}

def main():
    tornado.options.parse_command_line()
    app = tornado.web.Application(
            [
                (r'/async', Async)
            ], **settings
        )
    app.listen(tornado.options.options.port, tornado.options.options.ip)
    tornado.ioloop.IOLoop.current().start()

if __name__ == "__main__":
    main()
你可以分別測試一般的與非同步的處理速度與平均回應時間,我是使用 Siege 來測試,使用非同步的機制效能的確提升很多,同樣的時間內可以處理更多 Request。若你還想要再提升效能可以使用 tornado.curl_httpclient.CurlAsyncHTTPClient( ),官方文件中有提到 CurlAsyncHTTPClient 會更快,不過需要另外安裝其他套件:pycurl,我實際測試的結果顯示效能的確比 AsyncHTTPClient 更好。


Environment :
  ・ Arch Linux
  ・ Python 2.7

Reference :
  ・ Tornado