Python Web框架Tornado的异步处理代码示例

1. What is Tornado

Tornado是一个轻量级但高性能的Python web框架,与另一个流行的Python web框架Django相比,tornado不提供操作数据库的ORM接口及严格的MVC开发模式,但可以提供基本的web server功能,故它是轻量级的;它借助non-blocking and event-driven的I/O模型(epoll或kqueue)实现了一套异步网络库,故它是高性能的。

Tornado的轻量级+高性能特性使得它特别适用于提供web api的场合,使用合理的话,其非阻塞+异步能力可以应对C10K问题。

需要特别注意的是,由于Python的GIL导致多线程总是单核执行的”特点”,tornado处理http请求时,若某个请求的后端响应有阻塞现象(如从DB或磁盘读数据导致处理时间很长),则会导致其他http请求也被block,这会严重拖累tornado在高并发场景下的性能。

幸运的是,tornado提供了异步处理请求的能力,在异步模式下,我们可以通过传入回调函数或借助tornado提供的tornado.gen.coroutine装饰器,使得tornado内部的io loop在等待当前请求响应结果的同时,仍然可以接受其它的http请求,这样就避免了某个耗时操作影响tornado的处理能力。

2. 如何在tornado框架下编写异步处理代码

Tornado官网文档给出了几个简单的异步代码示例,不过说实话,代码太过简单(都是在某个uri的handler类的get或post函数中展现了基本的异步语法),没有多大的实战意义。

在实际项目中,复杂的处理逻辑不可能都堆在get或post函数中,而是会封装在其它class中供handler类的get或post函数调用。所以,本文给出一个稍复杂的实例,旨在说明如何在其它class的函数中实现异步处理逻辑,以实现http请求异步化处理的目的。

假设现在的需求是用tornado实现一个web server,支持名为cityhotel的uri方法,当client通过http GET请求访问该uri时,web server根据query参数指定的城市,去请求存放hotel详细数据的另一个后端api,进行业务处理后返回某个连锁hotel在该城市的所有门店给client。
假设client GET请求的url格式为:http://host/api/hotel/cityhotel?city=xxx
再假设存放hotel详细数据的后端api接口为:http://hotel_backend/getCityHotels?city=xxx

根据上面的场景,由于我们用tornado实现的web server接到client的请求后,还要去另一个API接口请求基础数据,而后者在返回前,tornado会block,所以,这种场景下,tornado最好以异步方式请求那个提供基础数据的API,避免不可控的后端拖累tornado的响应性能。

根据上面描述的业务需求,下面的代码示范了如何通过异步方式处理业务处理。

模块入口文件(main.py):

#!/bin/env python

import tornado.ioloop
import tornado.web
import tornado.gen
import hotelcore


class CityHotelHandler(tornado.web.RequestHandler):
    @tornado.gen.coroutine
    def get(self):
        ## parse query params
        params = {}
        keys = ['city']
        for key in keys:
            value = self.get_query_argument(key)
            params[key] = value
        (status, rsp) = yield hotelcore.HotelApiHandler.get_city_hotel(params['city'])
        if 200 == status:
            self.set_header('content-type', 'application/json')
            self.finish(rsp)
        else:
            self.set_status(404)
            self.finish()



def main():
    app_inst = tornado.web.Application([
        (r'/api/hotel/cityhotel', CityHotelHandler),
    ], compress_response = True)

    app_inst.listen(8218)
    tornado.ioloop.IOLoop.current().start()


if '__main__' == __name__:
    main()

处理业务逻辑的module封装在hotelcore.py文件中,代码如下:

#!/bin/env python
#-*- encoding: utf-8 -*-

import json

from tornado import gen
from tornado import httpclient


class HotelApiHandler(object):
    _cfg_dict = {
        'api_host' : 'api.hotelbackend.com',
    }


    @classmethod
    @gen.coroutine
    def get_city_hotel(cls, city):
        ret = yield cls._parallel_fetch_city_hotel(city)
        raise gen.Return((200, ret))


    @classmethod
    @gen.coroutine
    def _parallel_fetch_city_hotel(cls, city):
        base_url = 'http://%s/v1/getCityHotel' % (cls._cfg_dict['api_host'])
        ## hote type: 1=normal room; 2=deluxe room
        hotel_type = {'normal': 1, 'deluxe': 2}
        urls = []
        for v in hotel_type.values():
            api_url = '%s?city=%s&level=%s' % (base_url, city, v)
            urls.append(api_url)
        ## issue async http request
        http_clt = httpclient.AsyncHTTPClient()
        rsps_dict = yield dict(normal_room = http_clt.fetch(urls[0]), deluxe_room = http_clt.fetch(urls[1]))
        city_hotel_info = cls._parse_city_hotel(rsps_dict, city)
        ret = { }
        if len(city_hotel_info):
            ret['errno']  = 0
            ret['errmsg'] = 'SUCCESS'
            ret['data']   = city_hotel_info
        else:
            ret['errno']  = 1
            ret['errmsg'] = 'Service Not Found at This City'
            ret['data']   = ''
        raise gen.Return(ret)


    @classmethod
    def _parse_city_hotel(cls, rsp_dict, city):
        city_hotel_info = {}
        for hotel_level, rsp in rsp_dict.items():
            rsp_json = json.loads(rsp.body)
            datas = rsp_json['data']
            for city_id, city_detail in datas.items():
                name = city_detail['name']
                if city in name:
                    city_hotel_info[hotel_level] = city_detail
                    break
        return city_hotel_info

