Tornado

简介

Tornado龙卷风是一个开源的网络服务器框架,它是基于社交聚合网站FriendFeed的实时信息服务开发而来的。2007年由4名Google前软件工程师一起创办了FriendFeed,旨在使用户能够方便地跟踪好友在Facebook和Twitter等多个社交网站上的活动。结果两年后,Facebook宣布收购FriendFeed,交易价格约为5000万美元。而此时FriendFeed只有12名员工。据说这帮人后来又到了Google,搞出了现在的Google App Engine...

我们开发这个Web服务器的主要目的就是为了处理FriendFeed的实时功能 -- 在FriendFeed的应用里每个活动用户都会保持着一个服务器连接。
Tornado使FriendFeed使用的可扩展的非阻塞Web服务器及其相关工具的开源版本,这个Web框架看起来有些像web.py或 Google的webapp,不过为了更加有效地利用非阻塞服务器环境,Tornado这个Web框架还包含了一些相关的有用工具和优化。

区别

Tornado与现代主流的Web服务器框架有着明显的区别:它使非阻塞式的服务器,速度相当快。这得益于其非阻塞的方式和对epoll的运用。Tornado每秒可以处理数以千计的连接,对于实时Web服务来说Tornado确实是一个理想的Web框架。

与Node.js相同的是,Tornado也采用的是单进程单线程异步IO的网络模型,它们都可以编写异步非阻塞的程序。但由于Node.js是Google Chrome V8引擎的JS运行环境或工具包,它属于偏底层的抽象,扩展了JS编写服务器程序的能力,所以基于Node.js会由不同的Web框架。从这个角度来看Node.js和Tornado其实并不在一个层次上。

Tornado是使用Python编写的Web服务器兼Web应用框架,与主流Web服务器框架不同的是,Tornado是异步非阻塞式服务器,得益于非阻塞式和对epoll模型的运用,Tornado是实时Web服务的一个理想框架,它非常适合开发长轮询、WebSocket和需要与每个用户建立持久连接的应用。

特点

  • 轻量级Web框架
  • 异步非阻塞IO处理方式
    Tornado采用的单进程单线程异步IO的网络模式,其高性能源于Tornado基于Linux的Epoll(UNIX为kqueue)的异步网络IO。
  • 出色的抗负载能力
  • 不依赖多进程或多线程
  • WSGI全栈替代产品
    WSGI把应用(Application)和服务器(Server)结合起来,Tornado既可以是WSGI应用也可以是WSGI服务。
  • 既是WebServer也是WebFramework

Tornado是基于Bret Taylor和其他人员为FrientFeed所开发的网络服务框架,当FriendFeed被Facebook收购后得以开源。不同于那些最多只能达到1w并发连接的传统网络服务器。Tornado在设计之初就考虑到了性能因素,旨在解决C10K问题,这样的设计使其成为一个拥有高性能的框架。

结构

  • Web框架
    主要包括RequestHandler用于创建Web应用程序和各种支持类的子类
  • HTTP服务器与客户端
    主要包括HTTPServerAsyncHTTPClient
  • 异步网络库
    主要包括IOLoopIOStream作为HTTP组件的构建块
  • 协程库

Tornado的Web框架和HTTP服务器一起提供了完整的堆栈替代方案WSGI

模块

