引言php
回想Django的部署方式java
以Django爲表明的python web應用部署時採用wsgi協議與服務器對接(被服務器託管),而這類服務器一般都是基於多線程的,也就是說每個網絡請求服務器都會有一個對應的線程來用web應用(如Django)進行處理。python
考慮兩類應用場景ios
用戶量大,高併發nginx
如秒殺搶購、雙十一某寶購物、春節搶火車票c++
大量的HTTP持久鏈接web
使用同一個TCP鏈接來發送和接收多個HTTP請求/應答,而不是爲每個新的請求/應答打開新的鏈接的方法。redis
對於HTTP 1.0,能夠在請求的包頭(Header)中添加Connection: Keep-Alive。數據庫
對於HTTP 1.1,全部的鏈接默認都是持久鏈接。django
對於這兩種場景,一般基於多線程的服務器很難應對。
C10K問題
對於前文提出的這種高併發問題,咱們一般用C10K這一律念來描述。C10K—— Concurrently handling ten thousand connections,即併發10000個鏈接。對於單臺服務器而言,根本沒法承擔,而採用多臺服務器分佈式又意味着高昂的成本。如何解決C10K問題?
Tornado
Tornado在設計之初就考慮到了性能因素,旨在解決C10K問題,這樣的設計使得其成爲一個擁有很是高性能的解決方案(服務器與框架的集合體)。
1、關於Tornado
1.1 Tornado是爲什麼物
Tornado全稱Tornado Web Server,是一個用Python語言寫成的Web服務器兼Web應用框架,由FriendFeed公司在本身的網站FriendFeed中使用,被Facebook收購之後框架在2009年9月以開源軟件形式開放給大衆。
特色:
性能: Tornado有着優異的性能。它試圖解決C10k問題,即處理大於或等於一萬的併發,下表是和一些其餘Web框架與服務器的對比:
Tornado框架和服務器一塊兒組成一個WSGI的全棧替代品。單獨在WSGI容器中使用tornado網絡框架或者tornaod http服務器,有必定的侷限性,爲了最大化的利用tornado的性能,推薦同時使用tornaod的網絡框架和HTTP服務器
1.2 Tornado與Django
Django
Django是走大而全的方向,注重的是高效開發,它最出名的是其全自動化的管理後臺:只須要使用起ORM,作簡單的對象定義,它就能自動生成數據庫結構、以及全功能的管理後臺。
Django提供的方便,也意味着Django內置的ORM跟框架內的其餘模塊耦合程度高,應用程序必須使用Django內置的ORM,不然就不能享受到框架內提供的種種基於其ORM的便利。
Tornado
Tornado走的是少而精的方向,注重的是性能優越,它最出名的是異步非阻塞的設計方式。
2、初識Tornado
2.1 安裝
安裝
pip install tornado
關於使用平臺的說明
Tornado應該運行在類Unix平臺,在線上部署時爲了最佳的性能和擴展性,僅推薦Linux和BSD(由於充分利用Linux的epoll工具和BSD的kqueue工具,是Tornado不依靠多進程/多線程而達到高性能的緣由)。
對於Mac OS X,雖然也是衍生自BSD而且支持kqueue,可是其網絡性能一般不太給力,所以僅推薦用於開發。
對於Windows,Tornado官方沒有提供配置支持,可是也能夠運行起來,不過僅推薦在開發中使用。
2.2 Hello Tornado
新建文件demo.py,代碼以下:
import tornado.web import tornado.ioloop class IndexHandle(tornado.web.RequestHandler): """主路由處理類""" def get(self): """對應http的get請求""" self.write("hello Tornado") if __name__ == '__main__': app = tornado.web.Application([(r'/index', IndexHandle)]) app.listen(8001) tornado.ioloop.IOLoop.current().start()
打開瀏覽器,輸入網址127.0.0.1:8001,查看效果:
代碼講解
1. tornado.web
tornado的基礎web框架模塊
RequestHandler
封裝了對應一個請求的全部信息和方法,write(響應信息)就是寫響應信息的一個方法;對應每一種http請求方式(get、post等),把對應的處理邏輯寫進同名的成員方法中(如對應get請求方式,就將對應的處理邏輯寫在get()方法中),當沒有對應請求方式的成員方法時,會返回「405: Method Not Allowed」錯誤。
咱們將代碼中定義的get()方法更改成post()後,再用瀏覽器從新訪問
Application
Tornado Web框架的核心應用類,是與服務器對接的接口,裏面保存了路由信息表,其初始化接收的第一個參數就是一個路由信息映射元組的列表;其listen(端口)方法用來建立一個http服務器實例,並綁定到給定端口(注意:此時服務器並未開啓監聽)
2. tornado.ioloop
tornado的核心io循環模塊,封裝了Linux的epoll和BSD的kqueue,tornado高性能的基石。 以Linux的epoll爲例,其原理以下圖:
IOLoop.current()
返回當前線程的IOLoop實例。
IOLoop.start()
啓動IOLoop實例的I/O循環,同時服務器監聽被打開。
總結Tornado Web程序編寫思路
2.3 httpserver
上一節咱們說在tornado.web.Application.listen()(示例代碼中的app.listen(8001))的方法中,建立了一個http服務器示例並綁定到給定端口,咱們能不能本身動手來實現這一部分功能呢?
如今咱們修改上一示例代碼以下:
import tornado.web import tornado.httpserver import tornado.ioloop class IndexHandle(tornado.web.RequestHandler): """主路由處理類""" def get(self): """對應http的get請求""" self.write("hello Tornado") if __name__ == '__main__': app = tornado.web.Application([(r'/index', IndexHandle)]) # 修改這個部分 # app.listen(8001) http_server = tornado.httpserver.HTTPServer(app) http_server.listen(8001) tornado.ioloop.IOLoop.current().start()
在這一修改版本中,咱們引入了tornado.httpserver模塊,顧名思義,它就是tornado的HTTP服務器實現。
咱們建立了一個HTTP服務器實例http_server,由於服務器要服務於咱們剛剛創建的web應用,將接收到的客戶端請求經過web應用中的路由映射表引導到對應的handler中,因此在構建http_server對象的時候須要傳出web應用對象app。http_server.listen(8001)將服務器綁定到8001端口。
實際上一版代碼中app.listen(8001)正是對這一過程的簡寫。
單進程與多進程
咱們剛剛實現的都是單進程
咱們也能夠一次啓動多個進程,修改上面的代碼以下:
import tornado.web import tornado.httpserver import tornado.ioloop class IndexHandle(tornado.web.RequestHandler): """主路由處理類""" def get(self): """對應http的get請求""" self.write("hello Tornado") if __name__ == '__main__': app = tornado.web.Application([(r'/index', IndexHandle)]) http_server = tornado.httpserver.HTTPServer(app) http_server.bind(8000) http_server.start(0) tornado.ioloop.IOLoop.current().start()
http_server.bind(port)方法是將服務器綁定到指定端口。
http_server.start(num_processes=1)方法指定開啓幾個進程,參數num_processes默認值爲1,即默認僅開啓一個進程;若是num_processes爲None或者<=0,則自動根據機器硬件的cpu核芯數建立同等數目的子進程;若是num_processes>0,則建立num_processes個子進程。
咱們在前面寫的http_server.listen(8000)實際上就等同於:
http_server.bind(8000)
http_server.start(1)
說明
1.關於app.listen()
app.listen()這個方法只能在單進程模式中使用。
對於app.listen()與手動建立HTTPServer實例
http_server = tornado.httpserver.HTTPServer(app)
http_server.listen(8000)
這兩種方式,建議你們先使用後者即建立HTTPServer實例的方式,由於其對於理解tornado web應用工做流程的完整性有幫助,便於你們記憶tornado開發的模塊組成和程序結構;在熟練使用後,能夠改成簡寫。
2.關於多進程
雖然tornado給咱們提供了一次開啓多個進程的方法,可是因爲:
不建議使用這種多進程的方式,而是手動開啓多個進程,而且綁定不一樣的端口。
2.4 options
在前面的示例中咱們都是將服務端口的參數寫死在程序中,很不靈活。
tornado爲咱們提供了一個便捷的工具,tornado.options模塊——全局參數定義、存儲、轉換。
tornado.options.define()
用來定義options選項變量的方法,定義的變量能夠在全局的tornado.options.options中獲取使用,傳入參數:
tornado.options.options
全局的options對象,全部定義的選項變量都會做爲該對象的屬性。
tornado.options.parse_command_line()
轉換命令行參數,並將轉換後的值對應的設置到全局options對象相關屬性上。追加命令行參數的方式是--myoption=myvalue
新建opt.py,咱們用代碼來看一下如何使用:
import tornado.web import tornado.httpserver import tornado.ioloop import tornado.options tornado.options.define("port", default=8000, type=int, help="run server on the given port.") # 定義服務器監聽端口選項 tornado.options.define("test", default=[], type=str, multiple=True, help="itcast subjects.") # 無心義,測試多值狀況 class IndexHandle(tornado.web.RequestHandler): """主路由處理類""" def get(self): """對應http的get請求""" self.write("hello Tornado") if __name__ == '__main__': tornado.options.parse_command_line() print(tornado.options.options.test) app = tornado.web.Application([(r'/index', IndexHandle)]) http_server = tornado.httpserver.HTTPServer(app) http_server.listen(tornado.options.options.port) tornado.ioloop.IOLoop.current().start()
執行以下命令開啓程序:
python3 opt.py --port=9000 --test=python,c++,java,php,ios
tornado.options.parse_config_file(path)
從配置文件導入option,配置文件中的選項格式以下:
myoption = "myvalue" myotheroption = "myothervalue"
咱們用代碼來看一下如何使用,新建配置文件config,注意字符串和列表按照python的語法格式:
port = 8000
test= ["python","c++","java","php","ios"]
修改opt.py文件:
import tornado.web import tornado.httpserver import tornado.ioloop import tornado.options tornado.options.define("port", default=8000, type=int, help="run server on the given port.") # 定義服務器監聽端口選項 tornado.options.define("test", default=[], type=str, multiple=True, help="itcast subjects.") # 無心義,測試多值狀況 class IndexHandle(tornado.web.RequestHandler): """主路由處理類""" def get(self): """對應http的get請求""" self.write("hello Tornado") if __name__ == '__main__': tornado.options.parse_config_file("./config") tornado.options.parse_command_line() # 輸出多值選項 # print(tornado.options.options.test) app = tornado.web.Application([(r'/index', IndexHandle)]) http_server = tornado.httpserver.HTTPServer(app) http_server.listen(tornado.options.options.port) tornado.ioloop.IOLoop.current().start()
說明
1. 日誌
當咱們在代碼中調用parse_command_line()或者parse_config_file()的方法時,tornado會默認爲咱們配置標準logging模塊,即默認開啓了日誌功能,並向標準輸出(屏幕)打印日誌信息。
若是想關閉tornado默認的日誌功能,能夠在命令行中添加--logging=none 或者在代碼中執行以下操做:
from tornado.options import options, parse_command_line options.logging = None parse_command_line()
2. 配置文件
咱們看到在使用prase_config_file()的時候,配置文件的書寫格式仍須要按照python的語法要求,其優點是能夠直接將配置文件的參數轉換設置到全局對象tornado.options.options中;然而,其不方便的地方在於須要在代碼中調用tornado.options.define()來定義選項,並且不支持字典類型,故而在實際應用中大都不使用這種方法。
在使用配置文件的時候,一般會新建一個python文件(如config.py),而後在裏面直接定義python類型的變量(能夠是字典類型);在須要配置文件參數的地方,將config.py做爲模塊導入,並使用其中的變量參數。
如config.py文件:
# Redis配置 redis_options = { 'redis_host':'127.0.0.1', 'redis_port':6379, 'redis_pass':'', } # Tornado app配置 settings = { 'template_path': os.path.join(os.path.dirname(__file__), 'templates'), 'static_path': os.path.join(os.path.dirname(__file__), 'statics'), 'cookie_secret':'0Q1AKOKTQHqaa+N80XhYW7KCGskOUE2snCW06UIxXgI=', 'xsrf_cookies':False, 'login_url':'/login', 'debug':True, } # 日誌 log_path = os.path.join(os.path.dirname(__file__), 'logs/log')
使用config.py的模塊中導入config,以下:
import tornado.web import config if __name__ = "__main__": app = tornado.web.Application([], **config.settings) ....