对以上代码的几点补充说明:

  • 编写tornado异步处理代码需要对Python的decorator语法和generator/yield语法比较熟悉
  • tornado提供的装饰器@gen.coroutine表明被装饰函数是个异步处理函数,该函数的调用不会block tornado主线程
  • 被@gen.coroutine装饰的函数中,需要异步执行的耗时函数用yield来调用,yield本身返回的是个generator,结合@gen.coroutine后,它返回一个tornado定义的Future类型的对象
  • yield调用的函数在执行过程中,进程控制权会返给主线程,故即使该函数需要较长运行时间,tornado的主线程也可以继续处理其它请求
  • 在Python 2.x版本的语法中,generator中不允许用return返回函数的返回值,必须用tornado提供的raise gen.Return(ret)达到返回的目的,这是个比较tricky的方法
  • yield返回的Future对象可以通过调用body属性来获取通过yield调用的函数的返回值
  • 只要结合上述几点理解了@gen.coroutine和yield在tornado异步编程中的语法意义,那么,写出复杂的异步调用代码与编写实现相同功能但tornado整体性能无法保证的同步调用代码相比,实现难度就几乎不存在了。

上面的代码很多语法细节没有展开,希望实现思路能帮助到有缘人。^_^

参考资料

  1. Tornado Doc: User’s guide
  2. Book: Introduction to tornado chapter 5. asynchronous web services
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,这是一个关于 Python Web 框架性能对比以及如何使用 Gunicorn 和 Gevent 提高 Python Web 框架性能的问题。下面是我的回答: Python Web 框架性能对比 Python 有很多优秀的 Web 框架,如 Django、Flask、Tornado、Bottle 等等。这些框架都有自己的特点和优缺点,但是在性能方面却有所不同。下面是一些常见 Python Web 框架的性能对比数据: 框架 | Requests/s -----|---------- Flask| 673.24 Django| 372.77 Bottle| 3,040.36 Tornado| 3,714.29 从上表可以看出,Tornado 和 Bottle 性能比较突出,而 Django 和 Flask 的性能稍逊一些。但是这些数据并不是绝对的,实际性能还需要根据具体的应用场景和实现方式进行测试和评估。 使用 Gunicorn 和 Gevent 提高 Python Web 框架的性能 Gunicorn 和 Gevent 都是 Python Web 服务器,它们可以与常见的 Python Web 框架配合使用,提高 Web 应用的性能。 具体来说,Gunicorn 是一个使用 Python 编写的 WSGI HTTP 服务器,可以用于部署 Django、Flask 等 Web 应用。Gunicorn 使用多进程的方式来提高并发处理能力,可以根据系统的 CPU 核数来设置进程数,同时还支持异步处理和负载均衡等特性。 Gevent 是一个基于协程的 Python 网络库,可以用于编写高性能的网络应用程序。Gevent 可以与 Python Web 框架配合使用,使用协程来处理请求,可以显著提高 Web 应用的并发处理能力和响应速度。 下面是一个使用 Gunicorn 和 Gevent 提高 Flask Web 应用性能的示例代码: ``` python from gevent import monkey monkey.patch_all() from flask import Flask app = Flask(__name__) @app.route('/') def hello(): return 'Hello, World!' if __name__ == '__main__': from gunicorn.app.base import BaseApplication from gunicorn.six import iteritems class StandaloneApplication(BaseApplication): def __init__(self, app, options=None): self.options = options or {} self.application = app super(StandaloneApplication, self).__init__() def load_config(self): config = dict([(key, value) for key, value in iteritems(self.options) if key in self.cfg.settings and value is not None]) for key, value in iteritems(config): self.cfg.set(key.lower(), value) def load(self): return self.application options = { 'bind': '0.0.0.0:8000', 'workers': 4, } StandaloneApplication(app, options).run() ``` 上面的代码使用了 Gunicorn 和 Gevent 来启动一个 Flask Web 应用,同时设置了 4 个 worker 进程来处理请求。这样可以显著提高 Web 应用的性能和并发处理能力。 希望这个回答对你有帮助!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值