Nginx+Gunicorn+Flask 后端架构 [draft]
Beijing, China: ☀️ 🌡️+33°C 🌬️↑4km/h
1 写在前面 🚀
近期,我需要在 Ubuntu 服务器上搭建一个用于生产环境的 Web 后端,该项目基于 Flask 框架的 Python 模块。经过在 Windows 环境下的充分测试后,接下来便开始部署工作,并选用了 Nginx 作为反向代理服务器、Gunicorn 作为 WSGI 服务器来托管 Flask 应用。
2 架构原理 🔍
整个 Web 应用的处理流程如下:
- 客户端(浏览器或 API)
用户访问您的网站或 API(例如http://yourserver.com
),请求被发送至 Nginx。 - Nginx(反向代理)
Nginx 接收请求并将其转发给 Gunicorn。
它通过 Unix 套接字 (myproject.sock
) 或 HTTP 端口 (127.0.0.1:8000
) 与 Gunicorn 进行通信,同时高效处理静态文件。 - Gunicorn(WSGI 服务器)
Gunicorn 启动多个工作进程,以支持并发请求处理。
它在这些进程中运行 Flask 应用,并将接收到的请求转交给 Flask 处理。 - Flask(Web 应用程序)
Flask 处理请求,执行应用逻辑,并生成响应(HTML、JSON 等)。
生成的响应随后返回给 Gunicorn。 - Gunicorn 将 Flask 生成的响应传递回 Nginx。
- Nginx 进一步处理并最终将响应返回给客户端。
- 客户端 接收响应,并在浏览器或 API 接口中展示结果。
3 服务器环境与安装 🛠️
3.1 环境要求
- Ubuntu 22.04
- Nginx
- Gunicorn
- Flask
- Python3
3.2 安装依赖
在部署前,请先更新系统软件包,并安装相关依赖:
# 更新系统软件包
sudo apt update
# 安装 Nginx
sudo apt install nginx -y
# 创建 Python 虚拟环境(建议使用 conda 或 venv)
conda create -n web python=3.9 -y
source activate web # 激活虚拟环境
# 安装 Flask 与 Gunicorn
pip install Flask gunicorn
4 构建 Flask 应用 🐍
4.1 创建 Flask 服务器
首先,在 ~/flask_app/script/
目录下创建 flask_server.py
文件:
mkdir -p ~/flask_app/script
nano ~/flask_app/script/flask_server.py
在文件中添加如下内容:
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello():
return "<h1 style='color:blue'>Hello There!</h1>"
if __name__ == "__main__":
app.run(host='0.0.0.0', port=5090, debug=True)
4.2 测试 Flask 应用
通过以下命令启动 Flask 服务器:
python ~/flask_app/script/flask_server.py
若启动成功,终端会显示类似如下的信息:
* Running on http://127.0.0.1:5090
Press CTRL+C to quit
在浏览器中访问 http://your-server-ip:5090
,若页面显示 “Hello There!” 则表明 Flask 应用正常运行。👌
4.3 创建 WSGI 入口文件
为使 Gunicorn 能正确加载 Flask 应用,需要创建 WSGI 入口文件。在 ~/flask_app/script/
下创建 wsgi.py
文件:
nano ~/flask_app/script/wsgi.py
添加如下内容:
from flask_server import app
if __name__ == "__main__":
app.run()
此时,Gunicorn 就可以通过 wsgi:app
启动并管理 Flask 应用了。🎯
5 配置 Gunicorn ⚡
确保 Gunicorn 能正常运行 Flask 应用。可使用以下命令启动 Gunicorn,并指定 wsgi:app
作为入口,同时绑定到 0.0.0.0:5090 以允许外部访问:
cd ~/flask_app/script
# 这里我使用指定路径的conda环境启动(大家自行选择)
/home/micl/miniconda3/envs/web/bin/gunicorn --bind 0.0.0.0:5090 wsgi:app
为了实现系统启动时自动运行 Gunicorn,可创建 systemd 服务单元文件:
sudo nano /etc/systemd/system/flask_web.service
写入以下内容:
[Unit]
Description=Gunicorn instance to serve Flask application
After=network.target
[Service]
User=micl
Group=www-data
WorkingDirectory=/home/micl/flask_app/script
Environment="PATH=/home/micl/miniconda3/envs/web/bin"
ExecStart=/home/micl/miniconda3/envs/web/bin/gunicorn --workers 1 --bind unix:/home/micl/flask_app/script/flask_web.sock -m 007 --timeout 3600 wsgi:app
[Install]
WantedBy=multi-user.target
然后,依次执行以下命令启用并启动该服务:
sudo systemctl daemon-reload
sudo systemctl restart flask_web
sudo systemctl enable flask_web
sudo systemctl status flask_web
注意 🔔:若 Flask 框架下的 Python 代码有修改,只需运行sudo systemctl restart flask_web
即可重新导入相关 API 服务。
6 配置 Nginx 🌐
当 Gunicorn 在 Unix 套接字上监听请求后,需配置 Nginx 代理转发这些请求。首先,在 /etc/nginx/sites-available/
目录下创建 Nginx 配置文件:
sudo nano /etc/nginx/sites-available/flask_web
写入以下配置内容(这里我添加了 requests 的响应时间):
server {
listen 80;
server_name 123.123.66.66; # 请替换为实际的服务器 IP
location / {
include proxy_params;
proxy_pass http://unix:/home/micl/flask_app/script/flask_web.sock;
proxy_read_timeout 3600;
proxy_connect_timeout 3600;
proxy_send_timeout 3600;
}
}
创建符号链接以启用该站点配置:
sudo ln -s /etc/nginx/sites-available/flask_web /etc/nginx/sites-enabled
最后,重新加载 Nginx 配置使其生效:
sudo systemctl restart nginx
sudo systemctl status nginx
7 查看运行日志 📜
7.1 查看 Gunicorn 日志
由于 Gunicorn 默认不会自动保存日志至文件,可通过以下命令实时查看日志(假设服务名为 steel
,请根据实际情况修改):
# 实时查看 Gunicorn 日志(类似 tail -f)
sudo journalctl -u steel -f
# 查看最近 100 行日志
sudo journalctl -u steel -n 100
# 查看过去 1 小时内的日志
sudo journalctl -u steel --since "1 hour ago"
7.2 查看 Nginx 日志
当 Nginx 转发请求遇到问题时,可查看其访问日志和错误日志:
# 查看访问日志
sudo tail -f /var/log/nginx/access.log
# 查看错误日志
sudo tail -f /var/log/nginx/error.log
8 异步任务处理 ⏳
为了避免长时间任务阻塞 Flask 主进程,可通过多线程方式在后台执行任务。下面给出一个示例,展示如何在 Flask 中启动并查询后台任务进度。
- 创建带异步任务的 Flask 服务器
在 flask_server.py
文件中,加入如下内容:
from flask import Flask, request, jsonify
import threading
import time
app = Flask(__name__)
tasks = {} # 用于存储任务进度
def long_running_task(task_id, data):
""" 模拟长时间执行的任务 """
for i in range(1, 11):
time.sleep(5) # 模拟处理延时
tasks[task_id] = f"{i * 10}%" # 更新任务进度
tasks[task_id] = "Completed"
@app.route('/start_task', methods=['POST'])
def start_task():
""" 启动后台任务并返回任务 ID """
task_id = str(len(tasks) + 1) # 生成任务 ID
data = request.json.get("data", "default_task_data")
tasks[task_id] = "0%" # 初始化任务进度
thread = threading.Thread(target=long_running_task, args=(task_id, data))
thread.start()
return jsonify({'task_id': task_id}), 202 # 202 表示请求已被接受
@app.route('/task_status/<task_id>', methods=['GET'])
def task_status(task_id):
""" 查询指定任务的进度 """
progress = tasks.get(task_id, "Not Found")
return jsonify({'task_id': task_id, 'progress': progress})
if __name__ == '__main__':
app.run(debug=True)
- 运行服务器
通过如下命令启动 Flask 服务器:
python flask_server.py
- 启动长任务
使用 curl 命令向服务器发送 POST 请求启动任务:
curl -X POST http://223.223.185.192/start_task -H "Content-Type: application/json" -d '{"data": "steel_experiment"}'
预期响应示例:
{"task_id": "1"}
- 查询任务进度
使用 curl 命令查询任务状态:
curl -X GET http://223.223.185.192/task_status/1
可能的响应示例如下:
- 任务进行中:
{"task_id": "1", "progress": "50%"}
- 任务已完成:
{"task_id": "1", "progress": "Completed"}
9 补充说明(大模型生成)
9.1 ✨ 为什么选择 Nginx?
在生产环境中,直接让 Flask 处理所有请求并不可行。Flask 既不是高性能 Web 服务器,也难以高效处理并发请求。因此,使用 Nginx 作为反向代理 可以优化请求分发,提升应用性能,主要优势包括:
-
⚡ 高效处理静态资源
- Nginx 可直接提供 CSS、JS、图片 等静态文件,减少 Flask 服务器的额外负担。
- 与 Flask 相比,Nginx 处理静态资源的效率可提升 10-100 倍 🚀。
-
🛠️ 负载均衡与高并发
- Nginx 可将请求分发至多个 Gunicorn 进程,轻松应对 数千级并发连接。
- 确保应用在 高流量场景下依然稳定运行。
-
🛡️ 增强安全性
- 通过 隐藏 Gunicorn 端口,防止外部直接访问 WSGI 服务器。
- 提供 SSL 加密、请求过滤、IP 限制 等安全机制,提高应用防护能力。
-
🚨 不使用 Nginx 的风险
- 📉 静态文件加载缓慢,占用 Flask 服务器的计算资源。
- 💥 单点故障风险,Gunicorn 崩溃后整个应用将无法访问。
- ⚠️ 缺乏安全防护,易受到 DDoS 攻击及恶意请求的影响。
9.2 🔧 为什么需要 WSGI ?
Flask 自带的开发服务器 仅适用于调试,不支持高并发,并存在内存泄漏等问题。因此,在生产环境中,我们需要使用 专业的 WSGI 服务器(如 Gunicorn) 作为 Flask 与 Web 服务器(Nginx)之间的桥梁,主要优势如下:
-
🚀 高并发处理
- Gunicorn 采用 多进程 Worker 机制,可同时处理多个请求,提高吞吐量。
- Flask 自带服务器为 单线程,在生产环境下性能极其有限。
-
🔗 协议适配
- Gunicorn 负责将 HTTP 请求转换为符合 WSGI 规范的请求,让 Flask 专注于业务逻辑,而无需处理底层网络通信。
-
🛠️ 生产级特性
- 提供 进程管理、自动重启、超时重试、日志记录 等功能,确保应用的稳定运行。
-
❌ 不使用 WSGI 服务器的风险
- 🐢 低性能,无法处理高并发请求,导致响应缓慢甚至崩溃。
- 📌 缺乏扩展性,无法利用多核 CPU 进行并行计算,影响应用可扩展性。
- ⚠️ 安全隐患,缺乏进程管理和防护机制,易受攻击或资源耗尽。
9.3 🔄 为什么使用 Unix Socket 而非 TCP 端口?
Nginx 与 Gunicorn 之间的通信方式主要有两种:TCP 端口通信(如 127.0.0.1:8000
)和 Unix Socket 通信(如 myproject.sock
)。在生产环境中,推荐使用 Unix Socket,主要优势包括:
-
⚡ 更低的延迟
- Unix Socket 直接通过 内核传输数据,避免 TCP/IP 协议带来的额外开销。
- 相比
127.0.0.1:8000
,可降低 30%-50% 的延迟 🚀,提高数据传输效率。
-
🔒 更安全的访问控制
- Unix Socket 本质上是一个 文件,可以使用
chmod 660 myproject.sock
限制访问权限。 - 避免 TCP 端口暴露带来的安全风险,降低遭受恶意访问的可能性。
- Unix Socket 本质上是一个 文件,可以使用
-
🎯 资源占用更低
- 无需额外管理端口,特别适用于 同一服务器部署多个应用 的场景。
-
⚠️ TCP 端口的潜在问题
- 需要额外配置 防火墙 🏰,以防端口被滥用或暴露在公网中。
- 在极端高并发情况下,可能出现 端口资源耗尽 ⛔,影响服务器稳定性。