tornado with MySQL, torndb, django model, SQLAlchemy ==> JSON dumped

如今,咱們用torndo作web開發框架,用他內部機制來處理HTTP請求。傳說中的非阻塞式服務。python

 

整來整去,可謂之一波三折。但是,不管怎麼樣,算是被我作成功了。mysql

在tornado服務上,採用三種數據庫模型--》web

1)torndbsql

2)django model數據庫

3)SQLAlchemy modeldjango

    都同時輸出相應的JSON,作API服務。(一個比一個強大的ORM model)json

 

都同時實現了同樣的功能api

 

注意:要說明的一點是,在數據庫創建模型的時候,是用到的django model創建的數據庫模型。因此在使用torndb(SQL 操做語言的ORM) 和 django model的時候,很是簡單操做,直接引用就可使用了。而當再利用SQLAlchemy的時候,將會不得不將django的models 改寫成 SQLAlchemy 支持的格式服務器

1、先來講說torndb

使用很簡單。cookie

請看代碼

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)

2、來看看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裏面的纔是數據庫裏面保存的鮮活的數據。

3、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("\n","",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!

相關文章
相關標籤/搜索