Python三大框架对比,与同步阻塞问题
一般主流Python Web的框架莫过于 Flask,Django,Tornado 这三种,熟练掌握这三种框架做Python 后端开发基本就没有什么大的问题.
Flask:
-
优点:小巧简单易扩展
-
缺点:大型高并发网站不适合,解耦稍难,性能不足
Flask同步阻塞请求问题:
旧版Flask**(1.0以下)**没有解决同步阻塞问题,一个url在做耗时操作时,无法处理其他的url请求,需要使用gevent解决
from flask import Flask
import time
from gevent import monkey
from gevent.pywsgi import WSGIServer
monkey.patch_all()
app = Flask(__name__)
app.config.update(DEBUG=True)
@app.route('/frist')
def frist_request():
print('开始执行frist')
time.sleep(10)
print('结束执行frist')
return 'Hello Frist!'
@app.route('/second')
def second_request():
print('执行second')
return 'Hello Second!'
if __name__ == '__main__':
http_server = WSGIServer(('127.0.0.1', 5000), app)
http_server.serve_forever()
新版Flask**(1.0以上)**已经解决同步阻塞问题,不需要用gevent
from flask import Flask
import time
app = Flask(__name__)
app.config.update(DEBUG=True)
@app.route('/frist')
def frist_request():
print('开始执行frist')
time.sleep(10)
print('结束执行frist')
return 'Hello Frist!'
@app.route('/second')
def second_request():
print('执行second')
return 'Hello Second!'
if __name__ == '__main__':
app.run()
所以使用Flask框架时请使用最新版,可以不用考虑同步阻塞问题,莫作没必要的死
Flask同时请求同一个Url的阻塞问题:
不用担心,不是Flask问题,有可能是浏览器问题,如果每次请求的url、参数等所有元素都是相同的,浏览器会将其视为同一个请求。因此当前一个请求没有结束的时候,后一个一模一样的请求也不能产生结果,开两个浏览器同时请求同一个url接口试试
Django:
- 优点:大而全,使用简单
- 缺点:orm拖慢速度,而且还不怎么好用,group by很难搞,业务逻辑简单时可以使用,复杂的直接使用原生的sql,更快,更容易理解
Django如何使用原生的sql:
# 导入连接类
from django.db import connection
# 获取游标对象
cursor = connection.cursor()
# 执行数据库语句
cursor.execute("""select * from mytable;""")
# 获取所有的查询数据
result = cursor.fetchall()
Django同步阻塞请求问题:
不用担心,Django中没有这个问题,Django是多线程的,一个url中如果有耗时操作,同时可以处理其他的url请求
Tornado:
-
优点:少而精,性能优越,用来对付C10K问题
-
缺点:难度大,使用不方便,异步非阻塞方法要自己写(WTF),对开发人员要求高一点
Tornado同步阻塞问题:
Tornado是单线程的,一个url中如果有耗时操作,这时无法处理其他的url请求,服务器会宕掉.
Tornado实现异步非阻塞方法:
方法一:使用协程 @gen.coroutine
import time
import tornado
from tornado.web import RequestHandler
from tornado import gen
class FristHandler(RequestHandler):
@gen.coroutine
def get(self):
"""GET请求"""
print('开始测试1')
result = yield self.sleep_handler()
print('结束测试1')
self.write(result)
@gen.coroutine
def sleep_handler(self):
"""模拟延时操作"""
# 这个库方法是阻塞的,不支持异步操作,不要使用
# time.sleep(10)
# 使用这个方法代替上面的方法模拟 I/O 等待的情况,这个库方法是异步非阻塞的,可以使用
yield gen.sleep(10)
return '测试1'
class SecondHandler(RequestHandler):
def get(self):
print('开始测试2')
self.write('测试2')
if __name__ == "__main__":
application = tornado.web.Application([
(r"/frist", FristHandler),
(r"/second", SecondHandler),
])
application.listen(8000)
tornado.ioloop.IOLoop.instance().start()
方法二:使用线程池
import time
from concurrent.futures import ThreadPoolExecutor
import tornado
from tornado import gen
from tornado.web import RequestHandler
class FristHandler(RequestHandler):
@gen.coroutine
def get(self):
"""GET请求"""
print('开始测试1')
# 开启线程池
executor = ThreadPoolExecutor(4)
# 将耗时的操作提交到线程池中
task = yield executor.submit(self.sleep_handler)
print(task)
print('结束测试1')
self.write('测试1')
def sleep_handler(self):
"""模拟耗时操作"""
time.sleep(10)
return '耗时操作完成'
class SecondHandler(RequestHandler):
def get(self):
print('开始测试2')
self.write('测试2')
if __name__ == "__main__":
application = tornado.web.Application([
(r"/frist", FristHandler),
(r"/second", SecondHandler),
])
application.listen(8000)
tornado.ioloop.IOLoop.instance().start()
方法三:使用 Celery
Celery 是基于Python开发的分布式任务队列。它支持使用任务队列的方式在分布的机器/进程/线程上执行任务调度,它由四部分组成 Celery Client (客户端) , Message Broker(消息中间件) , Celery Worker (服务端) , Task Result (任务结果)
但是由于Celery本身不提供消息服务,但是可以方便的和第三方提供的消息中间件集成,比如 RabbitMQ , Redis , MongoDB 等.我们这里使用Redis ,所以整个框架如下图所示
在Linux系统中安装redis
# 安装redis
sudo apt-get install -y redis
# 启动
sudo service redis start
在tornado项目中安装redis工具
pip install redis
在tornado项目中安装Celery工具
pip install celery
server.py : 启动文件(不需要返回结果的方式)
import tornado
from tornado.web import RequestHandler
import asynchandler
class FristHandler(RequestHandler):
def get(self):
"""GET请求"""
print('开始测试1')
# 调用异步操作
asynchandler.sleep_handler.apply_async()
print('结束测试1')
class SecondHandler(RequestHandler):
def get(self):
print('开始测试2')
self.write('测试2')
if __name__ == "__main__":
application = tornado.web.Application([
(r"/frist", FristHandler),
(r"/second", SecondHandler),
])
application.listen(8000)
tornado.ioloop.IOLoop.instance().start()
server.py : 启动文件(需要实时返回结果的方式)
from concurrent.futures import ThreadPoolExecutor
import tornado.ioloop
from tornado import gen
from tornado.web import RequestHandler
import asynchandler
class FristHandler(RequestHandler):
@tornado.gen.coroutine
def get(self):
"""GET请求"""
print('开始测试1')
# 调用异步操作
async_result = asynchandler.sleep_handler.apply_async()
# 开启线程池
executor = ThreadPoolExecutor(4)
# 用线程轮询异步操作的结果
result = yield executor.submit(self.get_result, async_result)
# 获取轮询的结果
print(result)
print('结束测试1')
self.write('测试1')
def get_result(self, async_result):
"""
轮询异步操作的结果
:param async_result:
:return:
"""
while True:
# 判断异步操作是否结束
if async_result.ready() is True:
# 返回异步操作的结果
return async_result.result
class SecondHandler(RequestHandler):
def get(self):
print('开始测试2')
self.write('测试2')
if __name__ == "__main__":
application = tornado.web.Application([
(r"/frist", FristHandler),
(r"/second", SecondHandler),
])
application.listen(8000)
tornado.ioloop.IOLoop.instance().start()
# 这个方法用异步操作完之后,还要用线程轮询它的结果,感觉没什么卵用,还不如用方法二,直接上线程池来得简单,要是有好的方法,欢迎留言
asynchandler.py : 用来放需要异步的操作
import time
from celery import Celery
# 连接消息中间件(redis服务器)
# 如果中间件服务器设置了用户名密码
# 连接格式为:redis://username:[email protected]:6379/0
# 最后的 0 表示redis中第0个数据库
app = Celery(broker="redis://@localhost:6379/0")
# 设置任务结果(task result)的放置地址
app.conf.CELERY_RESULT_BACKEND = "redis://@localhost:6379/0"
@app.task(name='task.sleep_handler')
def sleep_handler():
# 模拟延时操作
time.sleep(10)
return '延时操作结束'
if __name__ == '__main__':
app.start()
启动 Celery Worker 监听任务队列(消费者会从任务队列中取走一个个的task并执行)
# 启动 Celery
celery -A asynchandler worker --loglevel=info
# 启动成功效果
[2019-03-19 10:22:57,211: INFO/MainProcess] Connected to redis://localhost:6379/0
[2019-03-19 10:22:57,233: INFO/MainProcess] mingle: searching for neighbors
[2019-03-19 10:22:58,265: INFO/MainProcess] mingle: all alone
启动 Tornado 项目
python server.py
访问测试
127.0.0.1:8000/frist
127.0.0.1:8000/second
三者比较
方法 | 优点 | 缺点 | 推荐指数 |
---|---|---|---|
协程@gen.coroutine | 简单,优雅 | 严重依赖第三方异步库 | ★★☆☆☆ |
线程池 | 简单 | 大量使用线程,影响性能 | ★★★☆☆ |
Celery | 性能好 | 操作复杂 | ★★★★☆ |
上面Tonado项目使用的Python环境
amqp==2.4.2
anyjson==0.3.3
billiard==3.6.0.0
celery==4.3.0rc2
certifi==2019.3.9
cffi==1.12.2
datadispatch==1.0.0
erlang-py==1.7.8
kombu==4.4.0
pycparser==2.19
PyMySQL==0.9.3
pytz==2018.9
redis==3.2.1
six==1.12.0
tornado==6.0.1
torndb-for-python3==0.2.3
vine==1.2.0