web框架:nginx+tornado
为什么要在tornado服务器前,加一层nginx服务器?
1.负载均衡:将请求均衡的分给多个服务器tornado服务器,保证了业务的高可用性(nginx会给你分配服务器压力小的去访问)
2.静态资源处理:可以将CSS,HTML,JS,图片等静态资源存放在nginx服务器上,即使客户端不从浏览器读取缓存,也可以快速的从nginx代理服务器读取缓存,通过'Cache-Control':'s-maxage=20'控制
3.安全性:nginx服务器对访问权限等进行过滤;原始服务器的防火墙只允许特定代理服务器(如nginx服务器)进出,起到保护和隐藏原始资源服务器的作用;
一、框架流程:
使用Nginx是因为一些单进程服务无法利用多核CPU,同时也会有单机的限制,所以通常会在多个服务器启动多个进程实例,使用Nginx在前端作为反向代理服务器来分发web请求,同时负载均衡。Nginx是C写的,有更好的并发性,可配置性,对静态文件也有更好的支持。当然这是Nginx的常用情景,其实也可以直接使用Nginx来构建web应用,可以参考OpenResty项目
Nginx的确是多进程的,但其实我想表达的是说Tornado这样的单进程服务无法利用多核CPU(全面点说,像Tornado自己也有multiple processes模式的,但它的进程管理的功能很弱,通常还是自己启动N个进程,而且不能拓展到多机
如果咬文嚼字的话,host静态文件的被称为web server,比如nginx和apache。动态的应该称为application server才对,比如tornado和tomcat
nginx常用做静态内容服务和代理服务器(不是你FQ那个代理),直面外来请求转发给后面的应用服务tomcat,django什么的),tomcat更多用来做做一个应用容器,让java web app跑在里面的东西,对应同级别的有jboss,jetty等东西。但是事无绝对,nginx也可以通过模块开发来提供应用功能,tomcat也可以直接提供http服务,通常用在内网和不需要流控等小型服务的场景。
apache用的越来越少了,大体上和nginx功能重合的更多
apache和nginx可以归为一类,属于静态页面服务器。tomcat属于Java Servlet容器,用来生成动态页面的。
apache属于比较老的静态页面服务器,比较多现有插件,包括代理、SSL支持、动态编程语言处理等。
nginx和apache差不多,都有插件支持动态编程语言处理等、但nginx的IO模型比apache更适合跑代理。所以一般都作为前端缓冲代理。
tomcat就是Java Servlet容器,主要用来跑Java的Web功能,当然也提供一个简单静态页面转换。
严格的来说,Apache/Nginx 应该叫做「HTTP Server」;而 Tomcat 则是一个「Application Server」,或者更准确的来说,是一个「Servlet/JSP」应用的容器(Ruby/Python 等其他语言开发的应用也无法直接运行在 Tomcat 上)。
一个 HTTP Server 关心的是 HTTP 协议层面的传输和访问控制,所以在 Apache/Nginx 上你可以看到代理、负载均衡等功能。客户端通过 HTTP Server 访问服务器上存储的资源(HTML 文件、图片文件等等)。通过 CGI 技术,也可以将处理过的内容通过 HTTP Server 分发,但是一个 HTTP Server 始终只是把服务器上的文件如实的通过 HTTP 协议传输给客户端。
而应用服务器,则是一个应用执行的容器。它首先需要支持开发语言的 Runtime(对于 Tomcat 来说,就是 Java),保证应用能够在应用服务器上正常运行。其次,需要支持应用相关的规范,例如类库、安全方面的特性。对于 Tomcat 来说,就是需要提供 JSP/Sevlet 运行需要的标准类库、Interface 等。为了方便,应用服务器往往也会集成 HTTP Server 的功能,但是不如专业的 HTTP Server 那么强大,所以应用服务器往往是运行在 HTTP Server 的背后,执行应用,将动态的内容转化为静态的内容之后,通过 HTTP Server 分发到客户端。
一般的运用场景下,apache和nginx在负载均衡里是前端服务器,用来处理请求的转发(反向代理等);绝大部分时候他们本身并不会运行项目。tomcat和jetty,WebLogic是后端服务器,是直接用来运行项目的容器。
简单来说就是你发出一个请求,先经过apache或nginx,他们会合理地把请求分配到后台比较不忙的tomcat或jetty。tomcat或jetty会把请求处理好返回给apache或nginx,然后a或n会把最终的请求结果告诉你。当然,如果是一些静态的数据,a和n就可以直接返回给你了。
二、问题:
nginx+supervisor+tornado:
因为Tornado是异步的网络框架,性能够好,可以直接放在最外层,但是为了避免阻塞问题,会开多个进程,然后使用 Nginx 做反向代理实现负载均衡。具体可以看这篇文章 Introduction to Tornado 中文翻译。
那么这里就涉及到要开多个Tornado进程的问题,使用Supervisor来做这件事是最简单的。Supervisor 的使用方法可以看这篇文章 Python 进程管理工具 Supervisor 使用教程
另外,如果你需要部署Django或者 Flask,则推荐 Nginx+Gunicorn+Supervisor
Nginx放在最外层,然后使用Supervisor做进程管理,使用Gunicorn启动Django或者Flask,相较于uwsgi 的方法,简单很多,而且Gunicorn可以让你使用Gevent和Tornado来为你的后端实现异步访问,性能直接飙升。
Tornado异步非阻塞的I/O模型的确让人耳目一新,Tornado的优势主要在于对大量Comet长轮询连接的维护上。这也是FriendFeed开发Tornado的原因-----因为FriendFeed需要实时更新Timeline,而Comet又是目前最好,最流行的方法。由于知乎也有大量长轮询连接需要维护,所以选择Tornado也就在情理之中了。
但是我们也要看到,Tornado不是万金油,由于Tornado的WEB服务器为单线程,一个Request如果阻塞了I/O,那么这个进程将一直挂起,既无法接受新的Request,也无法Finish正在阻塞的其它Request。虽然可以Spawn多个Tornado进程,但是进程这种重量级的东西,Spawn太多会消耗大量的内存资源。这种感觉很像PHP的FastCGI进程那种味道。所以如果是会阻塞I/O的Request一般都是利用Tornado内置的异步HTTP Client交给其它动态后端来做。
所以Tornado在生产中一般前面都要包一层nginx做反向代理,用nginx来做静态文件等大数据量的I/O操作。Tornado的I/O时间实在是太金贵了,在这上面耗不起。
@刘连响 提到的那个测试(http://kb.cnblogs.com/a/1591500/)在实际应用中并没有什么价值。因为实际应用中必然有逻辑处理,逻辑处理必然要阻塞I/O,这会使Tornado的性能大幅下降。测试可在网页代码
self.write("hello world")
前加一句
time.sleep(0.01)
,假设每个Request阻塞I/O 10毫秒,再测试一下性能。 至于你提到的Tornado文档少的问题,我觉得你可以抽空阅读一下Tornado的代码,毕竟是个轻量级框架,代码不多,但是注释却很详细,很容易看懂。请记住,代码永远是最好的文档! 各位,别忘了,只有原生异步才能发挥出Tornado的最大优势,比如Tornado自带的mysql库就不是异步的(实际上这个库就是MySQLdb的包装),一旦SQL调用阻塞,整个Tornado进程也会阻塞,这种情况下gen也是帮不了你的。gen只是为了让异步调用更简单,没办法把一个同步函数变为异步函数。
根据官网的推荐部署方式,我们还是采用Nginx通过upstream来反向代理到N个Tornado的服务器实例上的部署方式。so
Setp1:安装supervisord
由于Tornado并没有自身提供Daemon的能力,所以我们需要用一个服务管理工具来管理Tornado的进程,supervisord是用Python实现的一款非常实用的进程管理工具。可以很方便的管理N过进程,且支持进程分组。Supervisord可以通过sudo easy_install supervisor安装,当然也可以通过Supervisord官网下载后setup.py install安装。
Step2: 给Django的站点增加一个Tornado的服务器文件(比如serv.py)
创建一个文件Serv.py在Django站点的根目录(Django 1.4中应该放到和urls.py同一级目录),内容如下:
import os
import sys
from tornado.options import options, define, parse_command_line
import django.core.handlers.wsgi
import tornado.httpserver
import tornado.ioloop
import tornado.web
import tornado.wsgi
_HERE = os.path.dirname(os.path.abspath(file))
sys.path.append(_HERE)
sys.path.append(os.path.join(_HERE, '..'))
sys.path.append(os.path.join(_HERE, '../contrib'))
os.environ['DJANGO_SETTINGS_MODULE'] = "settings"
def main(port):
wsgi_app = tornado.wsgi.WSGIContainer(
django.core.handlers.wsgi.WSGIHandler())
tornado_app = tornado.web.Application(
[('.*', tornado.web.FallbackHandler, dict(fallback=wsgi_app)),
])
server = tornado.httpserver.HTTPServer(tornado_app)
server.listen(port)
tornado.ioloop.IOLoop.instance().start()
if name == 'main':
main(int(sys.argv[1]))
我这里通过第一个参数来指定Tornado服务监听的端口。这样比较灵活,这点我们在后面的步骤会用到。这个时候我们可以通过
python Serv.py 8000
这个命令来启动服务器
Step3: 配置Supervisord
第一步安装的Supervisord还没有配置,所以我们需要先创建一个配置文件的样板。在root权限下执行
echo_supervisord_conf > /etc/supervisord.conf
这个时候在/etc/创建了配置文件,用vim打开这个文件,在配置文件的屁股后面加上以下这一段
[program:web]
command=python /var/www/site/Serv.py 80%(process_num)02d
process_name=%(program_name)s_%(process_num)02d
umask=022
startsecs=0
stopwaitsecs=0
redirect_stderr=true
stdout_logfile=/tmp/codoon.log
numprocs=4
numprocs_start=1
这个配置会启动4个Tornado的服务进程分别监听 8001,8002,8003,8004 这四个端口
command这一行是要执行的命令,这里是用 python /var/www/site/Serv.py 端口号来启动Tornado的服务进程 80%(process_num)02d 的用途是通过进程编号来生成端口号。下面的process_name这个参数也会用到。这里要指定的文件名就是上一步我们创建那个Serv.py文件
process_name是进程的名字,由于这里要启动4个进程,所以要用process_num来区分
umask是程序执行的权限参数
startsecs这个参数是程序启动的等待时间
stopwaitsecs这个参数是程序停止的等待时间
redirect_stderr这个参数将错误流重定向到std的流输出,这样可以省去一个日志文件的配置,当然也可以不用这个参数分开配置日志文件
stdout_logfile 这个参数是STD流输出日志文件的路径,Tornado会输出所有的请求和错误信息,通过这个可以统一做日志处理,分隔什么的,在程序里就只需要print到std流就行了。
numprocs 这个参数指定了进程的数量,这里是4,表明要启动4个Tornado进程
numprocs_start 这个参数指定了进程号的起始编号,这里是1,这样前面的command和process_name里的%(process_num)02d部分就会在执行的时候被替换为01~05的字符串
配置修改完成后:wq保存退出,执行:
supervisorctl reload
重新加载配置后,这些进程就启动起来了
Step4:修改配置Nginx
首先找到在vhost目录里你的站点配置文件,打开后,在头上增加upstream的内容
upstream frontends {
server 127.0.0.1:8001;
server 127.0.0.1:8002;
server 127.0.0.1:8003;
server 127.0.0.1:8004;
}
然后在Server配置节里找到
location / { 这个配置节
以前是用的FastCGI,所以里面的配置可能是这样子的
host and port to fastcgi server
fastcgi_pass 127.0.0.1:8081;
fastcgi_param PATH_INFO $fastcgi_script_name;
fastcgi_param REQUEST_METHOD $request_method;
fastcgi_param QUERY_STRING $query_string;
fastcgi_param CONTENT_TYPE $content_type;
fastcgi_param CONTENT_LENGTH $content_length;
fastcgi_pass_header Authorization;
fastcgi_param REMOTE_ADDR $remote_addr;
fastcgi_param SERVER_PROTOCOL $server_protocol;
fastcgi_param SERVER_PORT $server_port;
fastcgi_param SERVER_NAME $server_name;
fastcgi_intercept_errors off;
把这些统统删掉,变成了这样
location / {
}
在{}中加入upstream的配置,变成如下样子
location / {
proxy_pass_header Server;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Scheme $scheme;
proxy_pass http://frontends;
proxy_next_upstream error;
}
保存配置文件后执行 让nginx重启的指令 nginx -s reload(注意 nginx文件在不同发行版中位置有差别)
然后你就能够通过域名看到你的网站了,试试是不是快多了
注意:生产系统下开启多少个Tornado进程比较好呢,这个见仁见智了,据我压力测试的结果看来,用CPU核数*2的数量最好,再多 就浪费了没有提升(为什么乘2?因为有种CPU上的技术叫超线程)。我的VPS上用的4个进程。如果是8核IntelCPU要挖尽CPU潜能的话需要开16个进程
类似于Django,express.js这样的框架本身就具有处理后端的能力,为什么在开发的环境中一般都要部署Apache,Nginx这样的服务器程序呢?
在不同的网络环境里面,对网络的需求,不仅仅是能够进行网络通信,还需要很多不同的需求,如果不将服务器隔离开来,那么会相当不灵活,处理这些需求,会很费事,甚至无法完成。下面是一些需求:
高性能:nginx和apache通过配置就可以大大的提高不同场景下的性能,对高并发更加是优化得很彻底了。
安全:对ssl的支持,证书的支持,其他服务器很可能没实现或者实现不统一。
代理分发:统一了入口点,避免暴露内部复杂的环境。
Nginx是一个高效的Web服务器及代理服务器,Tornado是一个基于epoll的异步Web开发框架,通常使用Nginx做为Web服务器时,都会以FastCGI模式,而我们从开发、调试、运维的角度考虑,使用了反向代理的模式,同时Nginx可以做一些特殊业务和负载均衡的处理。
其实反向代理模式很简单,Nginx监听在80端口,做为Web服务端口,而Tornado的Web服务进程监听在808*的内部端口(可以启动多个进程),使用supervisor对Nginx、Tornado服务进程进行统一的管理。
首先看supervisor的配置:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
|
# supervisor自己的配置及日志切割等[supervisord]logfile = /opt/logs/supervisord.loglogfile_maxbytes = 200MBlogfile_backups=10loglevel = warnpidfile = /opt/logs/supervisord.pidnodaemon = falseminfds = 1024minprocs = 200umask = 022identifier = supervisordirectory = %(here)snocleanup = truestrip_ansi = false[unix_http_server]file = /opt/logs/supervisord.sock[supervisorctl]serverurl = unix:///opt/logs/supervisord.sock[rpcinterface:supervisor]supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface# 服务进程的启动及多端口[program:MyWeb]command = /opt/bin/python %(here)s/server.py --port=808%(process_num)01dprocess_name = 808%(process_num)01dnumprocs = 4numprocs_start = 1autostart = trueautorestart = trueredirect_stderr = truestdout_logfile = /opt/logs/stdout.logstderr_logfile = /opt/logs/stdout.log# Nginx的管理配置[program:Nginx]command = /opt/sbin/nginx -c %(here)s/nginx.confprocess_name = Nginxnumprocs = 1autostart = trueautorestart = trueredirect_stderr = truestdout_logfile = /opt/logs/nginx_stdout.logstderr_logfile = /opt/logs/nginx_stdout.log |
启动脚本(可以放到start.sh中):
|
1
|
/opt/bin/supervisord /opt/conf/supervisor.conf |
重启脚本(可以放到restart.sh中)
|
1
2
3
4
5
6
7
8
|
#逐个启动MyWeb每个端口进程,不中断服务for i in "8081 8082 8083 8084":do /opt/bin/supervisorctl /opt/conf/supervisor.conf restart MyWeb:$i; done#重新加载nginx的配置/opt/sbin/nginx /opt/conf/nginx.conf -s reload; |
Nginx的部分配置(启动4个服务进程,监听在80端口,并反向代理负载到Tornado的808*端口上):
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
worker_processes 4;daemon off; #nginx不能以daemon模式启动user nobody;http { upstream myweb { server 127.0.0.1:8081; server 127.0.0.1:8082; server 127.0.0.1:8083; server 127.0.0.1:8084; } server { listen 80; server_name localhost; location / { proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $remote_addr; proxy_set_header X-Scheme $scheme; proxy_read_timeout 300s; proxy_pass http://myweb; } }} |
现在Nginx已经反向代理到Tornado的服务进程监听的端口了,那么MyWeb的服务进程如何构建、并如何优雅的重启呢,略过其他代码,介绍一下主进程采用信号停止服务,并重新启动的方法。主进程的启动参数会指定此进程监听的端口,这样supervisor检测到服务进程结束后,会自动启动对应的服务进程。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
import signalimport tornado.ioloopimport tornado.httpserverhttp_server = Nonedef sig_handler(sig, frame): """信号处理函数 """ tornado.ioloop.IOLoop.instance().add_callback(shutdown)def shutdown(): """进程关闭处理 """ # 停止接受Client连接 global http_server http_server.stop() io_loop = tornado.ioloop.IOLoop.instance() deadline = time.time() + 10 #设置最长强制结束时间 def stop_loop(): now = time.time() if now < deadline: io_loop.add_timeout(now + 1, stop_loop) else: io_loop.stop() stop_loop()if __name__ == '__main__': # 等待supervisor发送进程结束信号 signal.signal(signal.SIGTERM, sig_handler) signal.signal(signal.SIGINT, sig_handler) app = Application() http_server = tornado.httpserver.HTTPServer(app, xheaders=True) http_server.listen(tornado.options.options.port) tornado.ioloop.IOLoop.instance().start() |
posted on 2018-11-02 14:29 myworldworld 阅读(2100) 评论(0) 收藏 举报
浙公网安备 33010602011771号