我心中的 tornado 最佳實踐

本文最先發表於我的博客 我心中的 tornado 最佳實踐css

最新開發新項目一直在學習tornado的知識,在前人的基礎上找了些最佳實踐,記錄以下,備查。html

tornado 新人一枚,歡迎大神拍磚~python

項目目錄結構

import tornado.ioloop
import tornado.web

## 業務處理層
class MainHandler(tornado.web.RequestHandler):
    def get(self):
        self.write("Hello, world")

## 系統入口app 及 路由層
def make_app():
    return tornado.web.Application([
        (r"/", MainHandler),
    ])

if __name__ == "__main__":
    app = make_app()
    app.listen(8888)
    tornado.ioloop.IOLoop.current().start()

上邊是tornado 官網的hello world的實例,tornado作爲web框架使用時,只須要處理邏輯的handler和系統入口application及路由便可啓動系統,只提供了框架
最核心的部分,使系統更加靈活。這樣咱們在開發的時候便擁有了自主選擇權,能夠選擇本身喜歡的模板語言,能夠選擇是否使用orm,根據本身的需求任意組裝。
這樣問題便來了,咱們只能憑藉咱們有限的開發經驗來組織咱們的項目結構,路由層、業務層、數據庫層等。有沒有一個tornado的項目結構的最佳實踐呢?
經同事介紹,我從github 上找到了這個項目tornado-boilerplate,雖然說6年沒有更新了,可是這個目錄結構對
我這個初學者足夠了。git

tornado-boilerplate/
    handlers/  # handler 處理邏輯
        foo.py
        base.py  # 在其中重寫 RequestHandler 的部分方法,或自定義方法完成本身的功能。
    lib/  # 其餘python的模塊 
    logconfig/  # 日誌相關配置
    media/  # 靜態文件
        css/
            vendor/
        js/
            vendor/
        images/
    requirements/  # 環境依賴
        common.txt
        dev.txt
        production.txt
    templates/  # 模板文件
    vendor/  # python的依賴包
    environment.py  # 修改python path 增長 lib vender等目錄的包
    fabfile.py  # 遠程部署文件
    app.py  # app 啓動文件
    settings.py  # 項目配置文件

sqlalchemy 和 tornado的結合

sqlalchemy 是python系用的最多的orm,咱們的項目也選用了sqlalchemy 。在結合sqlalchemy 和tornado過程當中,查閱了大量資料。
sqlalchemy 執行各類操做時,最基本的單元爲session。sqlalchemy 官方文檔建議,儘可能適用框架的第三方擴展包來集成sqlalchemy,能夠自動的管理session範圍。根據sqlalchemy 文檔,session的管理放在了每次的request請求中處理爲最佳,及每次請求進來時,實例化session,請求結束後,將session關閉,見這裏tornado的一個相關issuesgithub

結合以下:web

from sqlalchemy.orm import scoped_session, sessionmaker
from models import *  # import the engine to bind

class Application(tornado.web.Application):
def __init__(self):
    handlers = [
        (r"/users", UsersHandler),
    ]
    settings = dict(
        cookie_secret="some_long_secret_and_other_settins"
    )
    tornado.web.Application.__init__(self, handlers, **settings)
    # Have one global connection.
    self.session = scoped_session(sessionmaker(bind=engine))

class BaseHandler(tornado.web.RequestHandler):

    def prepare(self):
        self.session = self.application.session

    def get_current_user(self):
        user_id = self.get_secure_cookie("user")
        if not user_id: return None
        return self.db.query(User).get(user_id)
    
    def on_finish(self):
        self.session.remove()

此處的scoped_session, 可理解爲session的註冊表,從中取用和交還,並保證屢次取用的爲統一session。詳見官方文檔,這裏sql

另外須要注意,此處的sqlalchemy的數據庫查詢,並非異步,當使用tornado 的異步特性時,遇到查詢數據庫慢時,仍是會阻塞的,此時咱們更多的須要考慮的
是去優化咱們的sql,而不是異步查詢數據庫。由於,當數據庫的查詢慢到能夠阻塞進程時,說明確實是有問題了。除非咱們確實是有這種長時間查詢數據庫的需求。
tornado 自己並無提供數據庫層的異步,看了許多異步查詢數據庫的三方庫,都不是特別成熟。還有另外一種解決方案,是使用其餘異步任務庫來完成長時間查詢數據庫的需求,如celery。數據庫

tornado 日誌使用

tornado 的日誌模塊使用了python的logging模塊實現。tornado 文檔日誌部分說的比較簡單,這裏.
讓人讀了,比較糊塗,文中說了,3個內部的 logger: accessapplicationgeneral。一開始我覺得是使用這3個logger來記錄tornado中的日誌信息,
其實不是,他們只是tornado本身內部使用的。咱們徹底能夠本身獲取咱們的logger,即便用root logger 。tornado 做者建議如此,可見這裏cookie

可以下使用,在py中直接獲取logger:session

import logging 

logger = logging.getLogger(__name__)

logger.info('...')

同時tornado提供了,logger的配置項,提供了日誌的文件的命名,路徑,切分等功能。均在在tornado.log.py裏定義。

# tornado/log.py 
def define_logging_options(options=None):
    """Add logging-related flags to ``options``.

    These options are present automatically on the default options instance;
    this method is only necessary if you have created your own `.OptionParser`.

    .. versionadded:: 4.2
        This function existed in prior versions but was broken and undocumented until 4.2.
    """
    if options is None:
        # late import to prevent cycle
        import tornado.options
        options = tornado.options.options
    options.define("logging", default="info",
                   help=("Set the Python log level. If 'none', tornado won't touch the "
                         "logging configuration."),
                   metavar="debug|info|warning|error|none")
    options.define("log_to_stderr", type=bool, default=None,
                   help=("Send log output to stderr (colorized if possible). "
                         "By default use stderr if --log_file_prefix is not set and "
                         "no other logging is configured."))
    options.define("log_file_prefix", type=str, default=None, metavar="PATH",
                   help=("Path prefix for log files. "
                         "Note that if you are running multiple tornado processes, "
                         "log_file_prefix must be different for each of them (e.g. "
                         "include the port number)"))
    options.define("log_file_max_size", type=int, default=100 * 1000 * 1000,
                   help="max size of log files before rollover")
    options.define("log_file_num_backups", type=int, default=10,
                   help="number of log files to keep")

    options.define("log_rotate_when", type=str, default='midnight',
                   help=("specify the type of TimedRotatingFileHandler interval "
                         "other options:('S', 'M', 'H', 'D', 'W0'-'W6')"))
    options.define("log_rotate_interval", type=int, default=1,
                   help="The interval value of timed rotating")

    options.define("log_rotate_mode", type=str, default='size',
                   help="The mode of rotating files(time or size)")

    options.add_parse_callback(lambda: enable_pretty_logging(options))

parse_command_line()執行時,日誌默認值被初始化,通知格式化了root logger,相關代碼均在tornado.log.py中。

有關root logger 的理解,可閱讀這篇博客《python日誌logging詳解》
如何修改tornado日誌格式,可參考這裏,change the log outpu format for a tornado app

其餘找到的最佳實踐的資料

  • tornado wiki 你能夠從tornado的wiki找到一些生產和開發中的最佳實踐。

  • Intoduction tornado 雖然此文檔的tornado版本是老的,可是介紹的知識點,比較全面且通俗易懂。

todo 持續更新

相關文章
相關標籤/搜索