第2章 框架基础和技术选型

上一章中,我们对需求进行了评审和分析,最终得到了具体要开发的功能点,并且对模块进行了划分。现在我们需要做的是,根据要开发的功能进行框架选择。

针对不同的场景选择不同的技术架构,所产生的开发成本和维护成本都不一样。特定场景下合适的技术架构能够让开发人员更快速地开发系统,并且后期的维护成本也大大降低。相反,一个不合适的技术架构,会导致开发和维护成本大大增加。

我尝试总结在做技术选型时应该考虑的因素。

□ 所选语言/框架/数据库是否应用广泛,是否有比较好的社区支持以及大量的用户反馈。

□ 语言/框架/数据库所提供的能力(功能)是否能够契合业务的需要,从而减少重复造轮子的工作量。

□ 自己团队的成员是否熟悉该框架和数据库,是否有人能够解决在框架使用中遇到的大部分问题。

这一章中,我们会讲Python 2 和Python 3 的选择,并对比Flask、Tornado 和Django 这三个Web 框架,了解它们的特点和应用场景。

2.1 Python 2.7 与Python 3.x

选择Python 2 还是Python 3 是近几年来比较流行的一个“话题”,当然这只是在网络某些论坛或者社区里。在真实的环境下,没有这么多纠结。选择目前应用最广泛的,周围人都在用,并且自己团队能够掌控住的,就是最合适的。对于Python 2 和Python 3 的差异,其实写起代码来,没那么大的差别,最关键的一点还是环境。一个熟悉而稳定的环境,能够支撑项目顺利上线,并且降低后期迭代的成本。

2.1.1 历史演进

随着Python 3.x 版本的成熟,越来越多新项目在开始时都会考量是否要选用Python 3 来做。相对几年前来说,现在这种倾向性更加明显。

其实早在前几年,我们就已经在考虑这样的事情了,甚至也做了些尝试。只是碍于相关周边的依赖,有些库还是没有Python 3 的支持,如果硬要上Python 3 的话,势必需要自己造很多轮子,时间成本非常高,所以还是在Python 2 上开发。无论是什么级别的技术选型,我们往往会选择团队擅长的技术栈来开发项目,这样能够在可控的时间内完成交付/上线。选择一个不熟悉的或者没有经受过大规模线上实践的技术栈是有风险的。

说明 哪些包已经支持Python 3,哪些还不支持,可以查看这里:https://python3wos.appspot.com/。

今天(2018 年5 月)看来,大部分的库已经对Python 3 做了支持。

在2017 年的PyCon 上,Instagram 宣布作为基于Django 的大规模(月活超过7 亿)应用,目前已经全面切换到Python 3.6 上,并且得到了不错的性能提升。这应该是当时公开的第一个把如此大规模的项目从Python 2 迁移到Python 3 上的案例。对于很多公司来说,这会是一个很好的榜样。但是需要注意的是,他们也付出了相应的时间成本以及试错成本。其实大部分的选型和决策都是在成本和收益之间进行考量。

