首页 > 学院 > 开发设计 > 正文

pythonbottle使用多个端口(多个进程)提高并发

2019-11-14 17:10:44
字体:
来源:转载
供稿:网友

我的程序是用python结合bottle框架写的,但bottle自带wsgi原本只是单进程单线程运行模式(Bottle 默认运行在内置的 wsgiref 服务器上面。这个单线程的 HTTP 服务器在开发的时候特别有用,但其性能低下,在服务器负载不断增加的时候也许会是性能瓶颈, 一次只能响应一个请求)。为了提升程序的处理能力,首先要启用多线程,即在程序中使用gevent( 大多数服务器的线程池都限制了线程池中线程的数量,避免创建和切换线程的代价。尽管和进程 (fork)比起来,线程还是挺便宜的。但是也没便宜到可以接受为每一个请求创建一个线程。gevent 模块添加了 greenlet 的支持。 greenlet 和传统的线程类似,但其创建只需消耗很少的资源。基于 gevent 的服务器可以生成成千上万的 greenlet,为每个连接分配一个 greenlet 也毫无压力。阻塞greenlet,也不会影响到服务器接受新的请求。同时处理的连接数理论上是没有限制的。)。只需要在run中加上 server='gevent',如下:

1 import gevent2 from gevent import  monkey.patch_all()3       代码段……4 run(host='0.0.0.0', port=8080, server='gevent')

 

尽管使用了多线程模式,但这些线程都是跑在一个进程里,所以需要开启多个进程来进一步提升并发处理能力,因此在启用脚本的时候,在run(port=)里,端口号不能写死,应该使用变量来传递,如下代码(在脚本执行时,在需带一个参数,这个参数是大于1024的整数,否则报错停止脚本):

import gevent,sysfrom gevent import  monkey.patch_all()#获取端口号try:    portnum = int(sys.argv[1])except Exception,e:    PRint "请带上整数类型的端口号启动此程序"    logging.error("请带上整数类型的端口号启动此程序")    sys.exit(1)if portnum <= 1024:    print "端口号请大于1024!"    logging.error("端口号请大于1024!")    sys.exit(1)      代码段……run(host='0.0.0.0', port=portnum , server='gevent') 

 

执行方式如下(osyw.py是我python程序名):

python osyw.py 1124

 

如果纯靠手动操作这些,在生产上,很不方便,所以我写了个shell来管理,这个shell定义了多个端口号,然后去循环启用或停止python进程,脚本大概如下(是用httpd改写的):

 #!/bin/bash## osyw        Startup script for the osyw HTTP Server## chkconfig: - 88 18# description: osyw# processname: osyw# config: # config: /home/bottle/osyw/# pidfile: /var/run/osyw.pid#### BEGIN INIT INFO# Provides: osyw# Short-Description: start and stop osyw HTTP Server# Description: The osyw HTTP Server is an extensible server #  implementing the current HTTP standards.### END INIT INFO# Source function library.. /etc/rc.d/init.d/functions# Path to the apachectl script, server binary, and short-form for messages.port_list=(8811 8812 8813)     #设置了3个端口#pidfile='/var/run/osyw.pid'pro_path='/var/www/osyw/osyw.py'    #程序路径log_path='/var/www/osyw/log/access.log'    #访问日志路径RETVAL=0start() {        for i in ${port_list[*]}        do                p=`/usr/sbin/lsof -i :${i} |wc -l`                if [ ${p} -ge 2 ]                then                        action "osyw ${i} already exists !" /bin/false                else                        /usr/bin/python ${pro_path} ${i} &>> ${log_path}                RETVAL=$?                        if [ ${RETVAL} == 0 ]                        then                                action "osyw ${i} start ..." /bin/true                        else                                action "osyw ${i} start ..." /bin/false                        fi                fi        done    return $RETVAL}stop() {        for i in ${port_list[*]}        do                pidfile="/var/run/osyw_${i}.pid"                if [ -f ${pidfile} ]                then                        pid=`cat ${pidfile}`                        kill -9 ${pid}                        RETVAL=$?                        if [ ${RETVAL} == 0 ]                    then                    action  "osyw ${i} stop ..." /bin/true                    else                    action  "osyw ${i} stop ..." /bin/false                        fi                        rm -f ${pidfile}                else                        action  "osyw ${i} Has stopped !" /bin/false                fi        done}# See how we were called.case "$1" in  start)        start        ;;  stop)        stop        ;;  status)        status -p ${pidfile} 'osyw'        RETVAL=$?        ;;  restart)        stop        sleep 2        start        ;;  condrestart|try-restart)        if status -p ${pidfile} 'osyw' >&/dev/null; then                stop                start        fi        ;;  force-reload|reload)        reload        ;;  *)        echo $"Usage: $prog {start|stop|restart|condrestart|try-restart|force-reload|reload|status|fullstatus|graceful|help|configtest}"        RETVAL=2esacexit $RETVAL

 

效果图:

shell bottle

 

本人的代码是用svn管理的,所以上传代码后,SVN钩子会调用shell脚本来重启这些程序,以下是SVN钩子代码:

export LANG=en_US.UTF-8/usr/bin/svn update --username xxxx --passWord xxxxxxxx /var/bottle/bin/bash /etc/init.d/osyw restart

 

当然,为了结合shell,python程序里也要做一些处理,如自动把程序转为后台守护进程,然后把进程ID写入文件,以下是关键的python代码:

 

#定义PID路径  pid_path = '/var/run/osyw_%s.pid' % portnum      def daemonize():      """把本脚本转为守护进程"""      try:          pid=os.fork()          if pid>0:              sys.exit(0)      except Exception,e:          logging.error(e)          sys.exit(1)        os.chdir('/')      os.umask(0)      os.setsid()        try:          pid=os.fork()          if pid>0:              sys.exit(0)      except Exception,e:          logging.error(e)          sys.exit(1)        PID = str(os.getpid())      with open(pid_path,'w') as f:          f.write(PID)    其它代码段……    if __name__ == '__main__':      try:          from oscore import setting    #导入配置文件        if setting.status == 'online':    #如果配置中是线上的,则程序转入后台运行            daemonize()      except Exception:          pass        app = default_app()      app = sessionMiddleware(app, session_opts)      #sessionMiddleware是session插件    run(app=app,host='0.0.0.0', port=portnum,server='gevent')

 

最好,用nginx代理来负载这些端口,我nginx和python程序是安装在同一台服务器上的:

以上是nginx反向代理的部分代码:

 

upstream myweb {#ip_hash;server 192.168.1.240:8811 weight=4 max_fails=2 fail_timeout=30s;server 192.168.1.240:8812 weight=4 max_fails=2 fail_timeout=30s;server 192.168.1.240:8813 weight=4 max_fails=2 fail_timeout=30s;}server {    listen       80;        server_name  192.168.1.240;    location /        {        proxy_pass http://myweb;        proxy_set_header Host  $host;        proxy_set_header X-Forwarded-For  $remote_addr;        proxy_cache_key $host$uri$is_args$args;        }        access_log  off;        }

 

做完这些后,当访问80端口时,nginx就会平均轮洵分配到每个端口上去,实现了多进程,多线程的运行模式,更有效的提升了并发处理能力

 


发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表