Tornado是一个轻量级框架,它的模块不多最重要的模块是web,web模块包含了Tornado大部分主要功能的Web框架,其他模块都是工具性质的,以便让Web模块更加有用。

  1. Core Web Framework 核心Web框架
  • tornado.web 包括Web框架大部分主要功能,包括RequestHandlerApplication类。
  • tornado.httpserver一个无阻塞HTTP服务器的实现
  • tornado.template模板系统
  • tornado.escape HTML、JSON、URLs等编码解码和字符串操作
  • tornado.locale国际化支持
  1. Asynchronous Networking 异步网络底层模块
  • tornado.ioloop 核心IO循环
  • tornado.iostream对非阻塞的Socket的简单封装以方便常用读写操作
  • tornado.httpclient无阻塞的HTTP服务器实现
  • tornado.netutil网络应用的实现主要是TCPServer类
  1. Integration With Other Services 系统集成服务
  • tornado.auth 使用OpenId和OAuth进行第三方登录
  • tornado.databaseMySQL服务端封装
  • tornado.platform.twisted在Tornado上运行Twisted实现的代码
  • tornado.websocket实现和浏览器的双向通信
  • tornado.wsgi其他Python网络框架或服务器的相互操作
  1. Utilities 应用模块
  • tornado.autoload产生环境中自动检查代码更新
  • tornado.gen基于生成器的接口,使用该模块 保证代码异步运行。
  • tornado.httputil分析HTTP请求内容
  • tornado.options解析终端参数
  • tornado.process多进程实现的封装
  • tornado.stack_context异步环境中对回调函数上下文保存、异常处理
  • tornado.testing单元测试

Tornado服务器的三个底层核心模块

  • httpserver 服务于web模块的一个简单的HTTP服务器的实现
    Tornado的HTTPConnection类用来处理HTTP请求,包括读取HTTP请求头、读取POST传递的数据,调用用户自定义的处理方法,以及把响应数据写给客户端的socket
  • iostream 对非阻塞式的socket的封装以便于常见读写操作
    为了在处理请求时实现对socket的异步读写,Tornado实现了IOStream类用来处理socket的异步读写。
  • ioloop 核心的I/O循环
    Tornado为了实现高并发和高性能,使用了一个IOLoop事件循环来处理socket的读写事件,IOLoop事件循环是基于Linux的epoll模型,可以高效地响应网络事件,这是Tornado高效的基础保证。


设计模型

Tornado不仅仅是一个Web框架,它完整地实现了HTTP服务器和客户端,再此基础上提供了Web服务,它可分为四层:

  • Web框架:最上层,包括处理器、模板、数据库连接、认证、本地化等Web框架所需功能。
  • HTTP/HTTPS层:基于HTTP协议实现了HTTP服务器和客户端
  • TCP层:实现TCP服务器负责数据传输
  • Event层:最底层、处理IO事件
 
使用Tornado可以方便地架构出各种类型的web服务器,以HTTP服务器为例来分析下web服务器的工作方式。
 

一个请求处理的处理过程

  1. 服务器绑定bind到特定端口port,然后开始监听listen客户端的请求。
  2. 当客户端连接connect到来时,会将请求发送给服务器。
  3. 服务器处理请求完毕后返回响应结果给客户端

当需要处理成千上万的连接的时候,就会遇到典型的The C10K Program问题,常见的解决方案有

  • 一个线程服务多个客户端,使用非阻塞I/O和水平触发的就绪通知。
  • 一个线程服务多个客户端,使用非阻塞I/O和就绪改变时通知。
  • 一个服务线程服务于多个客户端,使用异步I/O。
  • 一个服务线程服务于一个客户端,使用阻塞I/O。
  • 将服务代码编译进内核

Tornado采用的方式是“多进程 + 非阻塞 + epoll模式”


安装配置

参考资料

版本问题

  • Tornado4.3 可以运行在Python2.6、2.7、3.2+
  • Tornado6.0需要Python3.5.2+

CentOS安装Tornado

环境检查

# 查看python版本
$ python --version
Python 2.7.5

