高并发异步uwsgi+web.py+gevent

本文探讨了使用web.py框架的原因及其与Flask、Bottle的性能对比,介绍了WSGI协议的基本概念,并深入分析了uWSGI的工作原理与gevent在处理高并发请求时的优势及注意事项。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

高并发异步uwsgi+web.py+gevent

为什么用web.py?

python的web框架有很多,比如webpy、flask、bottle等,但是为什么我们选了webpy呢?想了好久,未果,硬要给解释,我想可能原因有两个:第一个是兄弟项目组用webpy,被我们组拿来主义,直接用了;第二个是我可能当时不知道有其他框架,因为刚工作,知识面有限。但是不管怎么样,webpy还是好用的,所有API的URL和class在一个文件中进行映射,可以很方便地查找某个class是为了哪个API服务的。(webpy的其中一个作者是Aaron Swartz,这是个牛掰的小伙,英年早逝)

这里对webpy、flask、bottle性能进行了测试,测试结果详见:webpy/flask/bottle性能测试

wsgi,这是python开发中经常遇到词(以前只管用了,趁写博客之际,好好学习下细节)。

wsgi协议

WSGI的官方文档参考http://www.python.org/dev/peps/pep-3333/。WSGI是the Python Web Server Interface,它的作用是在协议之间进行转换。WSGI是一个桥梁,一边是web服务器(web server),一边是用户的应用(wsgi app)。但是这个桥梁功能非常简单,有时候还需要别的桥(wsgi middleware)进行帮忙处理,下图说明了wsgi这个桥梁的关系。

一个简单的WSGI应用:

?
1
2
3
4
5
6
def simple_app(environ, start_response):
     """Simplest possible application object"""
     status = '200 OK'
     response_headers = [( 'Content-type' , 'text/plain' )]
     start_response(status, response_headers)
     return [HELLO_WORLD]

这个是最简单的WSGI应用,那么两个参数environ,start_response是什么?

evniron是一系列环境变量,参考https://www.python.org/dev/peps/pep-3333/#id24,用于表示HTTP请求信息。(为了让理解更具体一点,下表给出一个例子,这是uWSGI传递给wsgi app的值)

?
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
{
     'wsgi.multiprocess' : True,
     'SCRIPT_NAME' : '' ,
     'REQUEST_METHOD' : 'GET' ,
     'UWSGI_ROUTER' : 'http' ,
     'SERVER_PROTOCOL' : 'HTTP/1.1' ,
     'QUERY_STRING' : '' ,
     'x-wsgiorg.fdevent.readable' : <built-infunctionuwsgi_eventfd_read>,
     'HTTP_USER_AGENT' : 'curl/7.19.7(x86_64-unknown-linux-gnu)libcurl/7.19.7NSS/3.12.7.0zlib/1.2.3libidn/1.18libssh2/1.2.2' ,
     'SERVER_NAME' : 'localhost.localdomain' ,
     'REMOTE_ADDR' : '127.0.0.1' ,
     'wsgi.url_scheme' : 'http' ,
     'SERVER_PORT' : '7012' ,
     'uwsgi.node' : 'localhost.localdomain' ,
     'uwsgi.core' : 1023,
     'x-wsgiorg.fdevent.timeout' : None,
     'wsgi.input' : <uwsgi._Inputobjectat0x7f287dc81e88>,
     'HTTP_HOST' : '127.0.0.1: 7012' ,
     'wsgi.multithread' : False,
     'REQUEST_URI' : '/index.html' ,
     'HTTP_ACCEPT' : '*/*' ,
     'wsgi.version' : (1, 0),
     'x-wsgiorg.fdevent.writable' : <built-infunctionuwsgi_eventfd_write>,
     'wsgi.run_once' : False,
     'wsgi.errors' : <openfile 'wsgi_errors' ,   mode 'w' at0x2fd46f0>,
     'REMOTE_PORT' : '56294' ,
     'uwsgi.version' : '1.9.10' ,
     'wsgi.file_wrapper' : <built-infunctionuwsgi_sendfile>,
     'PATH_INFO' : '/index.html'
}

