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:password@localhost: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

总结:
Django,Flask 放心用,在部署到服务器上时,合理范围内,多开进程,多开线程,榨干服务器的硬件性能
Tornado 小心用,耗时的操作一定要实现异步非阻塞方法,不然整个服务器都会宕掉
  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值