Pycon 2016 将于今天谢幕了,在过去的几天里,Python 大神们分享了大量自己关于 Python 在各方面的见解以及实践,作为还处于大量积累能量的层次,我也是期待着这次大会的大神们的分享,很高兴的是本次 pycon 大会没有令我失望,有几个分享让我有种浴血喷张的感觉,其中一个就是 MG(Miguel Grinberg)大神(就是那个引我 Flask 入门的《Flask Web开发》的作者)分享的 《Flask At Scale》(我翻译为 《Flask 大规模实践》)。
MG 的《Flask At Scale》我觉得可以分为两部分,分别是:
- 项目结构层次的完善,这个是为了可维护性而做
- 性能层次的完善,这个当然为了性能而做
那本文就以个人的视角给大家分享一下我观看完 《Flask At Scale》的个人收获。
可维护的代码
前半部分的讲解和我在 《The Way To Flask》 中前 8 章介绍的差不多,都是不断地优化代码结构用的。
v0.1 版本的代码是一个非常简单的 Flask 应用:
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello():
return 'Hello World!'
if __name__ == '__main__':
app.run()
整体代码结构是这样的:
flack/
├── flack.py
├── templates/
| └── index.html
├── static/
| └── client-side js and css files
├── tests.py
└── requirements.txt
v0.2 版本主要是抽离出了一个 utils 文件,现在整体项目结构变成:
flack/
├── flack.py
├── utils.py <------ 新增的
├── templates/
| └── index.html
├── static/
| └── client-side js and css files
├── tests.py
└── requirements.txt
v0.3 版本抽离出了 Model,抽离之后的结构为:
flack/
├── flack.py
├── models.py <------ 新增的
├── utils.py
├── templates/
| └── index.html
├── static/
| └── client-side js and css files
├── tests.py
└── requirements.txt
这里构建 Model 文件有个技巧,就是如何使用 SQLAlchemy 对象,作者推荐的是这样的:
try:
from __main__ import db
except ImportError:
from flack import db
class User(db.Model):
pass
原因就是为了防止循环引用。
v0.4 版本是重构了整个项目的结构,引入了启动文件:
flack/
├── flack/
| ├── __init__.py
| ├── flack.py
| ├── models.py
| ├── utils.py
| ├── templates/
| | └── index.html
| └── static/
| └── client-side js and css files
├── tests.py
├── manage.py <- runserver, shell and createdb commands available here
└── requirements.txt
v0.5 版本的话是抽离出了验证授权的接口:
flack/
├── flack/
| ├── __init__.py
| ├── flack.py
| ├── auth.py <------ 新增的
| ├── models.py
| ├── utils.py
| ├── templates/
| | └── index.html
| └── static/
| └── client-side js and css files
├── tests.py
├── manage.py
└── requirements.txt
v0.6 的重点在重构单元测试文件:
flack/
├── flack/
| ├── __init__.py
| ├── flack.py
| ├── auth.py
| ├── models.py
| ├── utils.py
| ├── templates/
| | └── index.html
| └── static/
| └── client-side js and css files
├── manage.py <- test and lint commands added here
├── tests/
| ├── __init__.py
| └── tests.py
└── requirements.txt
v0.7 版本的话就是规整了配置文件,根据不同环境提供了不同的配置:
flack/
├── flack/
| ├── __init__.py
| ├── flack.py
| ├── auth.py
| ├── models.py
| ├── utils.py
| ├── templates/
| | └── index.html
| └── static/
| └── client-side js and css files
├── config.py
├── manage.py
├── tests/
| ├── __init__.py
| └── tests.py
└── requirements.txt
v0.8 的话就是创建 API Blueprint 了。。
flack/api.py
from .flack import db
api = Blueprint('api', __name__)
@api.route('/users', methods=['POST'])
def new_user():
pass
v0.10 版本就是修改 app 的创建方式,引入了工厂方法:
db = SQLAlchemy()
def create_app(config_name=None):
app = Flask(__name__)
app.config.from_object(config[config_name])
db.init_app(app)
# ...
return app
v0.11 就是创建了一个 API 包:
flack/
├── flack/
| ├── __init__.py
| ├── flack.py
| ├── auth.py
| ├── models.py
| ├── utils.py
| ├── api/
| | ├── __init__.py
| | ├── tokens.py
| | ├── messages.py
| | ├── users.py
| ├── stats.py
| ├── templates/
| | └── index.html
| └── static/
| └── client-side js and css files
├── config.py
├── manage.py
├── tests/
| ├── __init__.py
| └── tests.py
└── requirements.txt
这个版本的目录结构就是 MG 认为比较合理的 Flask 应用程序的结构,毕竟适合维护和扩展,但是,我认为还是有改进的空间的,例如 Models
还能抽离出一个目录,配置能抽离出一个目录,可能还会更具业务需要抽离出一个业务目录。但是,具体如何还需要各人结合自己的项目情况具体得取舍。
接下来就是性能方面的优化了,那就来看下 MG 大神在性能方面有哪些建议:
对于一个应用的性能来说,瓶颈可以概括得分为两块,分别是 IO 和 CPU 瓶颈,对于
IO 瓶颈:我们可以这样解决:
- 引入线程/进程/协程
- 让 IO 密集的请求异步化
CPU 瓶颈: 我们可以这样解决:
- 异步化请求
当然,我们完全可以使用堆机器的方法解决一些初期的瓶颈问题,所以 v0.12 版本就是使用 Nginx 进行一个反向代理进行多机服务:
图 1:反向代理 |
---|
但是这也架不住很多 CPU 或者 IO 密集的任务啊,所以是该引入异步任务了,所以 v0.13-14 引入了 Celery ,
图 2:引入异步任务 |
---|
那现在遇到的问题就是客户端需要不断地轮训保持实时刷新,所以这个时候需要选择方案:
- Option #1: Streaming
- Option #2: Long-polling
- Option #3: WebSocket
- Option #4: Socket.IO (long-polling + WebSocket)
MG 这里选择是 选项4,所以这里变成了服务端主动推送的模型:
# server.py
def push_model(model):
socketio.emit('updated_model', {
'class': model.__class__.__name__,
'model': model.to_dict()
})
# client/js
Client (JavaScript)
socket.on('updated_model', function(data) {
if (data['class'] == 'User') {
updateUser(data.model);
}
else if (data['class'] == 'Message') {
updateMessage(data.model);
}
});
将这三者结合起来,就成了作者最终介绍的形态:
图 3:最终形态 |
---|
这就是 MG 大神向我们展示的使用 Flask 构建大规模应用的策略,虽然他没说具体的效果如何,可以应对怎样场景多大规模的请求,但是确实让我学到了不少东西,可以给大家分享一下。
最后,附带上大神的视频国内链接:Flask At Scale 优酷