$ python
Python 2.7.5 (default, Apr  9 2019, 14:30:50) 
[GCC 4.8.5 20150623 (Red Hat 4.8.5-36)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> 

 

CentOS安装Python3.7并保留Python2

# 安装依赖
$ yum -y install zlib-devel bzip2-devel openssl-devel ncurses-devel sqlite-devel readline-devel tk-devel gdbm-devel db4-devel libpcap-devel xz-devel libffi-devel gcc gcc-c++

# 创建目录
$ mkdir /usr/local/python3

# 下载编译安装
$ wget https://www.python.org/ftp/python/3.7.3/Python-3.7.3.tgz
$ tar -zxvf Python3.7.3.tgz && cd Python3.7.3
$ ./configure
$ make && make install

# 创建软连接
$ ln -s /usr/local/python3/bin/python3 /usr/bin/python3
$ ln -s /usr/local/python3/bin/pip3 /usr/bin/pip3

# 测试版本
$ python3 -V
Python 3.7.3

$ pip3 -v
pip 19.0.3 from /usr/local/lib/python3.7/site-packages/pip (python 3.7)

# 使用pip3安装tornado
$ pip3 install tornado
Collecting tornado
  Using cached https://files.pythonhosted.org/packages/30/78/2d2823598496127b21423baffaa186b668f73cd91887fcef78b6eade136b/tornado-6.0.3.tar.gz
Installing collected packages: tornado
  Running setup.py install for tornado ... done
Successfully installed tornado-6.0.3
You are using pip version 19.0.3, however version 19.1.1 is available.
You should consider upgrading via the 'pip install --upgrade pip' command.

# 测试tornado
$ python3
Python 3.7.3 (default, Jul  8 2019, 17:00:47) 
[GCC 4.8.5 20150623 (Red Hat 4.8.5-36)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import tornado

 

CentOS安装配置pip

# CentOS安装EPEL扩展源
$ yum install -y epel-release

# CentOS安装Python Pip
$ yum install -y python-pip

# 查看pip版本
$ pip --version
pip 19.0.3 from /usr/local/lib/python3.7/site-packages/pip (python 3.7)

# 更新pip
$ pip install --upgrade pip

$ pip --version
pip 19.0.3 from /usr/local/lib/python3.7/site-packages/pip (python 3.7)

 

可直接使用git clone从GitHub中克隆下载Tornado源码,也可以使用pip命令安装Tornado。

# 使用GitHub克隆源码包
$ git clone https://github.com/tornadoweb/tornado
$ cd tornado
$ python setup.py install

 

检查当前Python环境

$ python -V
Python 3.7.3

$ pip -V
pip 19.0.3 from /usr/local/lib/python3.7/site-packages/pip (python 3.7)

 

当前 Tornado 版本为 6.0.3,Tornado6.0开始将抛弃对Python2.7和3.4的支持,同时将Python3.5.2作为最低支持版本。

Ubuntu安装Tornado

Ubuntu自身安装了Python2和Python3,默认采用的是Python3,使用时需要进行切换版本。

检查当前系统环境

$ uname -a

# 检查内核版本
$ uname -r

 

检查当前Python环境

$ python -V
Python 2.7.15+
$ whereis python
python: /usr/bin/python2.7 /usr/bin/python2.7-config /usr/bin/python3.6 /usr/bin/python /usr/bin/python3.6m /usr/lib/python2.7 /usr/lib/python3.6 /usr/lib/python3.7 /etc/python2.7 /etc/python3.6 /etc/python /usr/local/lib/python2.7 /usr/local/lib/python3.6 /usr/include/python2.7 /usr/include/python3.6m /usr/share/python /usr/share/man/man1/python.1.gz

$ pip -V
pip 9.0.1 from /usr/lib/python2.7/dist-packages (python 2.7)
$ whereis pip
pip: /usr/bin/pip /usr/share/man/man1/pip.1.gz
$ type pip
pip 已被录入哈希表 (/usr/bin/pip)

$ python2 -V
Python 2.7.15+
python2: /usr/bin/python2.7 /usr/bin/python2.7-config /usr/bin/python2 /usr/lib/python2.7 /etc/python2.7 /usr/local/lib/python2.7 /usr/include/python2.7 /usr/share/man/man1/python2.1.gz

$ python3 -V
Python 3.6.8
$ whereis python3
python3: /usr/bin/python3 /usr/bin/python3.6 /usr/bin/python3.6m /usr/lib/python3 /usr/lib/python3.6 /usr/lib/python3.7 /etc/python3 /etc/python3.6 /usr/local/lib/python3.6 /usr/include/python3.6m /usr/share/python3 /usr/share/man/man1/python3.1.gz

 

安装Python3

$ sudo apt-get update
$ sudo apt-get install python3

 

调整Python3的优先级

$ sudo update-alternative --install /usr/bin/python3 python3 /usr/bin/python3.6 1

 

调整Python版本的默认值为Python3

$ sudo update-alternatives --install /usr/bin/python python /usr/bin/python3 100
$ sudo update-alternatives --install /usr/bin/python python /usr/bin/python3 150
$ python -V
Python 3.6.8

 

安装pip

$ sudo apt install python-pip
$ sudo apt install python3-pip
$ pip -V
pip 9.0.1 from /usr/lib/python3/dist-packages (python 3.6)
$ pip2 -V
pip 19.1.1 from /usr/local/lib/python2.7/dist-packages/pip (python 2.7)
$ pip3 -V
pip 9.0.1 from /usr/lib/python3/dist-packages (python 3.6)
$ whereis pip3
pip3: /usr/bin/pip3 /usr/share/man/man1/pip3.1.gz

 

更新pip为最新版

$ sudo pip install --upgrade pip
$ pip -V
Traceback (most recent call last):
  File "/usr/bin/pip", line 9, in <module>
    from pip import main
ModuleNotFoundError: No module named 'pip'

$ sudo pip -V

# pip版本回退
$ sudo python3 -m pip install --user --upgrade pip==9.0.1

 

卸载pip

$ sudo apt-get remove python-pip
$ sudo apt-get remove python2-pip
$ duso apt-get remove python3-pip

 

查看pip已安装列表

$ pip list

 

安装Tornado

$ pip install tornado
Successfully installed tornado-6.0.3

$ python
>>> import tornado

 

开发流程

首先创建Web应用程序Application,并将指定处理器Handler传递过去,然后开始监听指定端口,最后启动事件循环,并开始监听网络事件,主要是socket的读写操作。

创建脚本

$ vim server.py
#! /usr/bin/python
# encoding:utf-8

# 导入Tornado模块
import tornado.ioloop #核心IO循环模块
import tornado.httpserver #异步非阻塞HTTP服务器模块
import tornado.web #Web框架模块
import tornado.options #解析终端参数模块

#从终端模块中导出define模块用于读取参数,导出options模块用于设置默认参数
from tornado.options import define, options

# 定义端口用于指定HTTP服务监听的端口
# 如果命令行中带有port同名参数则会称为全局tornado.options的属性,若没有则使用define定义。
define("port", type=int, default=8000, help="run on the given port")

# 创建请求处理器
# 当处理请求时会进行实例化并调用HTTP请求对应的方法
class IndexHandler(tornado.web.RequestHandler):
    # 定义get方法对HTTP的GET请求做出响应
    def get(self):
        # 从querystring查询字符串中获取id参数的值,若无则默认为0.
        id = self.get_argument("id", 0)
        # write方法将字符串写入HTTP响应
        self.write("hello world id = " + id)

# 创建路由表
urls = [(r"/", IndexHandler),]

# 定义服务器
def main():
    # 解析命令行参数
    tornado.options.parse_command_line()
    # 创建应用实例
    app = tornado.web.Application(urls)
    # 监听端口
    app.listen(options.port)
    # 创建IOLoop实例并启动
    tornado.ioloop.IOLoop.current().start()

# 应用运行入口,解析命令行参数
if __name__ == "__main__":
    # 启动服务器
    main()

 

运行服务器

$ python server.py

 

运行测试

$ curl http://127.0.0.1:8000

 

进程监控

SuperVisor是一个进程监控程序,当进程需要不间断运行时由于各种原因可能中断,当进程中断时希望能够自动重启,此时就可以使用SuperVisor。

# 安装
$ pip install supervisor

# 配置
$ echo_supervisord_conf  > /etc/supervisord.conf

# 开启配置
$ vim /etc/supervisord.conf
;[include]
files = /etc/supervisor/*.ini

# 创建配置
$ mkdir /etc/supervisor
$ cd /etc/supervisor
$ vim tornado.ini
[program:tornado]
command=python3 /home/tornado/server.py --port=8000
directory=/home/tornado/
autorestart=true
redirect_stderr=true

# 重启进程
$ ps aux |  grep supervisord
$ supervisord -c /etc/supervisor.conf

 

调试模式

$ vim server.py

 

应用程序执行后会首先解析并选择参数,然后创建有一个Application实例并传递给HTTPServer实例并启动。到此HTTPServer启动,tornado.httpserver模块用来支持非阻塞的HTTPServer。启动服务器后还需启动IOLoop实例以启动事件循环机制,配合非阻塞HTTPServer一起工作。

代码组织结构

注释
import 语句
定义选项参数
Application定义
BaseHandler定义
xxxHandler定义
main()定义
 
#! /usr/bin/python
# encoding:utf-8

from tornado.ioloop import IOLoop
from tornado.httpserver import HTTPServer
from tornado.web import Application, RequestHandler, url
#从终端模块中导出define模块用于读取参数,导出options模块用于设置默认参数
from tornado.options import define, options
#开始调试模式
import tornado.autoreload


# 定义端口用于指定HTTP服务监听的端口
# 如果命令行中带有port同名参数则会称为全局tornado.options的属性,若没有则使用define定义。
define("port", type=int, default=8000, help="run on the given port")
# 调试模式
define("debug", type=bool, default=True, help="debug mode")


# 创建请求处理器
# 当处理请求时会进行实例化并调用HTTP请求对应的方法
class MainHandler(RequestHandler):
    # 定义get方法对HTTP的GET请求做出响应
    def get(self, *args,  **kwargs):
        # 从querystring查询字符串中获取id参数的值,若无则默认为0.
        id = self.get_query_argument("id", strip=True)
        # write方法将字符串写入HTTP响应
        self.write("hello world id = " + str(id))

# 创建路由表
urls = [
    (r"/", MainHandler),
    (r"/index", MainHandler)
]
# 创建配置
settings = dict(
    debug = options.debug
)

# 创建应用
def make_app():
    return Application(urls, settings)

# 定义服务器
def main():
    # 解析命令行参数
    options.parse_command_line()
    # 创建应用
    app = make_app()
    # 创建HTTP服务器实例
    server = HTTPServer(app)
    # 监听端口
    server.listen(options.port)
    # 创建IOLoop实例并启动
    IOLoop.current().start()

# 应用运行入口,解析命令行参数
if __name__ == "__main__":
    # 启动服务器
    main()

 

运行测试

$ python server.py
 

浏览器访问http://127.0.0.1:8000?id=1000查看服务器输出

定义选项参数

Tornado提供了tornado.options.define方法用来简化选项参数的定义。

# 定义端口
define("port", type=int, default=8000, help="run on the given port")
# 定义调试模式
define("debug", type=bool, default=True, help="debug mode")

 

解析命令行参数

tornado.options.parse_command_line()

 

创建Web应用实例

app = tornado.web.Application(urls, settings)

 

Tornado中Application应用类是Handler处理器的集合

Application类的__init__初始化函数原型

# 原型
def __init__(self, handlers=None, default_host="", transforms=None, wsgi=False, **settings):

 

Tornado的HTTPServer会负责解析用户的HTTPRequest,构造一个request对象。并交给RequestHandler处理,Request的解析是一个规划化的流程,针对Request的处理函数RequestHandler是被自定义的重点部分。

由于HTTP是工作在TCP协议之上的,HTTPServer其实是TCPServer的派生类,常规socket编程中启动一个TCPServer有三个必备步骤:

  1. 创建socket
  2. 绑定指定地址的端口
  3. 执行监听

TCPServer类的实现借鉴UNIX/Linux中的Socket机制,也必然存在上述步骤,这几个步骤都是在HTTPServer.listen()函数调用时完成的。

server.listen(options.port)

 

listen函数的参数是端口号,端口定义可通过define来定义。
from tornado.options import define, options

define("port", default=8888, help="run on the given port", type=int)

 

define函数是OptionParser类的成员,定义在tornado/options.py文件中,机制于parse_command_line()类似。define定义端口port或,port变量会被存放在options对象的directory成员中,因此可直接使用options.port访问。

当使用server.listen(options.port)后,服务器就会在端口上启动一个服务,并开始监听客户端的连接。对于常规的Socket操作,listen之后的操作应该是accept

在Tornado中accept操作是这样的:

tornado.ioloop.IOLoop.current().start()

 

IOLoop是什么呢?IOLoop于TCPServer之间的关系其实很简单。例如使用C语言编写TCP服务器时,编写完create-bind-listen三段式之后,都需要编写accept/recv/send处理客户端请求。通常会写一个无限循环,不断调用accept来响应客户端连接,其实这个无线循环就是Tornado中的IOLoop。

IOLoop会负责accept这一步,对于recv/send操作通常也是在一个循环中进行的,也可以抽象成IOLoop。

最后,简单梳理下整个流程:当我们使用在客户端浏览器地址栏中输入http://127.0.0.1:8000?id=1000时,浏览器首先会连接服务器 ,将HTTP请求发送到HTTPServer中,HTTPServer会先解析请求parse request,然后将请求request交给第一个匹配到的处理器Handler。处理器Handler会负责组织数据并调用发送API将数据发送到客户端。

核心组件

Tornado的Web服务器通常包含四大组件

ioloop实例

tornado.ioloop是全局Tornado的IO事件循环,是服务器的引擎核心。

tornado.ioloop是核心IO循环模块,封装了Linux的epoll和BSD的kqueue,是Tornado高性能处理的核心。

tornado.ioloop.IOLoop.current()返回当前线程的IOLoop实例对象

tornado.ioloop.IOLoop.current().start() 用于启动IOLoop实例对象的IO循环并开启监听

# 加载Tornado核心IO事件循环模块
import tornado.ioloop

# 默认Tornado的ioloop实例
tornado.ioloop.IOLoop.current()

 

app实例

app实例代表了一个完成的后端应用,它会挂接一个服务端套接字端口并对外提供服务,一个ioloop事件循环实例中可以包含多个app实例。

# 创建应用实例
app = tornado.web.Application(urls)
# 监听端口
app.listen(options.port)

 

urls路由表

路由表用于将指定URL规则和处理器Handler挂接起来形成路由映射表,当请求到来时会根据请求的访问URL查询路由映射表来查询对应业务的处理器Handler

urls = [(r"/", MainHandler),]

 

handler

handler类代表着业务逻辑,在进行服务端开发时也就是在编写处理器,用以服务客户端请求。

# 当处理请求时会进行实例化并调用HTTP请求对应的方法
class MainHandler(tornado.web.RequestHandler):
    # 定义get方法对HTTP的GET请求做出响应
    def get(self):
        # 从querystring查询字符串中获取id参数的值,若无则默认为0.
        id = self.get_argument("id", 0)
        # write方法将字符串写入HTTP响应
        self.write("hello world id = " + id)

 

四大组件的关系

  • 一个IO事件循环ioloop可以包含多个应用app,即可以管理多个服务端口。
  • 一个应用app可以包含一个路由表urls
  • 一个路由表urls可以包含多个处理器Handler

ioloop是服务的引擎核心是发动机,负责接收和响应客户端请求,负责驱动业务处理器handler的运行,负责服务器内部定时任务的执行。同一个ioloop实例会运行在一个单线程环境下。

 
当一个请求到来时,IO事件循环ioloop会读取请求并解包形成 一个HTTP请求对象,并找到该套接字上对应应用app的路由表urls,通过请求对象的URL查询路由表中挂接的处理器Handler,然后执行处理器Handlerhandler处理器执行后会返回一个对象,ioloop负责将对象包装成HTTP响应对象并序列化发送给客户端。


posted @ 2020-06-21 12:13  子非鱼且于  阅读(673)  评论(0编辑  收藏  举报
Live2D