关于Instagram的分享,建议你搜索Instagram PyCon 2017,查看当时的分享视频或者对应的关于视频内容解析的文章。这里分享InfoQ 的一篇文章《Python 向来以慢著称,为啥Instagram却唯独钟爱它》(详见http://www.infoq.com/cn/articles/instagram-pycon-2017)。

这是个不错的信号,有企业带了个好头,并且也有一些踩坑的分享。后面会有越来越多基于Python 3 的项目。对于重点项目,我们也会考虑迁移到Python 3 上。这其实也是一个环境问题。今年大家还在谈论Python 2 还是Python 3,Python 2 上踩过哪些坑,基于Python 2 的生产环境的经验分享。但是过几年,大家再讨论的可能就是基于Python 3 的经验分享了。如果你不跟上,也会脱离环境。这个问题就跟早些年有些人/团队孤独地使用Python 3 来做项目一样。

整体来说,Python 3 是趋势,并且从目前的周边环境和配套上来说,你可以开始尝试在Python 3上开发了。Python 3 会逐渐成为主流。

目前,我们团队开发的新项目基本上都会选择Python 3.6 来开发。

2.1.2 现实场景

回到现实,就像开头所说,从写代码上来说,Python 2 和Python 3 的差异没那么大。并不是说那些在企业中工作的人不思上进,懒得去把代码升级到Python 3,而是在企业中考虑更多的还是成本和回报。从开发项目的角度来说,没有哪些业务只能在Python 3 中实现,而在Python 2中无法实现。所以优先考虑的还是让项目如期上线,尽量避免线上的bug 给用户造成影响。

虽说对于语法、基础库上的变化,写起代码来不会有太大差别,但是对于运行中的问题,在没有大量经验的前提下,直接在生产环境下跑还是很有风险的。这个风险是我们要尽量避免的。就像我在知乎上的回答一样,产品经理和用户不会关心你用的是Python 2 还是Python 3,他们只关心你的程序会不会挂,数据会不会丢 。

因此,即便你现在(2018 年)直接学习Python 3,但等你到公司之后会发现,有些老项目依然跑在Python 2 上面,你可能需要接手这些项目。但是不用担心,还是上面那句话,语言和库上的差别不需要花太多时间就能熟悉,主要还是靠经验。你是否有Python 2 生产环境下开发和解决问题的经验,能够帮助企业快速解决老项目中的线上问题?

2.1.3 为未来做准备

上面虽然说到现实场景中我们应该拿擅长的工具来做项目,但是这样下去,会不可避免地进入一个死循环,导致工作中的技术环境跟不上社区主流环境的发展。对于技术人员来说,这是一个可怕的事情,就好像你并没有做错什么事情,却被企业淘汰了。

因此,我们需要在专注当下(无论是语言版本还是框架版本)的同时,研究新技术,无论是通过写一个Demo,还是像the5fire 这样写一个线上博客,把新技术用进去。等熟练掌握新技术之后,你就可以推动项目升级或者团队的技术栈升级了。

在这本书中,我们将直接使用Python 3.6 作为开发版本。我相信在接下来的时间里,会有越来越多的项目基于Python 3.x 来开发。但是,如果你遇到Python 2.7 的项目,也不必慌张,因为差别没那么大。

2.1.4 参考资料

□ Python 2 和Python 3 的差别:http://blog.jobbole.com/80006/。

□ Python 2和Python 3的主要差别:http://sebastianraschka.com/Articles/2014_python_2_3_key_diff.html。

□ 如何看待Instagram 将所有Web 后端迁移到Python 3.6:https://www.zhihu.com/question/60333140/answer/175130694。

2.2 WSGI——Web 框架基础

2.2.1 简介

WSGI,全称是Web Server Gateway Interface(Web 服务器网关接口)。这是Python 中定义的一个网关协议,规定了Web Server 如何跟应用程序交互。Web Server 可以理解为一个Web 应用的容器,通过它可以启动应用,进而提供HTTP 服务。而应用程序是指我们基于框架所开发的系统。

这个协议最主要的目的就是保证在Python 中所有Web Server 程序或者说Gateway 程序,能够通过统一的协议跟Web 框架或者说Web 应用进行交互。这对于部署Web 程序来说很重要,你可以选择任何一个实现了WSGI 协议的Web Server 来跑你的程序。

如果没有这个协议,那么每个程序、每个Web Server 可能都会实现各自的接口,实现各自的“轮子”,最终的结果会是一团乱。

使用统一协议的另外一个好处就是,Web 应用框架只需要实现WSGI,就可以跟外部请求进行交互了,不用去针对某个Web Server 来独立开发交互逻辑,开发者可以把精力放在框架本身。这一节中,我们简单了解WSGI 协议是如何运作的。理解这一协议非常重要,因为在Python中大部分的Web 框架都实现了此协议,也使用WSGI 容器来进行部署。

2.2.2 简单的Web Server

在了解WSGI 协议之前,我们先来看一个通过socket 编程实现的Web 服务的代码。其逻辑很简单,就是通过监听本地8000 端口,接收客户端发过来的数据,然后返回对应的HTTP 响应:

图像说明文字 图像说明文字

这段代码的逻辑很简单,建议你在自己的电脑上敲一遍,然后用Python 3 运行(用Python 2的话,需要做些调整),通过浏览器访问看看能否展示页面。这个页面展示稍微有点问题,需要你修改其中的代码,将上面Content-Type: text/plain 中的plain 修改为 html,然后按Ctrl+C 组合键结束进程,重新运行,刷新页面,看看结果。这里思考一下为什么会不同。你也可以修改其他代码,查看结果有何不同。

理解这段代码很重要!这是Web 服务最基本的模型,通过socket 和HTTP 协议提供Web 服务。建议你在理解上面的代码之前,不要继续往下学习。你需要在脑海中有Web 请求处理的模型,才能更好地理解框架为我们提供了哪些能力。

2.2.3 多线程版的Web Server

上面只是简单的单进程、单线程的版本,这能够让你更好地理解Web 服务。现在我们把逻辑变得复杂一些,调整上面的代码,在handle_connection 方法的第一行之前增加如下代码:

图像说明文字

重新运行,然后访问页面,同时再打开一个(或者更多)新的浏览器窗口(注意不是新的标签页),访问页面,同时观察Console 上的输出。看看有什么问题。

思考一下。

有什么发现吗?

好了,问题是这样:当我们处于单进程、单线程模型时,程序接受一个请求,然后需要花100 s 处理,此时新的请求是进不来的,因为只有一个处理程序。类比到生活中就是,你去ATM上取钱,只有一台机器,前面如果有人需要花费100 s 才能完成操作,那么你就要等100 s。

到这里,你应该能明白最简单的模型只是用来理解原理的,实用性并不强。如果我用上面的代码做了个网站,每次只能有一个用户访问,那恐怕就没人来了。

所以,我们需要了解其他的Web 服务模型——多线程模式。

理论是这样的,每来一个新请求,我们就创建一个线程来处理它,这样的话,这个请求处理的耗时不会影响下一个请求。就好像是,只要有人来取钱,就会自动出现一个ATM 一样。

我们来看代码,需要提醒的是,你理解了上面的代码之后,需要再回忆Python 多线程部分的知识点。我知道,对你来说不是难事。来吧,看代码:

图像说明文字 图像说明文字

运行代码python thread_socketserver.py,在浏览器中访问http://127.0.0.1:8000,多开几个浏览器窗口,看看有什么不同。再仔细阅读代码,看看是如何处理的。

接着,还需要改一下代码。在handle_connection 下面有两行注释,需要打开,然后重启进程。再次访问页面,同时打开多个浏览器窗口(不是标签页),看看命令行上的输出。即便有一个任务耗时60 s,也不影响下一个请求进来。看到了吧。

可以思考一下具体实现。这里主要涉及的知识点除了一开始用到过的socket 处理HTTP 协议外,还有下面这些。

□ serversocket.setblocking(0):目的是设置为非阻塞模式。所谓非阻塞,就是当前socket不会在accept 或者recv 时处于阻塞状态(必须等待有连接或者数据过来才执行下一步)。

□ serversocket.accept 外的try...catch:在非阻塞模式下,当没有连接可以被接受时,就会抛出EAGAIN 错误。你可以简单理解为此时没有资源(连接)可以使用,所以会抛出错误来,而这个错误是合理的。

□ 多线程的使用:当我们通过accept 接受连接之后,就开启一个新的线程来处理这个连接,而主程序可以继续在while True 循环中处理其他连接。

当然,这里的threading 也可以使用Python 的multiprocessing 模块来替换,从而使用多进程的方式处理请求。另外,这里的非阻塞其实也不是必需的。你可以通过serversocket.setblocking(1)设置为阻塞模式,然后执行后看看结果是否一致。这里使用非阻塞的例子是希望你对这种模式有一个初步理解,因为在Web 框架中,异步非阻塞是一种很常见的模式。

另外,还有一种Web 模型,每次接受新请求时,就会产生一个子进程来处理,它跟多线程编程的模式类似。具体代码可以参考本书在GitHub 上的代码目录,位置为code/chapter2/section2/fork_socketserver.py。

到此为止,你应该能理解每天访问的网站大概是怎么处理你的访问的了(上面的示例代码只是基本原理)。接下来,我们来看看Python 中所有框架在处理HTTP 请求时需要用到的东西。

2.2.4 简单的WSGI Application

理解了上面的代码之后,我们继续看看WSGI 协议。该协议分为两部分:其中一部分是WebServer 或者Gateway,就像上面的代码一样,它监听在某个端口上接受外部的请求;另一部分是Web Application。Web Server 接受请求之后,会通过WSGI 协议规定的方式把数据传递给WebApplication,在Web Application 中处理完之后,设置对应的状态和header,之后返回body 部分。Web Server 拿到返回数据之后,再进行HTTP 协议的封装,最终返回完整的HTTP Response 数据。

这么说可能比较抽象,下面还是通过代码来演示这个流程。我们先实现一个简单的应用:

# 文件位置:/code/chapter2/section2/wsgi_example/app.py
# coding:utf-8

def simple_app(environ, start_response):
    """Simplest possible application object"""
    status = '200 OK'
    response_headers = [('Content-type', 'text/plain')]
    start_response(status, response_headers)
    return [b'Hello world! -by the5fire \n']

我们要怎么运行这个应用呢?参照Python PEP 3333 文档上的代码,编写能够运行上面应用程序的CGI 脚本:

图像说明文字 图像说明文字

运行脚本python gateway.py,在命令行上能够看到对应的输出:

Status: 200 OK
Content-type: text/plain

Hello world! -by the5fire

对比一开始通过socket 写的Web Server,这就是一个最基本的HTTP 响应了。只是现在是直接通过gateway.py 脚本来调用的。如果输出给浏览器,浏览器会展示出Hello world! -bythe5fire 字样。

我们再通过另外一种方式来运行应用,此时用到的工具就是Gunicorn。你可以先通过命令pip install gunicorn 进行安装。

安装完成之后,进入app.py 脚本的目录。通过命令gunicorn app:simle_app 来启动程序。这里的Gunicorn 就是一个Web Server。启动之后,会看到如下输出:

[2017-06-10 22:52:01 +0800] [48563] [INFO] Starting gunicorn 19.4.5
[2017-06-10 22:52:01 +0800] [48563] [INFO] Listening at: http://127.0.0.1:8000 (48563)
[2017-06-10 22:52:01 +0800] [48563] [INFO] Using worker: sync
[2017-06-10 22:52:01 +0800] [48566] [INFO] Booting worker with pid: 48566

通过浏览器访问http://127.0.0.1:8000,就能看到对应的页面了。

2.2.5 理解WSGI

通过上面的代码,你应该看到简单的Application 中对WSGI 协议的实现。你可以在simple_app 方法中增加print 语句来查看参数分别是什么。虽然gateway.py 的代码看起来有点麻烦,但是你只需要关注一点,那就是result = application(environ, start_response)这行代码。我们要实现的Application,只需要能够接收一个环境变量以及一个回调函数即可。当我们处理完请求之后,通过回调函数(start_response)来设置response 的状态和header,最后返回最终结果,也就是body。

WSGI 协议规定,application 必须是一个可调用对象,这意味这个对象既可以是Python中的一个函数,也可以是一个实现了 call 方法的类的实例。比如这个:

图像说明文字

我们依然可以通过Gunicorn 这个WSGI Server 来启动应用:gunicorn app:aplication。再次访问http://127.0.0.1:8000,看看是 不是输出了同样的内容。

除了这种方式之外,我们还可以通过另外一种方式实现WSGI 协议。从上面simpleapp和这里 AppClass._call 的返回值来看,WSGI Server 只需要返回一个可迭代的对象就行,那么我们可以用下面这种方式达到同样的效果:

图像说明文字

这里我们再次使用Gunicorn 来启动:gunicorn app:AppClassIter。然后,在浏览器中访问http://127.0.0.1:8000,看看结果。

这里的启动命令并不是一个类的实例,而是类本身,为什么呢?通过上面两个代码,我们可以观察到能够被调用的方法会传environ 和start_response 过来,而现在这个实现没有可调用的方式,所以就需要在实例化的时候通过参数传递进来,这样在返回body 之前,可以先调用start_response 方法。

因此,可以推测出WSGI Server 是如何调用WSGI Application 的。结合前面练习过的socket编程,大概代码如下:

图像说明文字

大概如此。其实上面的gateway.py 做的也是差不多的逻辑。

2.2.6 WSGI 中间件和Werkzeug

除了交互部分的定义,WSGI 还定义了中间件部分的逻辑,这个中间件可以理解为Python中的一个装饰器,可以在不改变原方法的情 况下对方法的输入和输出部分进行处理。比方说,返回body 中的文字部分,把英文转换为中文的操作,或者是一些更为易用的操作(比如对返回内容的进一步封装)。在上面的例子中,我们先调用start_response 方法,然后再返回body。那么,我们能不能直接封装一个Response 对象呢,直接给对象设置header,而不是按这种单独操作的逻辑?像这样:

图像说明文字

这样看起来就更加自然一些了。

因此,就存在Werkzeug 这样的WSGI 工具集,让你能够跟WSGI 协议更加友好地交互。从理论上看,我们可以直接通过WSGI 协议的简单实现(也就是上面的代码)写一个Web 服务。但是有了Werkzeug 之后,我们可以写得更加容易。在很多Web 框架中,都是通过Werkzeug 来处理WSGI 协议的。

2.2.7 参考资料

□ Python CGI:https://www.the5fire.com/python-project6-cgi.html。

□ gunicorn-sync 源码:https://github.com/benoitc/gunicorn/blob/master/gunicorn/workers/sync.py#L176。

□ gunicorn-wsgi 部分代码:https://github.com/benoitc/gunicorn/blob/master/gunicorn/http/wsgi.py#L241。

□ PEP 3333 中文:http://pep-3333-wsgi.readthedocs.io/en/latest/。

□ PEP 3333 英文:https://www.python.org/dev/peps/pep-3333/。

□ Werkzeug 官网:http://werkzeug.pocoo.org/。

□ Werkzeug 中文文档:http://werkzeug-docs-cn.readthedocs.io/zh_CN/latest/。

2.2.8 扩展阅读

□ ASGI 英文文档:https://channels.readthedocs.io/en/latest/asgi.html。

□ ASGI 中文翻译:https://blog.ernest.me/post/asgi-draft-spec-zh。

□ Django SSE:https://www.the5fire.com/message-push-by-server-sent-event.html。

2.3 Flask 框架

Flask 框架的官网是http://flask.pocoo.org/。

上一节中,我们讲了两种提供Web 服务的方式:一是直接通过socket 来处理HTTP 请求;二是通过实现WSGI Application 部分的协议。

基于这两种方式,我们完全可以自己写一个框架,或者抛开框架这个概念来实现自己的Web服务。从理论的角度来说,这没有任何问 题。但是考虑到我们已经进入现代化阶段,通过原始的方式除了增加开发成本之外,没有任何益处。我们需要更完善的脚手架帮我们把项目结构搭出来、封装好HTTP 协议、处理session 和cookie 等内容,让我们能够更加专注在项目本身的业务上。

2.3.1 入门推荐

在Python 中,有很多微型框架可供选择,比如web.py 和bottle,但是如果让我给新手推荐一个易上手的框架,我会建议他先用Flask。原因很简单,所有微型框架的特点就是小,只提供核心能力,这意味着很容易上手,很容易掌握。但是在此之后呢?无论是从文档还是第三方插件的发展来看,Flask 都要优于其他微型框架。这也意味着上手Flask 之后,除了写一个入门的Demo页,你还可以学习/实践更多的东西。

不过对于专门做业务开发的,我们不能只考虑入门,毕竟大家都是专业的程序员,考虑更多的还是本章开头说到的几点。

□ 所选语言/框架/数据库是否应用广泛,是否有比较好的社区支持以及大量的用户反馈。

□ 语言/框架/数据库所提供的能力(功能)是否能够契合业务的需要,从而减少重复造轮子的工作量。

□ 自己团队的成员是否熟悉该框架和数据库,是否有人能够解决在框架使用中遇到的大部分问题。

作为微型框架来说,Flask 是很受关注的一个,这点从GitHub 的star 数也可窥得一斑。另外,也可以在GitHub 上看Flask 的更新频率、issues 和Pull Request 的数量,以及对issues 和Pull Request的处理速度。这些都能够看出这个框架的受欢迎程度以及活跃程度。

2.3.2 Flask 内置功能

Flask 的定位是微型框架,这意味着它的目标就是给你提供一个Web 开发的核心支持。如果你需要其他功能,可以使用第三方插件,或者自己写一个插件。

下面我们先看一下Flask 本身所提供的功能。

□ 内置的开发服务器和debug 模块。

□ 支持集成的单元测试。

□ RESTful 风格的请求分发机制。

□ 默认使用Jinja2 模板。

□ 安全cookie 的支持,用作客户端会话。

□ 100% 兼容WSGI 1.0 协议。

□ 基于Unicode。

□ 良好的文档。

基本的Web 开发能力都已经有了,这符合它的定位。但是,它并没有提供数据库相关的功能,比如ORM、权限控制。这么做的好处就是你可以选择自己熟悉的ORM 工具(比如SQLAlchemy、Peewee 和PonyORM),但是这要求你有足够的能力来掌握其他工具。

2.3.3 匹配需求

有了上面的了解之后,我们可以再来回顾需求。如果用Flask 来开发的话,我们在框架之外应该做些什么呢?

□ ORM 工具,你可以选择SQLAlchemy、PonyORM 或者Peewee。

□ ORM跟Flask 集成到一起的插件。当然,你也可以自己写。

□ admin 界面开发,可以选择第三方的Flask-Admin。

□ 用第三方的Flask-Admin 之后,你需要自己控制后台权限。

……

对于微型框架来说,我们的诉求并不多。但是对于我们需要实现的业务来说,如果选择微型框架,也就意味着需要写更多的代码,去攒更多的插件,这其实是对我们Python 能力的考验。

从另外一个层面来说,微型框架给开发者提供了很好的灵活性,没有太多的约束,这导致的一个问题是“1000 个开发者,就有1000 种使用微型框架的方式”。

2.3.4 总结

我在Flask 这块没有太多的实践经验,上面的那些仅供参考。

但是对于微型框架,我个人的看法是,如果开发者能力足够强,用微型框架很适合,它不会约束你。但是你需要考虑团队协作,需要定好统一的规范。

如果能力比较弱,微型框架就无法给你提供更多帮助。比方说,你可以选择任意一个ORM框架来跟Flask 结合。那么,对于一个初学者来说,选择哪个ORM 框架?确实有多个选择,但是选择太多反而让新手不知道应该怎么做。

不过总的来说,对于简单的需求,或者不是很复杂的项目,可以使用Flask,利用它轻量的优势。

2.4 Tornado 框架

Tornado 框架的官方网站是http://tornadoweb.org/。

2.4.1 印象

我在工作中使用Tornado 有5 年了。相对于上一节的Flask,我对Tornado 更熟悉。但是如果要总结Tornado 的特性的话,那也只是高性能。除此之外,没有什么可以介绍的。

不同于Flask 或者其他基于WSGI 的框架,Tornado 并不是基于WSGI 协议的框架。虽然它提供了WSGI 协议的支持,但是为了能够用到它的特性(异步和非阻塞),官方建议还是直接通过自带的HTTP Server 进行部署,而不是WSGI。我们在之前的实践中也是这么做的。

因为WSGI 协议是一个同步接口,所以Application 端只需要处理上游发送过来的environ(2.2 节有介绍)。当然,现在的WSGI Server (或者叫WSGI 容器)支持多种启动方式,比如Gunicorn可以通过gevent/greenlet/gthread 等来实现协程或者通过异步I/O 的方式来处理连接,但是这些都是WSGI Server 中的功能,跟Application 是完全隔离的。所以,这对Tornado 中的WSGI 协议的适配也没太多作用,无法利用Tornado 自身的特性,官方也不推荐使用WSGI 的方式部署。

2.4.2 内置功能

对比Flask 来说,Tornado 的特点十分明显,除了基本的Request 和Response 封装之外,就是基于IOLoop 的特性。我们只来看看Web 相关的功能。

□ tornado.web:基础的Request 的封装。

□ tornado.template:简单的模板系统。

□ tornado.routing:基础的路由配置。

□ tornado.escape:转码和字符串的操作。

□ tornado.locale:国际化的支持。

□ tornado.websocket:WebSocket 的支持。

从整体上看,它并不如Flask 丰富,比如在session 的实现、文档友好程度、第三方插件的丰富程度等方面。但是,这个差异其实是因为两个框架定位的不同,Flask 更多的是对业务需求的满足,而Tornado 针对的是高性能Web 系统。至于业务的部分,自己实现吧。

除了上面列出来的基础功能,Tornado 最大的卖点还是基于IOLoop(或者说基于Event Loop)的异步非阻塞的实现。就像文档中声称的:

By using non-blocking network I/O, Tornado can scale to tens of thousands of openconnections, making it ideal for long polling, WebSockets, and other applications that require along-lived connection to each user.

翻译一下就是:通过非阻塞的网络I/O,Tornado 能够支撑成千上万个连接,这使它很适合对每个用户都建立长连接的需求,无论是通过长轮询、WebSocket 还是其他应用。

这也是我们选择它的原因。虽然我们的业务场景并非长连接,但是它能够承担更多的并发量正是我们需要的。

2.4.3 总结

在Python 2.x 的环境中,基于Event Loop 模型的Tornado 确实很有卖点。只是在Python 3.x中,语言内部支持了Event Loop,这 导致更多的框架可以很容易地开发出异步非阻塞的模型。这对于Tornado 确实是一个挑战。

但是,新兴的框架必然还要经受生产环境的考验,积累大量经验之后,其他人才可能放心使用。而Tornado 基于多年的发展已经在生产环境中得到了证明,并且有大量的企业会分享出他们的最佳实践。

未来哪种异步非阻塞的框架更加流行不好断言,但是从技术知识上来讲都差不太多。

2.5 Django 框架

Django 框架的官网:https://www.djangoproject.com/。

我使用Django 的时间比Tornado 还久,从Java 开发转到Python 开发时,直接从Java 的SSH(Struts+Spring+Hibernate)框架逃离到了Django 上。一开始使用Django 的感觉就是,这玩意太轻便了,比SSH 轻太多了。但没想到的是,在Python 社区中,Django 也算是比较重的框架了。

对于Django 框架,我的评价是,这是一个全功能的Web 开发框架。Web 开发所需要的一切它都包含了,你不需要去选择,只需要去熟悉,然后使用。

2.5.1 新手友好程度

对于前面介绍的两个框架——Flask 和Tornado,你从文档上直接把代码复制到server.py 文件中,然后执行python server.py 命令,就能看到界面。但是在Django 中,你会发现新手需要写好多代码才能看到界面。所以,大部分人觉得Django 对新手并不友好,或者说它有一定的门槛。

其实换个角度来看,你在写完Flask 和Tornado 的第一个Python 文件之后,接下来应该怎么做呢?就拿开发一个Blog 来说吧,你要怎么组织代码和项目结构呢?这些搞定之后,接下来要怎么选择一个适合你的ORM,然后把它配置到项目中?配置文件要怎么共享给其他模块?要怎么来处理用户登录?如果要放到外网访问的话,怎么保证系统安全?

面对这些,初学者可能会完全懵掉。

这些都是实际开发中要面对的问题。我的看法是,微型框架让你能够快速做些小应用,比如就是几个页面,整个项目只需要三四个Python 文件(模块)就搞定了。稍微大一些的项目,那就要考验Python 能力和代码组织或者设计能力了。这对于初学者来说,并不是那么友好。

而Django 提供了更完善的新手指导。一开始可能无法写一个文件就让代码跑起来,但是这一套新手招式打完之后,你可以基于此来完成一个稍微大点的项目。并且,Django 也会帮你处理好我上面提到的那些问题。

2.5.2 内置功能

一开始我也说到了,Django 是作为全功能的Web 开发框架出现的。这意味着它提供的可能远多于你想要的。我们简单列出常用的功能:

□ HTTP 的封装——request 和response

□ ORM

□ admin

□ Form

□ template

□ session 和cookie

□ 权限

□ 安全

□ cache

□ Logging

□ sitemap

□ RSS

上面列出了常用的部分,也是我们这次需求涉及的部分。Django 在此之外还提供了更多功能,比如i18n(多语言的支持)和GIS 的支持等。

我的观点是,如果你掌握了Django,就掌握了Web 开发中的大部分知识,因为这个框架涉及Web 开发的所有层面。

2.5.3 总结

对于Web 开发来说,尤其是内容驱动的项目,我推荐用Django 来做,因为即便你选择了Flask或者其他微型框架,然后把插件拼装起来,最终也是基于松散的配置做了一个类Django 的框架,还不如Django 在整体上的整合性强。

Django 作为一个从新闻系统生成环境中诞生的框架,是直接面向企业级开发的。无论是从社区的发展还是整体的生态(比如Django 大会和Django 基金会)来看,Django 都是十分成熟的框架,并且有十分完善的周边生态。

另外,我们也可以看看基于它开发的那些耳熟能详的产品,如Instagram、Disqus、Sentry、OpenStack 等,这些都证明了Django 在企业开发中的地位。

2.5.4 参考资料

□ 官网教程:https://www.djangoproject.com/start/overview/。

□ Django 第三方插件:https://djangopackages.org/。

□ 基于Django 的网站:https://www.djangosites.org/。

2.6 本章总结

对于选择什么样的框架和技术栈来支撑业务,我们首先要想到的是,整套技术是否能够匹配业务需求。上面我提到的几个框架都有各自的特点和所针对的领域。我们要做的还是带着需求去找框架,找技术栈,而不是拿着框架来做需求。有时,我们需要的就是做一个简单的页面,这种场景下拿Django 过来可以做,但是会显得有点束手束脚。

所以拿需求去找框架,选择合适的框架,而不是功能最全的框架或者最先进的框架。

另外,还需要考虑的问题就是,你的团队成员是否能够掌控住这个框架。毕竟在实际开发中,我们要在有限的时间内来落实产品经理的需求,最终上线。你们可能没有太多时间来学习新的框架,这也是技术选型需要考虑的因素。

当然,说句题外话,好的技术团队应该不断地增加优秀技术的储备,而不是“书到用时方恨少”。

经过这一轮的对比,在开发内容型产品时,显然用Django 更加合适。基于Django 内置的功能,我们可以省去自己造轮子的时间。

目录

  • 序一
  • 序二
  • 前言
  • 第一部分 初入江湖
  • 第1章 需求
  • 第2章 框架基础和技术选型
  • 第3章 Django小试牛刀
  • 第二部分 正式开发
  • 第4章 进入开发
  • 第5章 奠定项目基石:Model
  • 第6章 开发管理后台
  • 第7章 开发面向用户的界面
  • 第8章 引入前端样式框架Bootstrap
  • 第9章 完成整个博客系统
  • 第三部分 第三方插件的使用
  • 第10章 使用第三方插件增强管理后台
  • 第11章 使用django-rest-framework
  • 第四部分 上线前的准备及线上问题排查
  • 第12章 调试和优化
  • 第13章 配置MySQL和缓存
  • 第14章 上线前的准备
  • 第15章 升级到Django 2.0
  • 第16章 最后总结
  • 附录A 使用Fabric 2.0
  • 附录B 使用uWSGI来启动Django程序
  • 附录C Sentry安装和配置
  • 附录D 评论验证码功能 
  • 附录E 通过signal来解耦代码
  • 附录F 实现文章置顶的几种方案
  • 附录G 以腾讯云为例演示部署流程