Web 服务器
用于完成和客户端建立链接,接受并解析请求,转发请求,调用 Web 框架处理业务并生成返回内容,组织并返回内容给客户端,关闭链接等工作,比如 Nginx,Gunicorn,uWSGI 等就是 Web 服务器
Web 框架
对 Web 服务的常用功能提取、组织、简化使用,令开发人员可以专注于业务逻辑,比如 Flask、Django 就是 Web 框架
WSGI
为了使得任意 Web 服务器都可以和任意 Web 框架搭配,设计出了 WSGI(Web Server Gateway Interface)接口协议,凡是符合 WSGI 协议的 Web 服务器和 Web 框架都可以搭配使用(注意:uWSGI 和 WSGI 是两个概念,uWSGI 是一个实现了 WSGI 协议的 Web 服务器)
prefork
prefork 是一种服务端编程模型,Gunicorn, uWSGI 都是这种模型的实现
prefork 就是有一个 master 进程 fork 出多个 worker 子进程,由子进程处理请求,master 进程只负责监控worker 子进程状态,如果子进程出现问题,可以重启一个,子进程监听同一端口,可以配置 reuse_port 参数在worker 进程间负载均衡,为了避免多线程编程带来的问题,通常设置一个 worker 就一个线程用于处理请求,同时使用协程模块比如 gevent 使得一个线程可以高效地处理高并发 IO 请求
Gunicorn
Green Unicorn(绿色独角兽)是一个满足 WSGI 协议的、轻量的、简易的、由 Python 实现的、pre-fork 模型的 Web 服务器,支持 sync、eventlet、gevent、tornado、gthread、gaiohttp 等多种 worker 类型
uWSGI
uWSGI 是由 C 语言实现的、满足 WSGI 的、pre-fork 模型的 Web 服务器,支持 uGreen,Greenlet,Stackless,Gevent,Coro::AnyEvent,Tornado,Goroutines,Fibers 等技术,实际上 uWSGI 不仅是一个 Web 服务器,也可以作为 Web 框架,只不过一般都只是当 Web 服务器使用
Nginx
实际上 Gunicorn 或 uWSGI 搭配 Flask 或 Django 就可以提供 Web 服务了,但实际生产环境上经常会在前面再加一个 Nginx 服务器,哪怕 Nginx 和 Gunicorn/uWSGI 是一对一的关系,主要因为 Nginx 有一些其他服务器不具备的强大功能:
- 负载均衡,Nginx 后面可以有多个节点处理同一个业务,可以在不同节点的服务器之间实现负载均衡
- 地址映射和端口映射,对于处理不同业务的多个节点,Nginx 可以作为统一入口,通过地址映射隐藏后面的多个服务器
- 静态文件支持,经过配置之后,Nginx 可以直接处理静态文件请求,不需要经过 Gunicorn/uWSGI 服务器,Gunicorn/uWSGI 服务器负责处理动态请求
- 伪静态,通过 rewrite 配置实现伪静态,比如把 index.html 指向到一个 test.php?v=1 的动态请求,伪静态会增加性能损耗
- 缓存静态和伪静态页面,对于静态页面,或是一定时间内不会变化的伪静态页面,可以缓存起来,设置一个超时时间,这样 Nginx 就可以不用每次都去读文件,或是每次都要动态生成页面
- 缓冲请求和响应,如果由于网络问题,导致请求和响应比较慢,可能会占用 Web 服务器的资源,影响业务逻辑的处理,而 Nginx 可以做缓冲,等收到完整的连接、请求消息后,再发给后端处理,收到后端返回后,立刻响应后端,再将消息响应到前端,这样在后端的 Web 服务器看起来,网络的请求响应都非常快,自己主要时间都是在处理业务逻辑,而相应的缓冲请求响应的工作又是 Nginx 比较擅长的,这样就提高了性能
- Nginx 的高可用性,高并发抗压能力,都比较强
- 访问控制、限速等功能
- 避免直接暴露 WSGI 服务器,可以同时作为防火墙防御网络攻击
Supervisor
为了防止 Gunicorn/uWSGI 等意外挂了,通常可以加一个 Supervisor 做监控 master 进程
Flask
一个 Python 的 Web 框架,主要基于 Werkzeug 和 jinja2
Werkzeug 是一个 WSGI 工具包,它实现了请求,响应对象和实用函数,Flask 通过 Werkzeug 实现 WSGI 协议,同时 Werkzeug 还提供了一个 WSGI 服务器,Flask 默认就是使用 Werkzeug 服务器,但一般只是用于开发调式,生产环境还是用 Gunicorn 等服务器,因为 Werkzeug 服务器性能差一些
jinja2 是 Python 的一个模板引擎,用于渲染生成 HTML 页面
Flask + Gunicorn + Nginx 简例
安装 flask
pip install flask
pip install flask_restplus ## 方便实现 REST API 的扩展,不是必须的
flask 代码 flask_test.py
import time
from flask import Flask
from flask import request
from flask import Response
from flask_restplus import Api
from flask_restplus import Resource
from functools import wraps
app = Flask(__name__)
api = Api(app, version='1.0', doc='/doc', title='Test API', description='Test API')
'''
api = Api(None, version='1.0', doc='/doc', title='Test API', description='Test API')
api.init_app(app)
'''
def time_it(func):
@wraps(func)
def time_it_decorated(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
print(f"time consumption : {end_time - start_time}
")
return result
return time_it_decorated
count = {}
def count_it(url_type):
def count_it_decorated(func):
@wraps(func)
def new_func(*args, **kwargs):
if url_type not in count:
count[url_type] = 0
count[url_type] += 1
print(f"receive {url_type} request : {count[url_type]}
")
return func(*args, **kwargs)
return new_func
return count_it_decorated
ns = api.namespace('api_v1', path='/api/v1/', description='Test Interface', decorators=[time_it])
USE_NAMESPACE = False
if USE_NAMESPACE:
@ns.route('/test')
class Test(Resource):
@count_it("test_1")
def get(self):
return Response(f"Hello {request.remote_addr}
current time is {time.time()}
")
@count_it("test_2")
def post(self):
time.sleep(5)
return Response(f"Hello {request.remote_addr}
")
else:
@app.route('/test/1', methods=['GET'])
@time_it
@count_it("test_1")
def test():
return f"Hello {request.remote_addr}
current time is {time.time()}
"
@app.route('/test/2', methods=['POST'])
@time_it
@count_it("test_2")
def test_2():
time.sleep(5)
return f"Hello {request.remote_addr}
"
if __name__ == '__main__':
app.run()
可以直接运行这个 flask 程序,这时用的是 Werkzeug 自带的服务器
python3.6 flask_test.py
安装 Gunicorn
pip3.6 install gunicorn
pip3.6 install gevent # 使用 gevent 模式才需要,不是必须的
创建 Gunicorn 配置文件 gunicorn_config.py (也可以通过 gunicorn 参数指定,但通过配置文件更方便)
import gevent.monkey
gevent.monkey.patch_all() # 使用 gevent 模式才需要这步
import multiprocessing
debug = True
loglevel = "debug"
accesslog = "./access.log"
errorlog = "./error.log"
#daemon = True
#capture_output = True
bind = '127.0.0.1:8000'
workers = multiprocessing.cpu_count() * 2 + 1
worker_class = 'gevent'
preload_app = True
reload = True
x_forwarded_for_header = 'X-FORWARDED-FOR'
proxy_allow_ips = '*'
启动 gunicorn 服务器
sudo gunicorn -c gunicorn_config.py flask_test:app
安装 Nginx
sudo apt-get install nginx
Nginx 配置
/etc/nginx/nginx.conf
这个配置文件会引入下面两个目录的配置文件
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
创建 /etc/nginx/conf.d/flask_test.conf (配置文件可以有多个)
server {
listen 80;
server_name localhost;
# location 的 URL 匹配语法
# ~* 表示正则表达式,不区分大小写
# ~ 表示正则表达式,要区分大小写
# = 表示精确匹配
# 没有修饰符的,以指定模式开始,比如 location / 匹配所有以 / 开始的 URL
# 静态页面,直接读取 html 文件
location ~* .*.html$ {
gzip on;
root /usr/share/nginx/html;
}
# 动态页面,转发给 gunicorn
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
检测配置
sudo nginx -t
启动
sudo nginx
或
sudo nginx -s reload
如果需要负载均衡,配置如下
upstream flask_test {
server 192.168.1.2:8001;
server 192.168.1.3:8002;
}
server {
listen 80;
server_name localhost;
# 动态页面,转发给 gunicorn
location / {
proxy_pass http://flask_test;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}