友情提示:
1.文中大多是总结性的文字, 涉及很多省略的前置基础知识,可以自行搜索酌情补充;
2.另,个人水平有限,难免出错误导,请谨慎阅读
3.可以参考github上详细的代码笔记, 一定会有收获
谈谈我在学习Tornado时的一些理解:
9.补充 debug 模式
tornado是默认为debug=False即工作在生产模式。当设置debug=True 后,tornado会工作在调试/开发模式,在此种模式下,tornado为方便我们开发而提供了几种特性:
- 1.自动重启,tornado应用会监控我们的源代码文件,当有改动保存后便会重启程序,这可以减少我们手动重启程序的次数。需要注意的是,一旦我们保存的更改有错误,自动重启会导致程序报错而退出,从而需要我们保存修正错误后手动启动程序。这一特性也可单独通过autoreload=True设置;
- 2.取消缓存编译的模板,可以单独通过compiled_template_cache=False来设置;
- 3.取消缓存静态文件hash值,可以单独通过static_hash_cache=False来设置;
- 4.提供追踪信息,当RequestHandler或者其子类抛出一个异常而未被捕获后,会生成一个包含追踪信息的页面,可以单独通过serve_traceback=True来设置。
- 补充说明:取消缓存,可以在我们修改静态文件后,浏览器是用最新的内容而不是缓存.
10.tornado的RequestHandler类封装方法的调用顺序
在正常情况未抛出错误时,调用顺序为:
set_defautl_headers()
initialize()
prepare()
HTTP方法
on_finish() # 在请求处理结束后调用,即在调用HTTP方法后调用。通常该方法用来进行资源清理释放或处理日志等。注意:请尽量不要在此方法中进行响应输出。
在有错误抛出时,调用顺序为:
set_default_headers()
initialize()
prepare()
HTTP方法
set_default_headers()
write_error()
on_finish()
11.提供静态文件的思考
- 1.设置静态文件的根路径static_path, 这个是主要是为了支持自己的 css等其他静态资源之间的相互src;
- 2.设置静态文件的跟路由/static/, 用来给用户友好访问的, 可能会指定更加细致的静态文件路径, 以及一些请求静态资源不存在时候默认的资源路径.
- 3.总结: 这类问题思考的角度是从两点: 用户通过url使用; 前端文件之间的src;
12.cookies加密
import base64, uuid
s = base64.b64encode(uuid.uuid4().bytes + uuid.uuid4().bytes) # '2hcicVu+TqShDpfsjMWQLZ0Mkq5NPEWSk9fi0zsSt3A='
Base64是一种基于64个可打印字符来表示二进制数据的表示方法。由于2的6次方等于64,所以每6个比特为一个单元,对应某个可打印字符。三个字节有24个比特,对应于4个Base64单元,即3个字节需要用4个可打印字符来表示。
uuid, 通用唯一识别码(英语:Universally Unique Identifier,简称UUID),是由一组32个16进制数字所构成(两个16进制数是一个字节,总共16字节),因此UUID理论上的总数为1632=2128,约等于3.4 x 10^38。也就是说若每纳秒产生1兆个UUID,要花100亿年才会将所有UUID用完。
uuid模块的uuid4()函数可以随机产生一个uuid码,bytes属性将此uuid码作为16字节字符串。
13.浏览器的同源策略
浏览器有一个很重要的概念——同源策略(Same-Origin Policy)。 所谓同源是指,域名,协议,端口相同。 不同源的客户端脚本(javascript、ActionScript)在没明确授权的情况下,不能读写对方的资源。
14.CSRF 防护
这里不重复其原理, 只是再梳理一下, 其实我们除了cookies中要携带csrf_token字段之外, 其它还可以供我们携带的方式如下:
- 1.通过html模板中的form表单提交, 则采用 一条 隐藏的 标签来实现;
- 2.通过body来提交,有两种解决方案:
- 若请求体是表单编码格式的,可以在请求体中添加_xsrf参数
- 若请求体是其他格式的(如json或xml等),可以通过设置HTTP头X-XSRFToken来传递_xsrf值
在补充一点web frame一般的设置, tornado 和 flask 都是如此. 1.会提供一个开启CSRF Protect 的方式, 但是不会帮我们具体去实现 csrf protect. 需要我们自己采用上述的三种方式来实现 防护.
15.实现异步的方案
- 1.回调函数. 如, js等是采用此种方式, node.js就是通过 单进程单线程异步回调实现高性能服务器的.
- 2.协程(yield). (补充知识: yield可以实现 并行协程)
Tornado实现异步的机制不是线程,而是epoll,即将异步过程交给epoll执行并进行监视回调。是在一个线程上完成切换的, 属于真正意义上的协程
拓展思考
- 1.Tornado里实现异步的方式就是通常的协程对吗? 为什么
- 2.Tornado中出现yield就是异步,这句话对吗?
- 3.怎么理解yield将程序挂起?在Tornado中又如何理解yield挂起程序实现异步?
16,关于数据库的异步说明
下面来源于网络
网站基本都会有数据库操作,而Tornado是单线程的,这意味着如果数据库查询返回过慢,整个服务器响应会被堵塞。
数据库查询,实质上也是远程的网络调用;理想情况下,是将这些操作也封装成为异步的;但Tornado对此并没有提供任何支持。
这是Tornado的设计,而不是缺陷。
一个系统,要满足高流量;是必须解决数据库查询速度问题的!
数据库若存在查询性能问题,整个系统无论如何优化,数据库都会是瓶颈,拖慢整个系统!
异步并不能从本质上提到系统的性能;它仅仅是避免多余的网络响应等待,以及切换线程的CPU耗费。
如果数据库查询响应太慢,需要解决的是数据库的性能问题;而不是调用数据库的前端Web应用。
对于实时返回的数据查询,理想情况下需要确保所有数据都在内存中,数据库硬盘IO应该为0;这样的查询才能足够快;而如果数据库查询足够快,那么前端web应用也就无将数据查询封装为异步的必要。
就算是使用协程,异步程序对于同步程序始终还是会提高复杂性;需要衡量的是处理这些额外复杂性是否值得。
如果后端有查询实在是太慢,无法绕过,Tornaod的建议是将这些查询在后端封装独立封装成为HTTP接口,然后使用Tornado内置的异步HTTP客户端进行调用。
17.关于websocket
tornado提供了关于 websocket 的工具
- 1.websocket是 HTML5 提出的 通讯协议, 不是 HTTP协议的内容, 注意.
- 2.WebSocket 是独立的、创建在 TCP 上的协议,和 HTTP 的唯一关联是使用 HTTP 协议的101状态码进行协议切换,使用的 TCP 端口是80,可以用于绕过大多数防火墙的限制。
13.nginx在web部署中的介绍:
- 1.nginx的作用:
- 1.1 客户端提供静态资源;
- 1.2 负载均衡(方向代理)
- 2.配置文件重点关注:
- 2.1 upstream 分组
- 2.2 location 不同的路径, 实现 不跨域
- 2.3 proxy_pass :代理地址
- 2.4 proxy_set_header X-Real-IP $remote_addr; 注意: 一般在web业务中可能要对ip进行处理,这里一定要配置从而使用远端ip,否则使用的是本地nginx的ip,将失去意义.
- 3.nginx不同安装方式, 则配置文件的目录是不同的, 要注意.
并行|并发|串行
- 1.并行: 真正的多任务, 进程数正好小于或等于 系统核心数; 通俗来说, 指服务器同一时刻真正在处理的请求数
- 2.并发: 假的多任务. 通俗来说, 指服务器同一时刻到来的请求数;
- 3.串行: 假的多任务, 通俗来说, 就想皇帝临幸妃子, 皇帝只有一个所以只能一个一个排队.
关于python web部署时候的一些思考点:
- 1.采用一套代码, 多个进程共用呢?还是采用多进程-多套代码(但是一个进程对应一套代码)的方式?
- 1.多进程: 父进程和子进程, 操作系统在管理的时候有一个机制: 读时共享, 写时拷贝(复制). 所以如果是同一套代码, 通过命令行传递不同的port等参数时, 本质上是发生了写操作, 会影响其他正在读此内容的子进程.
- 2.所以, 我们实际上经常采用的是 多进程-多套代码(但是一个进程对应一套代码)的方式
- 3.注意, 这里和 多线程之间共享全局变量是不同的.
- 2.官网说明摘录:
因为充分利用Linux的epoll工具和BSD的kqueue工具,是Tornado不依靠多进程/多线程而达到高性能的原因
- 3.为了充分利用多核CPU,并且为了减少同步代码中的阻塞影响,在部署Tornado的时候需要开启多个进程(最好为每个CPU核心开启一个进程)
- 4.supervisor 是python 提供的一个 不受兼容性影响的 OS进程管理工具.
- 3.总结:
虽然tornado给我们提供了一次开启多个进程的方法,但是由于:
- 每个子进程都会从父进程中复制一份IOLoop实例,如过在创建子进程前我们的代码动了IOLoop实例,那么会影响到每一个子进程,势必会干扰到子进程IOLoop的工作;
- 所有进程是由一个命令一次开启的,也就无法做到在不停服务的情况下更新代码;
- 所有进程共享同一个端口,想要分别单独监控每一个进程就很困难。
- 不建议使用这种多进程的方式,而是手动开启多个进程,并且绑定不同的端口。