序
刚学Python的时候就是学的Tornado框架。当时看的是《Introduction to Tornado》,从这里了解了web开发是个什么东西。
后来因为工作上没有用到这个框架,虽然尝试写个side project玩玩,却一再中止。这样的结果是,虽然用过,却连tornado框架的并发原理都没懂,只知道是一个异步的web框架。然而这是不严谨的,其实它由web框架加上web server两部分组成。
web框架的定义是(摘自维基百科)
Web应用框架(Web application framework)是一种电脑软件框架,用来支持动态网站、网络应用程序及网络服务的开发。这种框架有助于减轻网页开发时共通性活动的工作负荷,例如许多框架提供数据库访问接口、标准模板以及会话管理等,可提升代码的可再用性。
因为tornado的异步IO模型,它可以完成在高并发和长轮询、长连接这样的场景下的开发任务。前段时间写比特币比价程序的时候,就是用了tornado的websocket来实现“一次连接,双向通信”的效果。
然而,代码写了又忘,这篇文章希望对一些细节和思考做一些记录。
Tornado 应用结构
Tornado应用程序包含了一个或多个RequestHandler
子类,一个Application
实例对象,这个app对象按顺序进行正则匹配来完成路由功能,把请求导向到相应的handler中。
app的路由规则的第三个参数接受一个字典,会传给handler的initialize
函数,对请求做一些初始化工作。而且路由规则可以包含一个name参数,来完成反向查询(reverse_url
)的功能?
单个请求的顺序过程
- 创建RequestHandler对象
- 调用
initialize()
- 调用
prepare()
- 调用对应的http方法
- 调用
on_finish()
- 同步方法立即调用
- 异步方法在
finish()
调用之后调用
Handler
一般会先定义一个BaseHandler的公共基础类,对每个请求会用到的功能做定义。例如write_error
, get_current_user
等
Handler子类可以覆写get、post等http请求方法对应的函数。handler通过write、render来返回响应内容。render方法是用来渲染模板的,write方法可以返回字符串、字节流、字典。如果是字典的话,会被序列化为JSON。
- 请求所有的数据都在
self.request
对象中- body
- files 所有文件数据都是放在内存中的;是一个字典结构,每个字典包含了文件名、http content-type、文件体
get_query_argument(s)
get_body_argument(s)
- 常覆写的方法
write_error
输出错误页面htmlon_connection_close
在客户端断开的时候被调用get_current_user
用户验证相关get_user_locale
返回Locale对象set_default_headers
设置默认响应头
异步handler
两种
gen.coroutine
装饰器:通过yield来暂停并恢复执行,直到函数返回(tornado推荐的方式)- Python3.5引入
async def
,在函数中通过await
来暂停 - 工作原理:
coroutine
装饰器通过yield和生成器交流,通过返回Future
和协程调用者交流 - 协程中的异常不会被察觉,一直被包裹在
Future
中,直到它yield,所以必须正确调用协程 - 无需等待结果的后台执行
- 使用
IOLoop.spawn_callback
- 使用
- 协程中与其他代码交互的一些模式
-
和回调交互
- 用
gen.Task
包裹 yield gen.Task(some_function, other_args)
,这将返回一个Future
对象
- 用
-
和阻塞代码交互
- 使用
ThreadExecutor
,返回一个和协程兼容的Futures
thread_pool = ThreadPoolExecutor(4) @gen.coroutine def call_blocking(): yield thread_pool.submit(blocking_func, args)
- 使用
-
并行
coroutine
能够识别所有值为Future
的列表或字典,并等待所有的异步执行完成
-
交错执行
- 在while循环中,通过yield手动解包异步代码返回的生成器,并flush至http响应中
-
对生成器进行遍历
- 需要将遍历条件和获取数据分开
-
周期性回调
- 在死循环中调用生成器函数,通过
gen.sleep
来休眠 - 结合上面所说的
IOLoop.spawn_callback
- 精确周期调用(周期时间包含实际代码的执行时间)需要先
gen.sleep
,在执行其他代码,最后yield sleep返回的生成器
- 在死循环中调用生成器函数,通过
-
- Python3.5引入
web.asynchronous
装饰器:通过回调来异步,直到显式调用finish()
方法- 特征:长连接,大部分时间是空闲的
- tornado使用单线程事件循环模式
- 同时只有一个操作在执行
- 所有代码必须是异步非阻塞的
- 阻塞和异步的区别
- 阻塞(被调用者角度):等待事件发生,可能由网络IO,磁盘IO,锁导致。事实上,每个函数都可能或多或少阻塞,比如使用CPU加解密。某个函数可能在某方面阻塞,而在其他方面不阻塞。
- 异步(调用者角度):函数在完成之前就返回,一般会产生一些后台工作,然后触发某些未来的动作
- 一些接口类型
- 定义回调参数
- 返回一个占位对象
- 发送给队列
- 回调注册
- 没有简单的方式使同步调用变成异步调用而同时对调用者透明
- gevent使用了轻量级线程,并不是真正的异步执行
- 一些接口类型
部署
- debug模式
- 自动reload
- 禁用模板缓存
- 禁用静态文件hash缓存
- 启用错误栈跟踪
- 不与
HTTPServer
的多线程模式兼容
- 多核多进程模式
- WSGI模式(
tornado.wsgi.WSGIAdapter
)不支持:coroutine
asyncronous
AsyncHTTPClient
auth
模块- WebSocket
协程和并发
tornado.queues
- 类似于python标准库为多线程设计的queue模块
- 可设置最大大小
- 不是线程安全的,必须先转让控制权
- 类
- 普通的队列
Queue
- 优先级队列
PrioriryQueue
(lowest first) - 后进先出队列
LifoQueue
- 普通的队列
同步原语
tornado.locks
- 用于协程间资源的同步,不是线程安全的
- 类
Condition
:一个协程等待另一个协程的通知,可设置超时,超时抛出TimeoutError
异常Event
:等待内部值被设置为TrueSemaphore
信号量:最多可被获取n次的“锁”,release将增加计数(无限制)BoundedSemaphore
:release不能使计数超过初始值Lock
:锁
Web框架
HTTP服务器和客户端
异步网络
IOLoop
非阻塞sockets事件循环
- 单例
IOLoop.instance
- 也可计划基于时间的事件(
.add_timeout
) - 电平触发
- 小节
- 运行IOLoop
- 回调和超时
- 调试和错误处理
- 子类方法
和其他服务交互
工具
- autoreload
- log
- options
- stack_context
- testing
- util