tornado

參考:http://www.nowamagic.net/academy/detail/13321002html

 

Tornado就是咱們在FriendFeed的Web服務器及其經常使用工具的開源版本。Tornado和如今的主流Web服務器框架(包括大多數Python的框架)有着明顯的區別:它是非阻塞式服務器,並且速度至關快。它還實現了一個一樣簡潔的模板框架。Tornado在本身的異步網絡訪問框架之上完整地實現了HTTP服務器,這也是它的核心框架的重要部分。python

我以爲對非阻塞IO (nonblocking IO)或異步IO (asynchronous IO, AIO)頗有必要談一談。若是你已經徹底知道他們是什麼了,能夠跳去看下一節。我儘量的使用一些例子來講明它們是什麼。ios

讓咱們假設你正在寫一個須要請求一些來自其餘服務器上的數據(好比數據庫服務,再好比新浪微博的open api)的應用程序,而後呢這些請求將花費一個比較長的時間,假設須要花費5秒鐘。大多數的web開發框架中處理請求的代碼大概長這樣:web

def handler_request(self, request): answ = self.remote_server.query(request) # this takes 5 seconds request.write_response(answ) 

若是這些代碼運行在單個線程中,你的服務器只能每5秒接收一個客戶端的請求。在這5秒鐘的時間裏,服務器不能幹其餘任何事情,因此,你的服務效率是每秒0.2個請求,哦,這太糟糕了。數據庫

固然,沒人那麼天真,大部分服務器會使用多線程技術來讓服務器一次接收多個客戶端的請求,咱們假設你有20個線程,你將在性能上得到20倍的提升,因此如今你的服務器效率是每秒接受4個請求,但這仍是過低了,固然,你能夠經過不斷地提升線程的數量來解決這個問題,可是,線程在內存和調度方面的開銷是昂貴的,我懷疑若是你使用這種提升線程數量的方式將永遠不可能達到每秒100個請求的效率。設計模式

若是使用AIO,達到每秒上千個請求的效率是很是輕鬆的事情。服務器請求處理的代碼將被改爲這樣:api

def handler_request(self, request): self.remote_server.query_async(request, self.response_received) def response_received(self, request, answ): # this is called 5 seconds later request.write(answ) 

AIO的思想是當咱們在等待結果的時候不阻塞,轉而咱們給框架一個回調函數做爲參數,讓框架在有結果的時候經過回調函數通知咱們。這樣,服務器就能夠被解放去接受其餘客戶端的請求了。瀏覽器

然而這也是AIO不太好的地方:代碼有點不直觀了。還有,若是你使用像Tornado這樣的單線程AIO服務器軟件,你須要時刻當心不要去阻塞什麼,由於全部本該在當前返回的請求都會像上述處理那樣被延遲返回。服務器

關於異步IO,比當前這篇過度簡單的介紹更好的學習資料請看 The C10K problemwebsocket

Tornado設計模型概覽

在深刻到模塊進行分析以前,首先來看看Tornado的設計模型。

/media/note/2013/02/09/tornado-core/fig1.png

Tornado框架設計模型

Tornado不只僅是一個WEB框架,它還完整地實現了HTTP服務器和客戶端,在此基礎上提供WEB服務。它能夠分爲四層:最底層的EVENT層處理IO事件;TCP層實現了TCP服務器,負責數據傳輸;HTTP/HTTPS層基於HTTP協議實現了HTTP服務器和客戶端;最上層爲WEB框架,包含了處理器、模板、數據庫鏈接、認證、本地化等等WEB框架須要具有的功能。

