zoukankan      html  css  js  c++  java
  • 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
  • 相关阅读:
    html的输出&,空格,大小于号
    html如何修改hr水平直线的粗细
    LODOP指定window默认打印机和临时默认打印机
    微软面试题: 找出二叉树上任意两个结点的最近共同父结点。
    说说自己对hibernate一级、二级、查询、缓存的理解。
    MySql中添加用户,新建数据库,用户授权,删除用户,修改密码
    修改MySQL的默认密码的四种小方法
    java中Scanner的nextLine()和next()的区别
    JAVA中String字符串比较equals()和equalsIgnoreCase()的区别
    HashMap与HashTable的区别
  • 原文地址:https://www.cnblogs.com/llguanli/p/8406251.html
Copyright © 2011-2022 走看看