start_response是个函数对象,参考https://www.python.org/dev/peps/pep-3333/#id26,其定义为为start_response(status, response_headers, exc_info=None),其作用是设置HTTP status码(如200 OK)和返回结果头部。

WSGI服务器

wsgi服务器就是为了构建一个让WSGI app运行的环境。运行时需要传入正确的参数,以及正确地返回结果,最终把结果返回客户端。工作流程大致是,获取客户端的request信息,封装到environ参数,然后把执行结果返回给客户端。

对于webpy而言,其内置了一个CherryPy服务器。CheckPy在运行时,采用多线程模式,主线程负责accept客户端请求,将请求放入一个request Queue里面,然后有N个子线程负责从Queue中取出request,然后处理后将结果返回给客户端。不过看起来,这个内置的服务器看起来是为了开发调试用,因为webpy并未开放这个默认容器的参数调节,例如线程数目,所以为了寻求高效地托管WSGI app,不建议用这个默认的容器。这可能也是使用uWSGI的原因,因为我们的线上系统有几个API的访问量很大,目前大概是250万次/天。(当然对于业务量不是很大的服务,可能这个默认的CheckPy也就够了)

uWSGI

我们知道,Python有把大锁GIL,会将多个线程退化为串行执行,所以一个多线程python进程,并不能充分使用多核CPU资源,所以对于Python进程,可能采用多进程部署方式比较有利于充分利用多核的CPU资源。

uWSGI就是这么一个项目,可以以多进程方式执行WSGI app,其工作模式为 1 master进程 + N worker进程(N*m线程),主进程负责accept客户端request,然后将请求转发给worker进程,因此最终是worker进程负责处理客户端request,这样很方便的将WSGI app以多进程方式进行部署。以下给出uwsgi响应客户端请求的执行流程图:

image值得注意的是,在master进程接收到客户端请求时,以round-bin方式分发给worker进程,所以多个process在处理前端请求时,所承受的负载相对还是均衡的。(这是我测试时的经验,改天再扒一下uwsgi的源代码确认一下 TODO)。关于uWSGI的使用,可能并不是这里的重点,不再赘述。

(其实看到uWSGI的多进程模型,我想到了Nginx,它也是多进程模型,这个也很有意思,由此了解了thunder herd问题,扯远了,继续往下说)

考虑一个应用场景:client向serverM(uwsgi)发起一个HTTP请求,serverM在处理这次请求时,需要访问另一个服务器serverN,直到serverN返回数据,serverM才会返回结果给client,即wsgi app是同步的。假如serverM访问serverN花费时间比较久,那么若是client请求数量比较多的情况下,(N*m)线程都会被占用,可想而知,大容量下的并发处理能力就受(N*m)的限制。

如果碰上了这种情况,怎么解决呢?

(1)增大N,即worker的数量:在增加进程的数量的时候,进程是要消耗内存的,并且如果进程数量太多的情况下(并且进程均处于活跃状态),进程间的切换会消耗系统资源的,所以N并不是越大越好。一般情况下,可能将进程数目设置为CPU数量的2倍。