主要模塊

  • 核心Web框架
    • tornado.web - RequestHandlerApplication
    • tornado.httpserver - 非阻塞式HTTP服務器
    • tornado.template - 靈活的輸出生成器
    • tornado.escape - 轉義和字符串處理
    • tornado.locale - 國際化支持
  • 異步網絡
    • tornado.ioloop - 核心的事件循環
    • tornado.iostream - 對非阻塞式的socket的簡單封裝,以方便經常使用讀寫操做
    • tornado.httpclient - 非阻塞式HTTP客戶端
    • tornado.netutil - 各類網絡工具
  • 和其餘服務的集成
    • tornado.auth - OpenID和OAuth的第三方認證登陸
    • tornado.database - 簡單的MySQL客戶端封裝
    • tornado.platform.twisted - Tornado上的Twisted運行代碼
    • tornado.websocket - 和瀏覽器的雙向通訊
    • tornado.wsgi - 和其餘Python框架和服務器的協做
  • 工具
    • tornado.autoreload - 在開發中自動檢測代碼更改
    • tornado.gen - 簡單的異步代碼
    • tornado.httputil - 處理HTTP頭和URL
    • tornado.options - 命令行解析
    • tornado.process - 多進程工具
    • tornado.stack_context - 異步回調中的異常處理
    • tornado.testing - 異步代碼的單元測試支持

爲了方便,約定$root指帶tornado的根目錄。總的來講,要用 Tornado 完成一個網站的構建,其實主要須要如下幾個文件:

  • $root/tornado/web.py
  • $root/tornado/httpserver.py
  • $root/tornado/tcpserver.py
  • $root/tornado/ioloop.py
  • $root/tornado/iostream.py
  • $root/tornado/platfrom/epoll.py
  • $root/app.py

另外可能還須要一些功能庫的支持而須要引入的文件就不列舉了,好比util和httputil之類的。來看看每一個文件的做用。

  1. app.py 是本身寫的,內容就如 tornado 的 readme 文檔裏給的示例同樣,定義路由規則和 handler,而後建立 application,發起 server 監聽,服務器就算跑起來了。
  2. 緊接着就是 web.py。其中定義了 Application 和 RequestHandler 類,在 app.py 裏直接就用到了。Application 是個單例,總攬全局路由,建立服務器負責監聽,並把服務器傳回來的請求進行轉發(__call__)。RequestHandler 是個功能很豐富的類,基本上 web 開發須要的它都具有了,好比redirect,flush,close,header,cookie,render(模板),xsrf,etag等等
  3. 從 web 跟蹤到 httpserver.py 和 tcpserver.py。這兩個文件主要是實現 http 協議,解析 header 和 body, 生成request,回調給 appliaction,一個經典意義上的 http 服務器(written in python)。衆所周知,這是個很考究性能的一塊(IO),因此它和其它不少塊都鏈接到了一塊兒,好比 IOLoop,IOStream,HTTPConnection 等等。這裏 HTTPConnection 是實現了 http 協議的部分,它關注 Connection 嘛,這是 http 纔有的。至於監聽端口,IO事件,讀寫緩衝區,創建鏈接之類都是在它的下層--tcp裏須要考慮的,因此,tcpserver 纔是和它們打交道的地方,到時候分析起來估計很麻煩
  4. 先說這個IOStream。顧名思義,就是負責IO的。說到IO,就得提緩衝區和IO事件。緩衝區的處理都在它自個兒類裏,IO事件的異步處理就要靠 IOLoop 了。
  5. 而後是 IOLoop。若是你用過 select/poll/epoll/libevent 的話,對它的處理模型應該至關熟悉。簡言之,就是一個大大的循環,循環裏等待事件,而後處理事件。這是開發高性能服務器的常見模型,tornado 的異步能力就是在這個類裏獲得保證的
  6. 最後是 epoll.py。其實這個文件也沒幹啥,就是聲明瞭一下服務器使用 epoll。選擇 select/poll/epoll/kqueue 其中的一種做爲事件分發模型,是在 tornado 裏自動根據操做系統的類型而作的選擇,因此這幾種接口是同樣的(固然效率不同),出於簡化,直接就epoll吧^_^
  7. PS。若是你是一個細節控,可能會注意到 tornado 裏的回調 callback 函數都不是直接使用的,而是使用 stack_context.wrap 進行了封裝。但據我觀察,封裝先後沒多大差異(指邏輯流程),函數的參數也不變。但根據它代碼裏的註釋,這個封裝仍是至關有用的:

