多套方案来提高python web框架的并发处理能力
Python常见部署方法有 :
fcgi :用spawn-fcgi或者框架自带的工具对各个project分别生成监听进程,然后和http 服务互动wsgi :利用http服务的mod_wsgi模块来跑各个project(Web应用程序或框架简单而通用的Web服务器 之间的接口)。uWSGI 是一款像php-cgi一样监听同一端口,进行统一管理和负载平衡的工具,uWSGI,既不用wsgi协议也不用fcgi协议,而是自创了一个uwsgi的协议,据说该协议大约是fcgi协议的10倍那么快。 |
其实 WSGI 是分成 server 和 framework (即 application) 两部分 (当然还有 middleware)。严格说 WSGI 只是一个协议, 规范 server 和 framework 之间连接的接口。
WSGI server 把服务器功能以 WSGI 接口暴露出来。比如 mod_wsgi 是一种 server, 把 apache 的功能以 WSGI 接口的形式提供出来。
|
1
2
3
4
5
|
WSGI framework 就是我们经常提到的 Django 这种框架。不过需要注意的是, 很少有单纯的 WSGI framework , 基于 WSGI 的框架往往都自带 WSGI server。比如 Django、CherryPy 都自带 WSGI server 主要是测试用途, 发布时则使用生产环境的 WSGI server。而有些 WSGI 下的框架比如 pylons、bfg 等, 自己不实现 WSGI server。使用 paste 作为 WSGI server。Paste 是流行的 WSGI server, 带有很多中间件。还有 flup 也是一个提供中间件的库。搞清除 WSGI server 和 application, 中间件自然就清楚了。除了 session、cache 之类的应用, 前段时间看到一个 bfg 下的中间件专门用于给网站换肤的 (skin) 。中间件可以想到的用法还很多。这里再补充一下, 像 django 这样的框架如何以 fastcgi 的方式跑在 apache 上的。这要用到 flup.fcgi 或者 fastcgi.py (eurasia 中也设计了一个 fastcgi.py 的实现) 这些工具, 它们就是把 fastcgi 协议转换成 WSGI 接口 (把 fastcgi 变成一个 WSGI server) 供框架接入。整个架构是这样的: django -> fcgi2wsgiserver -> mod_fcgi -> apache 。虽然我不是 WSGI 的粉丝, 但是不可否认 WSGI 对 python web 的意义重大。有意自己设计 web 框架, 又不想做 socket 层和 http 报文解析的同学, 可以从 WSGI 开始设计自己的框架。在 python 圈子里有个共识, 自己随手搞个 web 框架跟喝口水一样自然, 非常方便。或许每个 python 玩家都会经历一个倒腾框架的 |
uWSGI的主要特点如下:
超快的性能。
低内存占用(实测为apache2的mod_wsgi的一半左右)。
多app管理。
详尽的日志功能(可以用来分析app性能和瓶颈)。
高度可定制(内存大小限制,服务一定次数后重启等)。
uwsgi的官方文档:
http://projects.unbit.it/uwsgi/wiki/Doc
nginx.conf
|
|
location / { include uwsgi_params uwsgi_pass 127.0.0.1:9090} |
启动app
|
|
uwsgi -s :9090 -w myapp |
uwsgi的调优参数~
|
|
uwsgi的参数以上是单个project的最简单化部署,uwsgi还是有很多令人称赞的功能的,例如:并发4个线程: uwsgi -s :9090 -w myapp -p 4主控制线程+4个线程: uwsgi -s :9090 -w myapp -M -p 4执行超过30秒的client直接放弃: uwsgi -s :9090 -w myapp -M -p 4 -t 30限制内存空间128M: uwsgi -s :9090 -w myapp -M -p 4 -t 30 --limit-as 128服务超过10000个req自动respawn: uwsgi -s :9090 -w myapp -M -p 4 -t 30 --limit-as 128 -R 10000后台运行等: uwsgi -s :9090 -w myapp -M -p 4 -t 30 --limit-as 128 -R 10000 -d uwsgi.log |
为了让多个站点共享一个uwsgi服务,必须把uwsgi运行成虚拟站点:去掉“-w myapp”加上”–vhost”:
uwsgi -s :9090 -M -p 4 -t 30 --limit-as 128 -R 10000 -d uwsgi.log --vhost
然后必须配置virtualenv,virtualenv是Python的一个很有用的虚拟环境工具,这样安装:
最后配置nginx,注意每个站点必须单独占用一个server,同一server不同location定向到不同的应用不知为何总是失败,估计也 算是一个bug。
server { listen 80; server_name app1.mydomain.com; location / { include uwsgi_params; uwsgi_pass 127.0.0.1:9090; uwsgi_param UWSGI_PYHOME /var/www/myenv; uwsgi_param UWSGI_SCRIPT myapp1; uwsgi_param UWSGI_CHDIR /var/www/myappdir1; }}server { listen 80; server_name app2.mydomain.com; location / { include uwsgi_params; uwsgi_pass 127.0.0.1:9090; uwsgi_param UWSGI_PYHOME /var/www/myenv; uwsgi_param UWSGI_SCRIPT myapp2; uwsgi_param UWSGI_CHDIR /var/www/myappdir2; }} |
这样,重启nginx服务,两个站点就可以共用一个uwsgi服务了。
再来搞下 fastcgi的方式
|
|
location / { 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_param GATEWAY_INTERFACE CGI/1.1; fastcgi_param SERVER_SOFTWARE nginx/$nginx_version; fastcgi_param REMOTE_ADDR $remote_addr; fastcgi_param REMOTE_PORT $remote_port; fastcgi_param SERVER_ADDR $server_addr; fastcgi_param SERVER_PORT $server_port; fastcgi_param SERVER_NAME $server_name; fastcgi_param SERVER_PROTOCOL $server_protocol; fastcgi_param SCRIPT_FILENAME $fastcgi_script_name; fastcgi_param PATH_INFO $fastcgi_script_name; fastcgi_pass 127.0.0.1:9002;} |
|
|
location /static/ { root /path/to/www; if (-f $request_filename) { rewrite ^/static/(.*)$ /static/$1 break; } } |
启动一个fastcgi的进程
|
1
|
spawn-fcgi -d /path/to/www -f /path/to/www/index.py -a 127.0.0.1 -p 9002 |
用web.py写的一个小demo测试
|
|
#!/usr/bin/env python# -*- coding: utf-8 -*-import weburls = ("/.*", "hello")app = web.application(urls, globals())class hello: def GET(self): return 'Hello, world!'if __name__ == "__main__": web.wsgi.runwsgi = lambda func, addr=None: web.wsgi.runfcgi(func, addr) app.run() |
启动nginx
|
|
nginx |
这样就ok了~
下面开始介绍下 我一般用的方法:
前端nginx用负责负载分发:
部署的时候采用了单IP多端口方式,服务器有4个核心,决定开4个端口对应,分别是8885~8888,修改
upstream backend { server 127.0.0.1:8888; server 127.0.0.1:8887; server 127.0.0.1:8886; server 127.0.0.1:8885;} server{ listen 80; server_name message.test.com; keepalive_timeout 65; # proxy_read_timeout 2000; # sendfile on; tcp_nopush on; tcp_nodelay on; location / { proxy_pass_header Server; proxy_set_header Host $http_host; proxy_redirect off; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Scheme $scheme; proxy_pass http://backend; }} |
然后运行四个python程序,端口为咱们配置好的端口
我这里用tornado写了一个执行系统程序的例子:
|
|
import subprocessimport tornado.ioloopimport timeimport fcntlimport functoolsimport osclass GenericSubprocess (object): def __init__ ( self, timeout=-1, **popen_args ): self.args = dict() self.args["stdout"] = subprocess.PIPE self.args["stderr"] = subprocess.PIPE self.args["close_fds"] = True self.args.update(popen_args) self.ioloop = None self.expiration = None self.pipe = None self.timeout = timeout self.streams = [] self.has_timed_out = False def start(self): """Spawn the task. Throws RuntimeError if the task was already started.""" if not self.pipe is None: raise RuntimeError("Cannot start task twice") self.ioloop = tornado.ioloop.IOLoop.instance() if self.timeout > 0: self.expiration = self.ioloop.add_timeout( time.time() + self.timeout, self.on_timeout ) self.pipe = subprocess.Popen(**self.args) self.streams = [ (self.pipe.stdout.fileno(), []), (self.pipe.stderr.fileno(), []) ] for fd, d in self.streams: flags = fcntl.fcntl(fd, fcntl.F_GETFL)| os.O_NDELAY fcntl.fcntl( fd, fcntl.F_SETFL, flags) self.ioloop.add_handler( fd, self.stat, self.ioloop.READ|self.ioloop.ERROR) def on_timeout(self): self.has_timed_out = True self.cancel() def cancel (self ) : """Cancel task execution Sends SIGKILL to the child process.""" try: self.pipe.kill() except: pass def stat( self, *args ): '''Check process completion and consume pending I/O data''' self.pipe.poll() if not self.pipe.returncode is None: '''cleanup handlers and timeouts''' if not self.expiration is None: self.ioloop.remove_timeout(self.expiration) for fd, dest in self.streams: self.ioloop.remove_handler(fd) '''schedulle callback (first try to read all pending data)''' self.ioloop.add_callback(self.on_finish) for fd, dest in self.streams: while True: try: data = os.read(fd, 4096) if len(data) == 0: break dest.extend([data]) except: break @property def stdout(self): return self.get_output(0) @property def stderr(self): return self.get_output(1) @property def status(self): return self.pipe.returncode def get_output(self, index ): return "".join(self.streams[index][1]) def on_finish(self): raise NotImplemented()class Subprocess (GenericSubprocess): """Create new instance Arguments: callback: method to be called after completion. This method should take 3 arguments: statuscode(int), stdout(str), stderr(str), has_timed_out(boolean) timeout: wall time allocated for the process to complete. After this expires Task.cancelis called. A negative timeout value means no limit is set The task is not started until start is called. The process will then be spawned using subprocess.Popen(**popen_args). The stdout and stderr are always set to subprocess.PIPE. """ def __init__ ( self, callback, *args, **kwargs): """Create new instance Arguments: callback: method to be called after completion. This method should take 3 arguments: statuscode(int), stdout(str), stderr(str), has_timed_out(boolean) timeout: wall time allocated for the process to complete. After this expires Task.cancel is called. A negative timeout value means no limit is set The task is not started until start is called. The process will then be spawned using subprocess.Popen(**popen_args). The stdout and stderr are always set to subprocess.PIPE. """ self.callback = callback self.done_callback = False GenericSubprocess.__init__(self, *args, **kwargs) def on_finish(self): if not self.done_callback: self.done_callback = True '''prevent calling callback twice''' self.ioloop.add_callback(functools.partial(self.callback, self.status, self.stdout, self.stderr, self.has_timed_out))if __name__ == "__main__": ioloop = tornado.ioloop.IOLoop.instance() def print_timeout( status, stdout, stderr, has_timed_out) : assert(status!=0) assert(has_timed_out) print "OK status:", repr(status), "stdout:", repr(stdout), "stderr:", repr(stderr),"timeout:", repr(has_timed_out) def print_ok( status, stdout, stderr, has_timed_out) : assert(status==0) assert(not has_timed_out) print "OK status:", repr(status), "stdout:", repr(stdout), "stderr:", repr(stderr),"timeout:", repr(has_timed_out) def print_error( status, stdout, stderr, has_timed_out): assert(status!=0) assert(not has_timed_out) print "OK status:", repr(status), "stdout:", repr(stdout), "stderr:", repr(stderr),"timeout:", repr(has_timed_out) def stop_test(): ioloop.stop() t1 = Subprocess( print_timeout, timeout=3, args=[ "sleep", "5" ] ) t2 = Subprocess( print_ok, timeout=3, args=[ "sleep", "1" ] ) t3 = Subprocess( print_ok, timeout=3, args=[ "sleepdsdasdas", "1" ] ) t4 = Subprocess( print_error, timeout=3, args=[ "cat", "/etc/sdfsdfsdfsdfsdfsdfsdf" ] ) t1.start() t2.start() try: t3.start() assert(false) except: print "OK" t4.start() ioloop.add_timeout(time.time() + 10, stop_test) ioloop.start() |
多套方案来提高python web框架的并发处理能力的更多相关文章
- Django,Flask,Tornado三大框架对比,Python几种主流框架,13个Python web框架比较,2018年Python web五大主流框架
Django 与 Tornado 各自的优缺点Django优点: 大和全(重量级框架)自带orm,template,view 需要的功能也可以去找第三方的app注重高效开发全自动化的管理后台(只需要使 ...
- 浅谈Python web框架
一.Python web框架 Web Framework,Ruby的世界Rails一统江湖,而Python则是一个百花齐放的世界,各种micro-framework.framework不可胜数,不完全 ...
- python web框架介绍对比
Django Python框架虽然说是百花齐放,但仍然有那么一家是最大的,它就是Django.要说Django是Python框架里最好的,有人同意也有人 坚决反对,但说Django的文档最完善.市场占 ...
- python三大web框架Django,Flask,Flask,Python几种主流框架,13个Python web框架比较,2018年Python web五大主流框架
Python几种主流框架 从GitHub中整理出的15个最受欢迎的Python开源框架.这些框架包括事件I/O,OLAP,Web开发,高性能网络通信,测试,爬虫等. Django: Python We ...
- 2018年要学习的10大Python Web框架
通过为开发人员提供应用程序开发结构,框架使开发人员的生活更轻松.他们自动执行通用解决方案,缩短开发时间,并允许开发人员更多地关注应用程序逻辑而不是常规元素. 在本文中,我们分享了我们自己的前十大Pyt ...
- 一步一步理解 python web 框架,才不会从入门到放弃
要想清楚地理解 python web 框架,首先要清楚浏览器访问服务器的过程. 用户通过浏览器浏览网站的过程: 用户浏览器(socket客户端) 3. 客户端往服务端发消息 6. 客户端接收消息 7. ...
- 自定义python web框架
-- Bootstrap http://www.bootcss.com/ -- Font Awesome http://fontawesome.io/ -- bxslider http://bxsli ...
- 微型 Python Web 框架: Bottle
微型 Python Web 框架: Bottle 在 19/09/11 07:04 PM 由 COSTONY 发表 Bottle 是一个非常小巧但高效的微型 Python Web 框架,它被设计为仅仅 ...
- Python Web框架Tornado的异步处理代码演示样例
1. What is Tornado Tornado是一个轻量级但高性能的Python web框架,与还有一个流行的Python web框架Django相比.tornado不提供操作数据库的ORM接口 ...
随机推荐
- 开启session
在index.php中开启 session_start();
- 微服务深入浅出(9)-- Nginx
Nginx ("engine x") 是一个高性能的HTTP和反向代理服务器,处理请求是异步非阻塞的,多个连接(万级别)可以对应一个进程.而Apache是同步多进程模型,一个连接对 ...
- Python基础入门(一)
1.在线教程 2.Python下载地址,安装步骤,就是next.next... 3.配置环境变量(win8) 电脑 --> 属性 --> 高级系统设置 --> 环境变量,找到系统变量 ...
- nginx+tomat https ssl 部署 完美解决方案
关于nginx+tomcat https的部署之前网上一直有2种说法: 1.nginx和tomcat都要部署ssl证书 2.nginx部署ssl证书,tomcat增加ssl支持 在实际的部署过程中ng ...
- 20155303 2016-2017-2 《Java程序设计》第四周学习总结
20155303 2016-2017-2 <Java程序设计>第四周学习总结 教材学习内容总结 第六章 继承与多态 6.1 何谓继承 继承避免多个类间重复定义共同行为,使用关键字exten ...
- Python标准库笔记(11) — Operator模块
Operator--标准功能性操作符接口. 代码中使用迭代器时,有时必须要为一个简单表达式创建函数.有些情况这些函数可以用一个lambda函数实现,但是对于某些操作,根本没必要去写一个新的函数.因此o ...
- 查看linux服务器内存信息
查看服务器内存信息 dmidecode|grep -P -A5 "Memory\s+Device"|grep Size [root@localhost home]# dmideco ...
- redis学习笔记之redis简介
redis简介 Redis是一个开源的,高性能的,基于键值对的缓存与存储系统,通过设置各种键值数据类型来适应不同场景下的缓存与存储需求.同事redis的诸多高层级功能使其可以胜任消息队列,任务队列等不 ...
- shell脚本练习题->1
猜随机数的大小 描述: 写一个猜数字脚本,当用户输入的数字和预设数字(随机生成一个0-100的数字)一样时,直接退出,否则让用户一直输入:并且提示用户输入的数字比预设数字大或者小 分析: 1:随机数字 ...
- MySQL 四种链接
1.内联接 INNER JOIN(典型的联接运算,使用像 = 或 <> 之类的比较运算符).包括相等联接和自然联接. 内联接使用比较运算符根据每个表共有的列的值匹配两个表中的行. ...