为了处理多并发请求,我们设计了CGI通讯的方式,使用多进程来处理请求,但是CGI的实现也分成了很多种。所以为了统一接口规范,Python定义了WSGI协议之后,出现了很多基于这个协议的应用,常用的WSGI容器有Gunicorn和uWSGI,但Gunicorn直接用命令启动,不需要编写配置文件,相对uWSGI要容易很多。

Gunicorn 简介

Gunicorn使用pre-fork worker模型,简单来说就是为了提高CGI的响应速度,先从master进程中预先fork出一些进程,这样当请求到来的时候就可以快速响应。
当然了,Gunicorn是WSGI的实现,但同时也自带web server,能直接对外提供web服务。包括大部分的web app框架比如Flask和Django也都带有web server。不过,在真正的生产环境的部署中,它们还是各司其职,Flask/Django只用于写app、Gunicorn只用于运行和管理Python web app,而在它们前面有专门的web server,比如Nginx。

安装Gunicorn

1
pip install gunicorn

启动 Gunicorn

首先新建一个简单的web实例应用

main.py
1
2
3
4
5
6
7
from flask import Flask
app = Flask(name)
@app.route('/')
def index():
return 'hello world'
if name == 'main':
app.run()

然后使用一个简单的命令启动Gunicorn

1
gunicorn --worker=3 main:app -b 0.0.0.0:8080

当然,这个时候就会有小伙伴要问了,flask也有WSGI协议的实现,和Gunicorn的有什么区别呢?
这里做一个简单的压力测试,首先直接运行flask,python main.py,然后使用ab工具进行500次访问的压力测试。
测试结果如下所示:

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
48
This is ApacheBench, Version 2.3 <$Revision: 1843412 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking localhost (be patient)
Completed 100 requests
Completed 200 requests
Completed 300 requests
Completed 400 requests
Completed 500 requests
Finished 500 requests


Server Software: Werkzeug/2.1.2
Server Hostname: localhost
Server Port: 5000

Document Path: /
Document Length: 12 bytes

Concurrency Level: 500
Time taken for tests: 0.414 seconds
Complete requests: 500
Failed requests: 0
Total transferred: 92500 bytes
HTML transferred: 6000 bytes
Requests per second: 1208.81 [#/sec] (mean)
Time per request: 413.630 [ms] (mean)
Time per request: 0.827 [ms] (mean, across all concurrent requests)
Transfer rate: 218.39 [Kbytes/sec] received

Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 2 2.5 0 8
Processing: 9 101 28.8 112 143
Waiting: 2 100 28.9 111 142
Total: 9 103 27.0 112 144

Percentage of the requests served within a certain time (ms)
50% 112
66% 114
75% 117
80% 119
90% 122
95% 128
98% 135
99% 143
100% 144 (longest request)

可以看到,具体的结果是这样的,500次并发请求的结果:

1
2
3
Time per request:       413.630 [ms] (mean)
Time per request: 0.827 [ms] (mean, across all concurrent requests)
Transfer rate: 218.39 [Kbytes/sec] received

同样的,使用Gunicorn之后,再测试一次,结果如下:

1
2
3
Time per request:       66.469 [ms] (mean)
Time per request: 0.133 [ms] (mean, across all concurrent requests)
Transfer rate: 1212.09 [Kbytes/sec] received

可以看到,性能上有了大大的提升,轻松应对500次并发请求。

应用场景

在实际的应用当中,Gunicorn对于静态文件的支持比较差,所以一般会使用Nginx做反向代理服务器,然后由gunicorn做中间人来连接flask和nginx。
具体的架构如下所示:
python-web-3-2022-06-08-20-33-33
由客户端发起请求,然后Nginx代理服务器将请求转发给Gunicorn,由Gunicorn的worker管理处理请求的进程,再将请求发送给Flask,然后Flask进行响应静态页面。

后话

当然我这里只是粗略的介绍了一下Gunicorn的使用和简单的工作原理,要深入了解的话可以看一下其他的资料,就不再展开赘述了。
当然,随着Python的不断发展,多线程领域也是遍地开花,也出现了很多处理多并发的应用,但是并没有WSGI这么好使,所以这里也不展开,按理来说,通过多线程和多进程的方式,我们基本上可以处理大部分的应用场景了。但是,进入21世纪以来,随着互联网的飞速发展,C10K的问题到来了,服务器的资源又不够了,能开的进程终究是有上限的,我们需要寻找别的方式来处理请求了。

什么是C10K问题?Python服务器又是怎么处理高并发的呢?且听下回分解。