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 应用:

  1. from flask import Flask
  2. app = Flask(__name__)
  3. @app.route('/')
  4. def hello():
  5. return 'Hello World!'
  6. if __name__ == '__main__':
  7. app.run()

整体代码结构是这样的:

  1. flack/
  2. ├── flack.py
  3. ├── templates/
  4. | └── index.html
  5. ├── static/
  6. | └── client-side js and css files
  7. ├── tests.py
  8. └── requirements.txt

v0.2 版本主要是抽离出了一个 utils 文件,现在整体项目结构变成:

  1. flack/
  2. ├── flack.py
  3. ├── utils.py <------ 新增的
  4. ├── templates/
  5. | └── index.html
  6. ├── static/
  7. | └── client-side js and css files
  8. ├── tests.py
  9. └── requirements.txt

v0.3 版本抽离出了 Model,抽离之后的结构为:

  1. flack/
  2. ├── flack.py
  3. ├── models.py <------ 新增的
  4. ├── utils.py
  5. ├── templates/
  6. | └── index.html
  7. ├── static/
  8. | └── client-side js and css files
  9. ├── tests.py
  10. └── requirements.txt

这里构建 Model 文件有个技巧,就是如何使用 SQLAlchemy 对象,作者推荐的是这样的:

  1. try:
  2. from __main__ import db
  3. except ImportError:
  4. from flack import db
  5. class User(db.Model):
  6. pass

原因就是为了防止循环引用。

v0.4 版本是重构了整个项目的结构,引入了启动文件:

  1. flack/
  2. ├── flack/
  3. | ├── __init__.py
  4. | ├── flack.py
  5. | ├── models.py
  6. | ├── utils.py
  7. | ├── templates/
  8. | | └── index.html
  9. | └── static/
  10. | └── client-side js and css files
  11. ├── tests.py
  12. ├── manage.py <- runserver, shell and createdb commands available here
  13. └── requirements.txt

v0.5 版本的话是抽离出了验证授权的接口:

  1. flack/
  2. ├── flack/
  3. | ├── __init__.py
  4. | ├── flack.py
  5. | ├── auth.py <------ 新增的
  6. | ├── models.py
  7. | ├── utils.py
  8. | ├── templates/
  9. | | └── index.html
  10. | └── static/
  11. | └── client-side js and css files
  12. ├── tests.py
  13. ├── manage.py
  14. └── requirements.txt

v0.6 的重点在重构单元测试文件:

  1. flack/
  2. ├── flack/
  3. | ├── __init__.py
  4. | ├── flack.py
  5. | ├── auth.py
  6. | ├── models.py
  7. | ├── utils.py
  8. | ├── templates/
  9. | | └── index.html
  10. | └── static/
  11. | └── client-side js and css files
  12. ├── manage.py <- test and lint commands added here
  13. ├── tests/
  14. | ├── __init__.py
  15. | └── tests.py
  16. └── requirements.txt

v0.7 版本的话就是规整了配置文件,根据不同环境提供了不同的配置:

  1. flack/
  2. ├── flack/
  3. | ├── __init__.py
  4. | ├── flack.py
  5. | ├── auth.py
  6. | ├── models.py
  7. | ├── utils.py
  8. | ├── templates/
  9. | | └── index.html
  10. | └── static/
  11. | └── client-side js and css files
  12. ├── config.py
  13. ├── manage.py
  14. ├── tests/
  15. | ├── __init__.py
  16. | └── tests.py
  17. └── requirements.txt

v0.8 的话就是创建 API Blueprint 了。。

  1. flack/api.py
  2. from .flack import db
  3. api = Blueprint('api', __name__)
  4. @api.route('/users', methods=['POST'])
  5. def new_user():
  6. pass

v0.10 版本就是修改 app 的创建方式,引入了工厂方法:

  1. db = SQLAlchemy()
  2. def create_app(config_name=None):
  3. app = Flask(__name__)
  4. app.config.from_object(config[config_name])
  5. db.init_app(app)
  6. # ...
  7. return app

v0.11 就是创建了一个 API 包:

  1. flack/
  2. ├── flack/
  3. | ├── __init__.py
  4. | ├── flack.py
  5. | ├── auth.py
  6. | ├── models.py
  7. | ├── utils.py
  8. | ├── api/
  9. | | ├── __init__.py
  10. | | ├── tokens.py
  11. | | ├── messages.py
  12. | | ├── users.py
  13. | ├── stats.py
  14. | ├── templates/
  15. | | └── index.html
  16. | └── static/
  17. | └── client-side js and css files
  18. ├── config.py
  19. ├── manage.py
  20. ├── tests/
  21. | ├── __init__.py
  22. | └── tests.py
  23. └── requirements.txt

这个版本的目录结构就是 MG 认为比较合理的 Flask 应用程序的结构,毕竟适合维护和扩展,但是,我认为还是有改进的空间的,例如 Models
还能抽离出一个目录,配置能抽离出一个目录,可能还会更具业务需要抽离出一个业务目录。但是,具体如何还需要各人结合自己的项目情况具体得取舍。

接下来就是性能方面的优化了,那就来看下 MG 大神在性能方面有哪些建议:

对于一个应用的性能来说,瓶颈可以概括得分为两块,分别是 IO 和 CPU 瓶颈,对于

当然,我们完全可以使用丢机器的方法解决一些初期的瓶颈问题,所以 v0.12 版本就是使用 Nginx 进行一个反向代理进行多机服务:

图 1:反向代理

但是这也架不住很多 CPU 或者 IO 密集的任务啊,所以是该引入异步任务了,所以 v0.13-14 引入了 Celery ,

图 2:引入异步任务

那现在遇到的问题就是客户端需要不断地轮训保持实时刷新,所以这个时候需要选择方案:

MG 这里选择是 选项4,所以这里变成了服务端主动推送的模型:

  1. # server.py
  2. def push_model(model):
  3. socketio.emit('updated_model', {
  4. 'class': model.__class__.__name__,
  5. 'model': model.to_dict()
  6. })
  7. # client/js
  8. Client (JavaScript)
  9. socket.on('updated_model', function(data) {
  10. if (data['class'] == 'User') {
  11. updateUser(data.model);
  12. }
  13. else if (data['class'] == 'Message') {
  14. updateMessage(data.model);
  15. }
  16. });

将这三者结合起来,就成了作者最终介绍的形态:

图 3:最终形态

这就是 MG 大神向我们展示的使用 Flask
构建大规模应用的策略,虽然他没说具体的效果如何,可以应对怎样场景多大规模的请求,但是确实让我学到了不少东西,可以给大家分享一下。

最后,附带上大神的视频国内链接:Flask At Scale
优酷