(2)增大m,即worker的线程数量:在创建线程的时候,最大能够多大呢?由于线程栈是要消耗内存的,因此线程的数量跟系统设置(virtual memory)和(stack size)有关。线程数量太大会不会不太好?(这个肯定不好,我答不上来 TODO

由此在大并发需求的情况下,我了解到了C10K问题,并进一步学习到I/O的多路复用的epoll,可以避免阻塞调用在某个socket上。比如libevent就是封装了多个平台的高效地I/O多路复用方法,在linux上用的就是epoll。但是这里我们不讨论epoll或者libevent的使用,我们这里引入gevent模块。

gevent协程

gevent在使用时,跟thread的接口很像,但是thread是由操作系统负责调度,而gevent是用户态的“线程”,也叫协程。gevent的好处就是无需等待I/O,当发生I/O调用是,gevent会主动切换到另一gevent进行运行,这样在等待socket数据返回时,可以充分利用CPU资源。

在使用gevent内部实现:

1. gevent 协程切换使用greenlet项目,greenlet其实就是一个函数,及保存函数的上下文(也就是栈),greenlet的切换由应用程序自己控制,所以非常适合对于I/O型的应用程序,发生I/O时就切换,这样能够充分利用CPU资源。

2. gevent在监控socket事件时,使用了libevent,就是高级的epoll。

3. python中有个猴子补丁(monkey patch)的说法,在python进程中,python的函数都是对象,存在于进程的全局字典中,因此,开发者可以通过替换这些对象,来改变标准库函数的实现,这样还不用修改已有的应用程序。在gevent里,也有这样的monkey patch,通过gevent.monkey.patch_all()替换掉了标准库中阻塞的模块,这样不用修改应用程序就能充分享受gevent的优势了。(真是方便啊)

使用gevent需注意的问题

1. 无意识地引入阻塞模块

我们知道gevent通过monkey patch替换掉了标准库中阻塞的模块,但是有的时候可能我们会“无意识”地引入阻塞模块,例如MySQL-Python,pylibmc。这两个模块是通过C扩展程序实现的,都需要进行socket通信,由于调用的底层C的socket接口,所以超出了gevent的管控范围,这样就在使用这两个模块跟mysql或者memcached进行通信时,就退化为了阻塞调用

这样一个应用场景:在一个gevent进程中,基于MySQL-Python模块,创建一个跟mysql有10个连接的连接池,当并发量大的情况下,我们期望这10个连接可以同时处理对mysql的10个请求。实际如我们期望的这样么?的确跟期望不一样,因为在conn.query()的时候,阻塞了进程,这样意味着同一时刻不可能同时有2个对mysql的访问,所以并发不起来了。如mysql响应很慢的话,那么整个process跟hang住了没什么两样(这个时候,便不如多线程的部署模式了,因为多个线程的话,一个线程hang住,另一个线程还是有机会执行的)。

如何解决:在这些需要考虑并发的效率的场景,尽量避免引入阻塞模块,可以考虑纯python实现的模块,例如MySQL-Python->pymysql, pylibmc->memcached(这个python实现的memcached client可能不太完备,例如一致性hash目前都没实现)。

2. gevent在遇到I/O访问时,会进行greenlet切换。若是某个greenlet需要占用大量计算,那么若是计算任务过多(激进一点,陷入死循环),可能会导致其他greenlet没有机会执行。若是一个gevent进程需要执行多个任务时,若某个任务计算过多,可能会影响其他任务的执行。例如我曾遇到一个进程中,采用生产者任务(统计数据,将结果放入内存)+消费者任务(将计算结果写入磁盘),然而当数据很大的时候,生产者任务占用大量的CPU资源,然而消费者任务不能及时将统计结果写入磁盘,即生产太快,消费太慢,这样内存占用越来越多,一度高达2G内存。所以鉴于此,需要根据任务的特点(I/O密集或者CPU密集),合理分配进程任务。

参考:

https://www.python.org/dev/peps/pep-3333/

http://www.oschina.net/question/12_26400

http://blog.jobbole.com/77240/

http://www.tuicool.com/articles/2aIZZb

 

以上为工作经验总结,在整理成文的时候,才发现有些知识点只是一知半解,所以需要继续完善该文。

<think>好的,我现在需要回答用户在Flask中处理高并发和优化性能的问题。首先,我得回想一下用户的问题,他们想知道高并发的解决方案和性能优化方法。根据用户提供的引用内容,里面提到了SQL优化,比如使用LIMIT、分解关联查询等,但用户现在关心的是Flask层面的处理。我需要结合Flask的特点和相关技术来组织答案。 首先,Flask本身是一个轻量级框架,默认情况下是单线程的,处理高并发可能不够。所以可能需要提到使用异步视图或者配合其他服务器,比如Gunicorn或uWSGI,并设置多worker或线程。此外,使用异步框架如Gevent或Eventlet可能也是一个方向。 然后,数据库优化部分,用户提供的引用里提到SQL语句优化,这部分可以结合ORM的使用,比如SQLAlchemy的优化,比如批量操作、避免N+1查询、使用索引等。另外,缓存技术如Redis或Memcached也应该包含进去,比如缓存常用数据,减少数据库压力。 代码层面的优化,比如避免全局变量、减少请求处理时间、使用生成器和迭代器来节省内存。还有静态文件处理,建议用CDN或者Nginx来分担负担。 另外,可能需要考虑水平扩展,使用负载均衡,比如Nginx反向代理多个Flask实例。还有监控和日志分析,帮助发现瓶颈。 接下来要确保结构清晰,分点回答。每个部分给出具体的方法和例子,比如代码示例或者配置示例。还要注意用户提到的引用内容,虽然主要针对SQL,但可以适当关联到ORM的使用优化。 最后生成相关问题,需要围绕Flask性能优化和高并发处理,比如异步编程、部署方式、数据库优化手段、缓存策略等。 现在检查是否有遗漏的部分,比如异步支持在Flask中的实现方式,可能需要使用Flask的异步视图或者第三方插件。同时,数据库连接池的设置,比如使用SQLAlchemy的连接池配置,避免频繁创建连接。还有考虑使用消息队列来处理耗时任务,比如Celery,避免阻塞请求线程。 确保所有技术点都覆盖到,并且每个解决方案都有具体的实施方法或工具推荐。这样用户可以根据建议逐步优化他们的Flask应用。</think>### Flask高并发处理与性能优化方案 在Flask中处理高并发需要从服务器配置、异步处理、数据库优化和代码结构四个维度进行优化,以下是具体实施步骤: #### 一、服务器层优化 1. **多进程部署方案** ```bash # 使用Gunicorn启动4个worker进程+12个线程 gunicorn -w 4 --threads 12 app:app # 使用Eventlet协程模式 gunicorn -k eventlet -w 50 app:app ``` 通过worker数量设置建议公式:$workers = (2 * cores) + 1$[^1] 2. **反向代理配置** ```nginx # Nginx配置示例 upstream flask_app { server 127.0.0.1:8000; server 127.0.0.1:8001; } location / { proxy_pass http://flask_app; proxy_set_header Host $host; } ``` #### 二、异步处理方案 1. **异步视图(Flask 2.0+)** ```python from flask import jsonify import asyncio @app.route('/async') async def async_view(): await asyncio.sleep(1) return jsonify({"status": "async completed"}) ``` 2. **Celery分布式任务 ```python # tasks.py配置 from celery import Celery celery = Celery(__name__, broker='redis://localhost:6379/0') @celery.task def background_task(data): # 执行耗时操作 return process_data(data) ``` #### 三、数据库优化 1. **ORM优化策略** ```python # 避免N+1查询 users = User.query.options(joinedload('posts')).all() # 批量插入 db.session.bulk_insert_mappings(User, user_list) ``` 2. **连接池配置 ```python # SQLAlchemy配置 app.config['SQLALCHEMY_POOL_SIZE'] = 20 app.config['SQLALCHEMY_MAX_OVERFLOW'] = 10 ``` #### 四、代码级优化 1. **请求处理优化 ```python # 使用流式响应 @app.route('/large_data') def generate_large_data(): def generate(): for row in data_stream(): yield f"{row}\n" return Response(generate(), mimetype='text/plain') ``` 2. **缓存机制 ```python from flask_caching import Cache cache = Cache(config={'CACHE_TYPE': 'RedisCache'}) @app.route('/expensive') @cache.cached(timeout=60) def expensive_operation(): return complex_calculation() ``` #### 五、监控与调试 1. **性能分析工具 ```bash # 使用pyinstrument分析 flask --app app.py --debug run --extra-files --reload --inspect ``` 2. **日志分级配置 ```python app.logger.setLevel(logging.WARNING) handler = RotatingFileHandler('app.log', maxBytes=10000, backupCount=3) handler.setFormatter(logging.Formatter('[%(asctime)s] %(levelname)s: %(message)s')) ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值