Use this whenever saving a callback to be executed later in a different execution context (either in a different thread or asynchronously in the same thread).

tornado網絡部分最核心的兩個模塊就是ioloop.py與iostream.py,咱們主要分析的就是這兩個部分。

  • ioloop.py 主要的是將底層的epoll或者說是其餘的IO多路複用封裝做異步事件來處理。
  • iostream.py主要是對於下層的異步事件的進一步封裝,爲其封裝了更上一層的buffer(IO)事件。

咱們先來看看 ioloop(文檔地址:http://www.tornadoweb.org/en/stable/ioloop.html):

We use epoll (Linux) or kqueue (BSD and Mac OS X) if they are available, or else we fall back on select(). If you are implementing a system that needs to handle thousands of simultaneous connections, you should use a system that supports either epoll or kqueue.

程序中主函數一般調用 tornado.ioloop.IOLoop.instance().start() 來啓動IOLoop,可是看了一下 IOLoop 的實現,start 方法是這樣的:

def start(self):
    """Starts the I/O loop.

    The loop will run until one of the callbacks calls `stop()`, which
    will make the loop stop after the current event iteration completes.
    """
    raise NotImplementedError()

也就是說 IOLoop 是個抽象的基類,具體工做是由它的子類負責的。若是是 Linux 平臺,因此應該用 Epoll,對應的類是 PollIOLoop。PollIOLoop 的 start 方法開始了事件循環。

問題來了,tornado.ioloop.IOLoop.instance() 是怎麼返回 PollIOLoop 實例的呢?剛開始有點想不明白,後來看了一下 IOLoop 的代碼就豁然開朗了。

class IOLoop(Configurable):

IOLoop 繼承自 Configurable,後者位於 tornado/util.py。

A configurable interface is an (abstract) class whose constructor acts as a factory function for one of its implementation subclasses. The implementation subclass as well as optional keyword arguments to its initializer can be set globally at runtime with configure.

Configurable 類實現了一個工廠方法,也就是設計模式中的「工廠模式」,看一下__new__函數的實現:

def __new__(cls, **kwargs):
    base = cls.configurable_base()
    args = {}
    if cls is base:
        impl = cls.configured_class()
        if base.__impl_kwargs:
            args.update(base.__impl_kwargs)
    else:
        impl = cls
    args.update(kwargs)
    instance = super(Configurable, cls).__new__(impl)
    # initialize vs __init__ chosen for compatiblity with AsyncHTTPClient
    # singleton magic.  If we get rid of that we can switch to __init__
    # here too.
    instance.initialize(**args)
    return instance

當建立一個Configurable類的實例的時候,其實建立的是configurable_class()返回的類的實例。

@classmethod
def configured_class(cls):
    """Returns the currently configured class."""
    base = cls.configurable_base()
    if cls.__impl_class is None:
        base.__impl_class = cls.configurable_default()
    return base.__impl_class

最後,就是返回的configurable_default()。此函數在IOLoop中的實現以下:

@classmethod
def configurable_default(cls):
    if hasattr(select, "epoll"):
        from tornado.platform.epoll import EPollIOLoop
        return EPollIOLoop
    if hasattr(select, "kqueue"):
        # Python 2.6+ on BSD or Mac
        from tornado.platform.kqueue import KQueueIOLoop
        return KQueueIOLoop
    from tornado.platform.select import SelectIOLoop
    return SelectIOLoop

EPollIOLoop 是 PollIOLoop 的子類。至此,這個流程就理清楚了。



未完,待續。。。。。。。。。。。。。。。。。。。。。。

相關文章
相關標籤/搜索