摘要:最大的特點就是其支持異步,所以它有著優異的性能。的代碼結構可以在其官網了解,本文著重分析的實現。事件驅動模型的大致思路的方法用于啟動事件循環。行文比較草率,如有錯誤和不足之處,敬請指正。
0. 簡介
tornado是一個用Python語言寫成的Web服務器兼Web應用框架,由FriendFeed公司在自己的網站FriendFeed中使用,被Facebook收購以后框架以開源軟件形式開放給大眾。
tornado最大的特點就是其支持異步IO,所以它有著優異的性能。下表是和一些其他Web框架與服務器的對比:(處理器為 AMD Opteron, 主頻2.4GHz, 4核) (來源wikipedia)
服務 | 部署 | 請求/每秒 |
---|---|---|
Tornado | nginx, 4進程 | 8213 |
Tornado | 1個單線程進程 | 3353 |
Django | Apache/mod_wsgi | 2223 |
web.py | Apache/mod_wsgi | 2066 |
CherryPy | 獨立 | 785 |
先來看看hello world的例子。^_^
import tornado.httpserver import tornado.ioloop import tornado.options import tornado.web from tornado.options import define, options define("port", default=8888, help="run on the given port", type=int) class MainHandler(tornado.web.RequestHandler): def get(self): self.write("Hello, world") def main(): tornado.options.parse_command_line() application = tornado.web.Application([ (r"/", MainHandler), ]) http_server = tornado.httpserver.HTTPServer(application) http_server.listen(options.port) tornado.ioloop.IOLoop.current().start() if __name__ == "__main__": main()
運行:
$ python3 helloworld.py
我們就得到一個web server監聽在8888端口。用curl命令get一下,就返回了"Hello, world"。
tornado的代碼結構可以在其官網了解,本文著重分析IOLoop的實現。
1. IOLoop 1.1 http交互的大致過程介紹IOLoop之前我們先看看http server和http client交互的一個大致過程。
server端監聽在某個端口,client端發送請求過來,server處理后返回,然后繼續等待下一個請求,周而復始。如果用socket那一坨來描述的話就是:
1. server.py ================================================================ s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.bind(address) s.listen(backlog) While True: connection = s.accept() do_something() connection.send() connection.close() 2. client.py ================================================================= s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect() s.send() s.recv() s.close()1.2 聊聊阻塞與非阻塞
所謂阻塞,就是進程正在等待某些資源(如IO),而處于等待運行的狀態(不占用CPU資源)。比如connect(("google.com", 80))返回之前進程都是阻塞的,在它下面的語句得不到執行,除非connect返回。
很顯然阻塞式的IO模型有個缺點就是并發量不大,試想如果server進程在do_something()處阻塞,而這時另外有個客戶端試圖連進來,則可能得不到響應。
提高并發量有幾種實現方式:多線程(一個連接fork一個線程去處理);多進程(一個連接fork一個子進程去處理)(apache);事件驅動(nginx, epoll)等。tornado就是基于epoll(Linux)事件驅動模型實現的。
當然它們有各自的優缺點,此文不詳述,有興趣的讀者可以自行google之。^_^
關于IO模型,epoll, 同步,異步,阻塞,非阻塞的概念,可以參考這兩篇文章:
https://segmentfault.com/a/11...
http://blog.csdn.net/historya...
1.3 IOLoop實現 1.3.1 IOLoop配置前文說到tornado是基于epoll事件驅動模型,也不完全正確,tornado實際上是根據平臺選擇底層驅動。請看IOLoop類的configurable_default方法:
@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
這里的IOLoop實際上是個通用接口,根據不同平臺選擇:linux->epoll,BSD->kqueue,如果epoll和kqueue都不支持則選擇select(性能要差些)。
class IOLoop(Configurable):IOLoop繼承了Configurable類,Configurable類的__new__方法調用了configured_class方法:
def __new__(cls, *args, **kwargs): base = cls.configurable_base() init_kwargs = {} if cls is base: impl = cls.configured_class() if base.__impl_kwargs: init_kwargs.update(base.__impl_kwargs) else: impl = cls init_kwargs.update(kwargs) instance = super(Configurable, cls).__new__(impl) # initialize vs __init__ chosen for compatibility with AsyncHTTPClient # singleton magic. If we get rid of that we can switch to __init__ # here too. instance.initialize(*args, **init_kwargs) return instance
configured_class方法又調用了configurable_default方法:
@classmethod def configured_class(cls): # type: () -> type """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
所以當初始化一個IOLoop實例的時候就給IOLoop做了配置,根據不同平臺選擇合適的驅動。
1.3.2 IOLoop實例化下面我們來看IOLoop的實例化函數:
# Global lock for creating global IOLoop instance _instance_lock = threading.Lock() @staticmethod def instance(): if not hasattr(IOLoop, "_instance"): with IOLoop._instance_lock: if not hasattr(IOLoop, "_instance"): # New instance after double check IOLoop._instance = IOLoop() return IOLoop._instance
很顯然,這里是實現了一個全局的單例模式。確保多個線程也只有一個IOLoop實例。(思考一下:為什要double check?if not hasattr(IOLoop, "_instance") ^_^)
1.3.3 實現epoll的接口(假設是在Linux平臺)我們知道epoll支持3種操作:
EPOLL_CTL_ADD 添加一個新的epoll事件 EPOLL_CTL_DEL 刪除一個epoll事件 EPOLL_CTL_MOD 改變一個事件的監聽方式
分別對應tornado.IOLoop里面的三個函數:add_handler, remove_handler, update_handler
下面看看這三個函數:
def add_handler(self, fd, handler, events): fd, obj = self.split_fd(fd) self._handlers[fd] = (obj, stack_context.wrap(handler)) self._impl.register(fd, events | self.ERROR) def update_handler(self, fd, events): fd, obj = self.split_fd(fd) self._impl.modify(fd, events | self.ERROR) def remove_handler(self, fd): fd, obj = self.split_fd(fd) self._handlers.pop(fd, None) self._events.pop(fd, None) try: self._impl.unregister(fd) except Exception: gen_log.debug("Error deleting fd from IOLoop", exc_info=True)
這里的self._impl就是select.epoll(),使用方法可以參考epoll接口。
1.3.4 事件驅動模型的大致思路IOLoop的start()方法用于啟動事件循環(Event Loop)。
(部分源碼) while self._events: fd, events = self._events.popitem() try: fd_obj, handler_func = self._handlers[fd] handler_func(fd_obj, events) except (OSError, IOError) as e: if errno_from_exception(e) == errno.EPIPE: # Happens when the client closes the connection pass else: self.handle_callback_exception(self._handlers.get(fd)) except Exception: self.handle_callback_exception(self._handlers.get(fd))
大致的思路是:有連接進來(client端請求),就丟給epoll,順便注冊一個事件和一個回調函數,我們主線程還是繼續監聽請求;然后在事件循環中,如果發生了某種事件(如socket可讀,或可寫),則調用之前注冊的回調函數去處理。這和Node.js的思路是一致的。
1.3.5 關于cpu bound任務tornado很適合處理IO bound的任務,如果遇到cpu bound的任務,則還是會阻塞整個進程。這個時候就必須將耗時的任務丟到另一個worker,或者隊列中去處理(如celery)。
1.3.6 其他IOLoop類還有其他一些方法,多為輔助函數,讀者可以自行參考,此處不詳述。
行文比較草率,如有錯誤和不足之處,敬請指正。
下次繼續分析tornado其他模塊。^_^
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/38118.html
摘要:序言最近閑暇無事閱讀了一下的源碼對整體的結構有了初步認識與大家分享不知道為什么右邊的目錄一直出不來非常不舒服不如移步到吧是的核心模塊也是個調度模塊各種異步事件都是由他調度的所以必須弄清他的執行邏輯源碼分析而的核心部分則是這個循環內部的邏輯貼 序言 最近閑暇無事,閱讀了一下tornado的源碼,對整體的結構有了初步認識,與大家分享 不知道為什么右邊的目錄一直出不來,非常不舒服. 不如移...
摘要:源碼之分析的協程原理分析版本為支持異步,實現了一個協程庫。提供了回調函數注冊當異步事件完成后,調用注冊的回調中間結果保存結束結果返回等功能注冊回調函數,當被解決時,改回調函數被調用。相當于喚醒已經處于狀態的父協程,通過回調函數,再執行。 tornado 源碼之 coroutine 分析 tornado 的協程原理分析 版本:4.3.0 為支持異步,tornado 實現了一個協程庫。 ...
摘要:前言本文將嘗試詳細的帶大家一步步走完一個異步操作從而了解是如何實現異步的其實本文是對上一篇文的實踐和復習主旨在于關注異步的實現所以會忽略掉代碼中的一些異常處理文字較多湊合下吧接下來只會貼出部分源碼幫助理解希望有耐心的同學打開源碼一起跟蹤一遍 前言 本文將嘗試詳細的帶大家一步步走完一個異步操作,從而了解tornado是如何實現異步io的. 其實本文是對[上一篇文][1]的實踐和復習 主...
摘要:上一篇文章第二章實戰演練開發網站第六節異步與協程化下一篇文章第二章實戰演練開發網站第八節用戶身份認證是很多網站為了辨別用戶的身份而存儲在用戶本地終端的數據,在中使用可以方便地對進行讀寫。 上一篇文章:Python:Tornado 第二章:實戰演練:開發Tornado網站:第六節:異步與協程化下一篇文章:Python:Tornado 第二章:實戰演練:開發Tornado網站:第八節:用戶...
摘要:上一篇文章第一章異步及協程基礎第三節協程下一篇文章第二章實戰演練開發網站第二節網站結構路由解析實例瀏覽器輸入鏈接頁面顯示下面逐行解析上面的代碼做了些什么首先通過語句引入包中的和類。該對象的第一個餐食用于定義程序的路由映射。 上一篇文章:Python:Tornado 第一章:異步及協程基礎:第三節:協程下一篇文章:Python:Tornado 第二章:實戰演練:開發Tornado網站:第...
閱讀 1654·2019-08-30 13:04
閱讀 2205·2019-08-30 12:59
閱讀 1764·2019-08-29 18:34
閱讀 1857·2019-08-29 17:31
閱讀 1255·2019-08-29 15:42
閱讀 3530·2019-08-29 15:37
閱讀 2857·2019-08-29 13:45
閱讀 2771·2019-08-26 13:57