zoukankan      html  css  js  c++  java
  • tornado with MySQL, torndb, django model, SQLAlchemy ==> JSON dumped

    现在,我们用torndo做web开发框架,用他内部机制来处理HTTP请求。传说中的非阻塞式服务。

    整来整去,可谓之一波三折。可是,无论怎么样,算是被我做成功了。

    在tornado服务上,采用三种数据库模型--》

    1)torndb

    2)django model

    3)SQLAlchemy model

        都同时输出相应的JSON,做API服务。(一个比一个强大的ORM model)

    都同时实现了一样的功能

    注意:要说明的一点是,在数据库建立模型的时候,是用到的django model建立的数据库模型。所以在使用torndb(SQL 操作语言的ORM) 和 django model的时候,非常简单操作,直接引用就可以使用了。而当再利用SQLAlchemy的时候,将会不得不将django的models 改写成 SQLAlchemy 支持的格式

    一、先来说说torndb

    使用很简单。

    请看代码

    import torndb
    from tornado.options import define, options
    
    
    # define 完成后,同时生成一个options里面的属性,在下面方便 torndb.Connection
    define("port", default=8000, help="run on the given port", type=int)
    define("mysql_host", default="127.0.0.1:3306", help="database host")
    define("mysql_database", default="center", help="database name")
    define("mysql_user", default="root", help="database user")
    define("mysql_password", default="root", help="database password")
    define("secret", default="justsecret", help="secret key")
    
    db = torndb.Connection(
            host=options.mysql_host, database=options.mysql_database,
            user=options.mysql_user, password=options.mysql_password)
    
    
    # 设定JSON跳出规则,配合torndb取数据用
    
    def __default(obj):
        if isinstance(obj, datetime):
            return obj.strftime('%Y-%m-%d %H:%M:%S')
        elif isinstance(obj, date):
            return obj.strftime('%Y-%m-%d')
        else:
            raise TypeError('%r is not JSON serializable' % obj)
    
    def json_dumps(s):
        """
    
        """
        return json.dumps(s,ensure_ascii=False,default=__default)

    然后,看看响应的文档,

    对http://localhost:8000/testpage1 的HTTP请求会导入到下面的例程中,

    @require_basic_auth
    class TestPage1Handler(BaseHandler):
        """This is a test page to show the asker's utmost parent's all details
        """
        def post(self, **kwargs):
            ret = {'ret':'0','msg':''}
            parent_asker = kwargs['a']
            #boss_profile = db.get("SELECT * from sometable where username=%s", parent_asker)
            boss_profile = db.query("SELECT * FROM kms_sgroup")
            ret['result'] = { parent_asker : boss_profile }
            self.write(json_dumps(ret))
            return

    注意: db.query("") 跳出的是一个列表,多组数据。多row

    db.get("")跳出的是单个元素,单组数据

    class Application(tornado.web.Application):
        def __init__(self):
            handlers = [
                # 测试用torndb model用的URL
                (r"/testpage1", TestPage1Handler),            # 通过       测试结果10000次请求,花费时间10.3秒  每秒处理970次请求
                # 测试 django model 用的URL
                (r"/testpage2", TestPage2Handler),            # 通过       测试结果10000次请求,花费时间18.6秒  每秒处理538次请求
                # 测试 sqlalchemy model 用的URL
                (r"/testpage3", TestPage3Handler),            # 通过       测试结果10000次请求,花费时间24.1秒  每秒处理415次请求
            
            ]
    
            settings = dict(
                cookie_secret="__TODO:_GENERATE_YOUR_OWN_RANDOM_VALUE_HERE__",
                debug=True,
            )
            tornado.web.Application.__init__(self, handlers, **settings)

    二、来看看tornado 和 django model的配合

    ( tornado 的center.py 文件放在django 的项目下,和 django 的 manage.py 同级 ,  此文件放在 ./center.py )

    # let's get userprofile with django model instead! :)
    #from api.models import SGroup
    # 下面的这个引入django默认目录的环境的行为是必须的!
    import os os.environ['DJANGO_SETTINGS_MODULE'] = 'project.settings'
    # 这里的SGroup 就是生成数据模型的时候用的。django model生成之后,我们才选择的torndb或者django model ORM的
    from api.models import SGroup as SG from django.core import serializers @require_basic_auth class TestPage2Handler(BaseHandler): def post(self, **kwargs): ret = {'ret':'0','msg':''} parent_asker = kwargs['a'] boss_profile = SG.objects.all() # the type of 'data' is string data = serializers.serialize("json", boss_profile) # make 'data' a list data = json.loads(data) # to get data[0]['fields'] . that's what we need beyond the model's "pk" and "model" property of the object data # data[0]['fields'] is a dict ret['result'] = { parent_asker : data[0]['fields'] } self.write(json_dumps(ret)) return

    django 的ORM 的自带的serializers 在格式化JSON的时候,有点小区别。

    这里式serializers  的官方地址:https://docs.djangoproject.com/en/1.6/topics/serialization/

    这里是默认会给出的结果,

    JSON

    When staying with the same example data as before it would be serialized as JSON in the following way:

    [
        {
            "pk": "4b678b301dfd8a4e0dad910de3ae245b",
            "model": "sessions.session",
            "fields": {
                "expire_date": "2013-01-16T08:16:59.844Z",
                ...
            }
        }
    ] 

    貌似长的跟想要的不一样,我们只需要fields里面的东西。这就是为什么上面我做了

    data = serializers.serialize("json", boss_profile) 
            # make 'data' a list
            data = json.loads(data)
            
            # to get data[0]['fields'] . that's what we need beyond the model's "pk" and "model" property of the object data
            # data[0]['fields'] is a dict
            ret['result'] = { parent_asker : data[0]['fields'] }

    这个操作。这样,我们就会得到需要的东西了。一般情况下,字段"pk", "model" 是我们不需要的。fields里面的才是数据库里面保存的鲜活的数据。

    三、SQLAlchemy 来配合tornado 表达MySQL

    这个才是重头戏,喜欢DIY还有有特定需求的童鞋可以使用这样的方法,就是什么东西都要自己写。很爽!

    首先是建立让SQL Alchemy 与 SQL 会话。话说回来了,SQLAlchemy有自己的格式的model,而django有自己的一套。而纯SQL语言将是万能的,因为纯SQL没有任何封装。

    所以,为了使用SQLAlchemy,我们需要针对数据结构,封装一个SQLAlchemy的数据模型。

    先看看此前的django 的model, 次文件为 ./api/models.py

    from django.db import models
    class
    SGroup(models.Model): """ Subscriber & Group """ # 车机组名称 name = models.CharField(_(u"Subscriber Group Name"), max_length=100, unique=True) is_active = models.BooleanField(_(u'Active Status'), default=True, help_text=_('Designates whether this user should be treated as ' 'Active. Unselect this instead of deleting accounts.')) date_created = models.DateTimeField(_(u'Account Created'), auto_now_add=True) class Meta: verbose_name = _(u'Subscriber Group') verbose_name_plural = _(u'Subscriber Groups') db_table = 'sometable' ordering = ['-id'] def __unicode__(self): return self.name

    再看看我们封装成SQLAlchemy后的model类型,此文件为 ./sqla/models.py

    #coding: utf-8
    from sqlalchemy import (
        ForeignKey,
        Column,
        Index,
        Integer,
        String,
        Boolean,
        Text,
        Unicode,
        UnicodeText,
        DateTime,
        )
    
    from sqlalchemy.ext.declarative import declarative_base
    Base = declarative_base()
    
    
    
    import datetime
    class SGroup(Base):
        """ Subscriber & Group
    
    
        """
    
        __tablename__ = 'sometable'
        id = Column(Integer, primary_key=True)
        name = Column(String(100), unique=True)
        is_active = Column(Boolean, default=True)
        date_created = Column(DateTime, default=datetime.datetime.utcnow)
        
        #name = models.CharField(_(u"Subscriber Group Name"), max_length=100, unique=True)    
        #is_active = models.BooleanField(_(u'Active Status'), default=True,
        #    help_text=_('Designates whether this user should be treated as '
        #                'Active. Unselect this instead of deleting accounts.'))
    
        #date_created = models.DateTimeField(_(u'Account Created'), auto_now_add=True)
    
        #class Meta:
            #verbose_name = _(u'Subscriber Group')
            #verbose_name_plural = _(u'Subscriber Groups')
    
        #    db_table = 'sometable'
        #    ordering = ['-id']
        

    新建数据模型后,我们再来看看如何修改tornado 服务 center.py的代码,添加这些进去

    # 先建立sqlalchemy 到 数据库的链接会话
    from sqlalchemy import create_engine
    from sqlalchemy.orm import sessionmaker
    from sqla.models import SGroup
    mysql_engine = create_engine('mysql://root:root@localhost:3306/center?charset=utf8',encoding = "utf-8",echo =True)  
    DB_Session = sessionmaker(bind=mysql_engine)
    session = DB_Session()
    
    # 将SQLAlchemy 对象打包成JSON格式
    # 这里重写json.dump(data, cls=json.JSONEncoder)
    from sqlalchemy.ext.declarative import DeclarativeMeta def new_alchemy_encoder(): _visited_objs = [] class AlchemyEncoder(json.JSONEncoder): def default(self, obj): fields = {} if isinstance(obj.__class__, DeclarativeMeta): # don't re-visit self if obj in _visited_objs: return None _visited_objs.append(obj) # an SQLAlchemy class for field in [x for x in dir(obj) if not x.startswith('_') and x != 'metadata']: fields[field] = obj.__getattribute__(field) # a json-encodable dict else: # 这里是我新添加的,用来处理JSON对象中,如果存在时间类型的话 # 将按这种方式返回 if isinstance(obj, datetime): return obj.strftime('%Y-%m-%d %H:%M:%S') if isinstance(obj, date): return obj.strftime('%Y-%m-%d') return fields return json.JSONEncoder.default(self, obj) return AlchemyEncoder @require_basic_auth class TestPage3Handler(BaseHandler): """This is a test page to show the asker's utmost parent's all details """ def post(self, **kwargs): ret = {'ret':'0','msg':''} #name_queue = get_outersystem_org(kwargs) parent_asker = kwargs['a'] data = session.query(SGroup).all() # 使用新建的JSON encoder对象,作为json.dumps的标准 来dumps数据出去 # 请注意:这里是 json.dumps 与 torndb的例子中的 json_dumps不同 self.write(json.dumps(data, cls=new_alchemy_encoder() ) ) return

    好了,SQLAlchemy的道路比较曲折,什么都得重写,而且比较深入,但是可以高度定制!有特殊要求的可以使用SQLAlchemy

    我们来测试一下

    开始测试

     

    测试用的脚本写好了。

    testauth.py

    import urllib
    import urllib2
    import base64
    import time
    
    
    
    def url_post(url):
        """Let's do an analogical browser authentication loop
    
        """
        TOKEN_KEY_OS1="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
        token_encoded = base64.b64encode("level1:"+level2:"+TOKEN_KEY_OS1)
        token_encoded = token_encoded.replace("
    ","",1)
        token = token_encoded
    
        postDict = {
                #查询指定username的客户信息
                #"username":"",
    
        }
    
      
        postData = urllib.urlencode(postDict)
        request = urllib2.Request(url, postData)
        request.get_method = lambda : 'POST'
        basic = "Basic "+ token
        # 这里是实现了一个HTTP header 的加密验证等服务
        request.add_header('Authorization', basic)
        response = urllib2.urlopen(request)
    
        #return response.readlines() , response.headers.dict
        return response

    testrequest.py

    from testauth import url_post
    import datetime
    
    def mega_attack(number=100000):
    
        start = datetime.datetime.now()
        for i in range(number):
            r = url_post('http://localhost:8000/testpage1')
            print r.readlines()
        end = datetime.datetime.now()
        td = end - start
        print "It takes " + str(td.total_seconds()/60) + " minutes to response " + str(number) + " requests!"
        print "Need to upgrade your computer ? :P"
    
        
    
    if __name__ == "__main__":
        mega_attack(10000)

    然后我们来测试一下:

    $~ python testrequest.py

    我们会得到这些数据:

    ['{"msg": "", "result": {"level1": [{"date_created": "2014-01-08 08:11:03", "is_active": 1, "id": 1, "name": "VIP_level1"}, {"date_created": "2014-01-08 08:11:03", "is_active": 1, "id": 2, "name": "VIP_level2"}]}, "ret": "0"}']
    ...
    ... [
    '{"msg": "", "result": {"level1": [{"date_created": "2014-01-08 08:11:03", "is_active": 1, "id": 1, "name": "VIP_level1"}, {"date_created": "2014-01-08 08:11:03", "is_active": 1, "id": 2, "name": "VIP_level2"}]}, "ret": "0"}'] ['{"msg": "", "result": {"level1": [{"date_created": "2014-01-08 08:11:03", "is_active": 1, "id": 1, "name": "VIP_level1"}, {"date_created": "2014-01-08 08:11:03", "is_active": 1, "id": 2, "name": "VIP_level2"}]}, "ret": "0"}'] It takes 0.179841 minutes to response 10000 requests! Need to upgrade your computer ? :P

    这里是服务器端看到的内容:

    我们把三个不同的数据库模型都进行测试,得到的结果为:

               # 测试用torndb model用的URL
                (r"/testpage1", TestPage1Handler),       # 通过       测试结果10000次请求,花费时间10.3秒  每秒处理970次请求
                # 测试 django model 用的URL
                (r"/testpage2", TestPage2Handler),       # 通过       测试结果10000次请求,花费时间18.6秒  每秒处理538次请求
                # 测试 sqlalchemy model 用的URL
                (r"/testpage3", TestPage3Handler),       # 通过       测试结果10000次请求,花费时间24.1秒  每秒处理415次请求

    总结:

    纯SQL最快,毫无疑问!

    django model 和 SQLAlchemy model (以下称sqla)各有千秋,

    django model里面做了很多优化,sqla 高度封装,里面也做了一些优化,但是如果每次都是徒手来写的话,不一定能一定用到最优方法。针对特殊需求,需要改动的,则需要使用sqla。django model几乎什么都自带了,自带了太多比如serializer 输出的东西,不一定都是我们想要的。但是通过不同方法,都能实现同样的功能。

    不同点在于,开发使用思考难度,开发使用效率,服务器相应时间。

    从上面来看,

    在开发过程中:

            torndb 和 django model 比较好用。当需求发生改变时, torndb 和 django model 可以很快改变以提供所见所得效果。只不过torndb需要的SQL语句标点等校验,是非常耗费开发时间的。

            而,sqla来说,也可以达到共同的效果,但是,每次都要dive into 数据库模型里面,但是对特殊的需求有非常多的变身受应面。

    从服务器角度:

            torndb 最快毫无疑问了。django model 是高度封装的ORM, 服务器也还好,不过一般要消耗一半的性能。sqla,可以有多种形态变身,因为sqla支持纯SQL语言也支持sqla自身的model类型。当然,优化做好了sqla也可以速度非常快!

    不同的学习曲线。django model 看起来只是算一个比较简单的数据model,而sqlalchemy 几乎覆盖了所有数据能操作的范畴,很强大。

    总之,各有千秋。

    Happy Hacking and Happy Coding!

  • 相关阅读:
    SQL select结果集和return的区别
    转发:上海软件公司排行 (估计是2008年的吧)
    还未复习的
    转发:IT行业中的甲方乙方关系
    多线程 异步调用委托
    用sessionStorage实现页面之间的数据传输
    【转】Vue.js:轻量高效的前端组件化方案
    几种web数据渲染模板对比
    ThinkPHP执行原生sql,实现一些复杂的业务需求
    listview可见再加载图片
  • 原文地址:https://www.cnblogs.com/spaceship9/p/3511915.html
Copyright © 2011-2022 走看看