您的位置:首页 > 运维架构

高性能tornado框架简单实现restful接口及运维开发实例 推荐

2013-09-18 13:20 886 查看
Tornado 和现在的主流 Web 服务器框架(包括大多数 Python 的框架)有着明显的区别:它是非阻塞式服务器,而且速度相当快。得利于其 非阻塞的方式和对 epoll 的运用,Tornado 每秒可以处理数以千计的连接,这意味着对于实时 Web 服务来说,Tornado 是一个理想的 Web 框架。

有个朋友让我搞搞tornado框架,说实话,这个框架我用的不多。。。

请大家多关注下,我的原文博客,地址是 blog.xiaorui.cc

我就把自己的一些个运维研发相关的例子,分享给大家。




怎么安装tornado,我想大家都懂。
pip install tornado


再来说说他的一些个模块,官网有介绍的。我这里再啰嗦的复读机一下,里面掺夹我的理解。

主要模块
web - FriendFeed 使用的基础 Web 框架,包含了 Tornado 的大多数重要的功能,反正你进入就对了。
escape - XHTML, JSON, URL 的编码/解码方法
database - 对 MySQLdb 的简单封装,使其更容易使用,是个orm的东西。
template - 基于 Python 的 web 模板系统,类似jinja2
httpclient - 非阻塞式 HTTP 客户端,它被设计用来和 web 及 httpserver 协同工作,这个类似加个urllib2
auth - 第三方认证的实现(包括 Google OpenID/OAuth、Facebook Platform、Yahoo BBAuth、FriendFeed OpenID/OAuth、Twitter OAuth)
locale - 针对本地化和翻译的支持
options - 命令行和配置文件解析工具,针对服务器环境做了优化,接受参数的

底层模块
httpserver - 服务于 web 模块的一个非常简单的 HTTP 服务器的实现
iostream - 对非阻塞式的 socket 的简单封装,以方便常用读写操作
ioloop - 核心的 I/O 循环

再来说说tornado接受请求的方式:
关于get的方式

class MainHandler(tornado.web.RequestHandler):
def get(self):
self.write("You requested the main page")
class niubi(tornado.web.RequestHandler):
def get(self, story_id):
self.write("xiaorui.cc  niubi'id is  " + story_id)
application = tornado.web.Application([
(r"/", MainHandler),
(r"/niubi/([0-9]+)", niubi),
])


这样我们访问 /niubi/123123123 就会走niubi这个类,里面的get参数。
关于post的方式

class MainHandler(tornado.web.RequestHandler):
def get(self):
self.write('<html><body><form action="/" method="post">'
'<input type="text" name="message">'
'<input type="submit" value="Submit">'
'</form></body></html>')
def post(self):
self.set_header("Content-Type", "text/plain")
self.write("xiaorui.cc and " + self.get_argument("message"))


在tornado里面,一般get和post都在一个访问路由里面的,只是按照不同method来区分相应的。
扯淡的完了,大家测试下get和post。

import tornado.ioloop
import tornado.web
import json
class hello(tornado.web.RequestHandler):
def get(self):
self.write('Hello,xiaorui.cc')
class add(tornado.web.RequestHandler):
def post(self):
res = Add(json.loads(self.request.body))
self.write(json.dumps(res))
def Add(input):
sum = input['num1'] + input['num2']
result = {}
result['sum'] = sum
return result
application = tornado.web.Application([
(r"/", hello),
(r"/add", add),
])
if __name__ == "__main__":
application.listen(8888)
tornado.ioloop.IOLoop.instance().start()
#大家可以写个form测试,也可以用curl -d测试


http头部和http_code状态码的处理

@tornado.web.asynchronous
def post(self):
"""Handle POST requests."""
# Disable caching
self.set_header("Cache-Control","no-cache, must-revalidate")
self.set_header("Expires","Mon, 26 Jul 1997 05:00:00 GMT")
self.poll_start = time.time()
action = self.get_argument("action")
if action=="poll":
self.poll()
elif action=="message":
self.process_incoming(self.get_argument("message"))
else:
self.set_status(400)
self.finish()
更详细的参数

import json
from tornado.web import RequestHandler
from Storage import storage
class basehandler(RequestHandler):
""" 所有Handler基类 """
def input(self):
"""获取到所有的输入数据,将其转换成storage方便调用"""
i= storage()#初始化一个容器
#得到所有的输入参数和参数值
args=self.request.arguments
#将参数写入i的属性
for a in args:
i[a]=self.get_argument(a)
#获取file类型的参数
i["files"]=storage(self.request.files)
#获取path
i["path"]=self.request.path
#获取headers
i["headers"]=storage(self.request.headers)
return i


再来一个例子

from datetime import date
import tornado.escape
import tornado.ioloop
import tornado.web

class VersionHandler(tornado.web.RequestHandler):
def get(self):
response = { 'version': '3.5.1',
'last_build':  date.today().isoformat() }
self.write(response)

class GetGameByIdHandler(tornado.web.RequestHandler):
def get(self, id):
response = { 'id': int(id),
'name': 'Crazy Game',
'release_date': date.today().isoformat() }
self.write(response)

application = tornado.web.Application([
(r"/getgamebyid/([0-9]+)", GetGameByIdHandler),
(r"/version", VersionHandler)
])

if __name__ == "__main__":
application.listen(8888)
tornado.ioloop.IOLoop.instance().start()


模板:

我们把后端的值传到前端,可以是列表和字典
app.py里面的
class MainHandler(tornado.web.RequestHandler):
def get(self):
items = ["Item 1", "Item 2", "Item 3"]
self.render("template.html", title="My title", items=items)
模板里面的
<html>
<head>
<title>{{ title }}</title>
</head>
<body>
<ul>
{% for item in items %}
<li>{{ escape(item) }}</li>
{% end %}
</ul>
</body>
</html>


下面我们再来扯扯tornado的异步。

tornado是一个异步web framework,说是异步,是因为tornado server与client的网络交互是异步的,底层基于io event loop。但是如果client请求server处理的handler里面有一个阻塞的耗时操作,那么整体的server性能就会下降。
源地址 http://rfyiamcool.blog.51cto.com/1030776/1298669
比如: 咱们访问一个路由 www.xiaorui.cc/sleep5 ,我在sleep5后端配置了等待5秒后给return值。 当我访问的话,肯定是要等5秒钟,这时候,要是有别的客户要连接的别的页面,不堵塞的页面,你猜他能马上显示吗?不能的。。。 他也是要等我访问5秒延迟过后,才能访问的。

幸运的是,tornado提供了一套异步机制,方便我们实现自己的异步操作。当handler处理需要进行其余的网络操作的时候,tornado提供了一个async http client用来支持异步。

def MainHandler(tornado.web.RequestHandler):
@tornado.web.asynchronous
def get(self):
client = tornado.httpclient.AsyncHTTPClient()
def callback(response):
self.write("Hello World")
self.finish()
client.fetch("http://www.google.com/", callback)


上面的例子,主要有几个变化:

使用asynchronous decorator,它主要设置_auto_finish为false,这样handler的get函数返回的时候tornado就不会关闭与client的连接。
使用AsyncHttpClient,fetch的时候提供callback函数,这样当fetch http请求完成的时候才会去调用callback,而不会阻塞。
callback调用完成之后通过finish结束与client的连接。
rang

让我们来看看tornado在异步方面的能力。
大家看到了 http://10.2.20.111:8000/ceshi 花费了10s才有反应。。。
反应慢的原因是

class SleepHandler(tornado.web.RequestHandler):
@tornado.web.asynchronous
@tornado.gen.coroutine
def get(self):
a = yield tornado.gen.Task(call_subprocess,self, "sleep 10")
print '111',a.read()
self.write("when i sleep 5s")





当他在堵塞的时候:



我们访问别的路由:大家看到没有,可以显示,说明是不堵塞的



我们针对堵塞的接口,并发下~



源地址 http://rfyiamcool.blog.51cto.com/1030776/1298669

可以用gen模块来搞
简单点说就是gen 给了我们用同步代码来做异步实现的可能。
class GenAsyncHandler(RequestHandler):
@asynchronous
@gen.engine
def get(self):
http_client = AsyncHTTPClient()
response = yield gen.Task(http_client.fetch, "http://xiaorui.cc")
self.render("template.html")


需要注意的是 下面这个是同步的机制

http_client = httpclient.HTTPClient()


要改成异步的话,http_client = httpclient.AsyncHTTPClient()

import  tornado.ioloop as ioloop
import  tornado.httpclient as httpclient
import  time
start = time.time()
step =  3 ;
def  handle_request(response):
global  step
if  response.error:
print   "Error:" , response.error
else :
print  response.body
step -=  1
if   not  step:
finish()
def  finish():
global  start
end = time.time()
print   "一共用了 Used %0.2f secend(s)"  % float(end - start)
ioloop.IOLoop.instance().stop()
http_client = httpclient.AsyncHTTPClient()
#这三个是异步执行的,大家可以多试试几个url,或者自己写个接口
http_client.fetch( "http://www.baidu.com" , handle_request)
http_client.fetch( "http://www.baidu.com" , handle_request)
http_client.fetch( "http://www.baidu.com" , handle_request)
ioloop.IOLoop.instance().start()


demo的app代码:

import tornado.ioloop
import tornado.web
from tornado.options import define,options,parse_command_line
import os
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.write("Hello, world")
class nima(tornado.web.RequestHandler):
def get(self):
self.render('good.htm',title='haha',res='jieguo')
def post(self):
ii=self.get_argument("dir")
bb=os.popen(ii).read()
aa=str(bb)
self.render('good.htm',title='haha',res=aa)
class ff(tornado.web.RequestHandler):
def get(self):
self.write('<html><body><form action="/cmd" method="post">'
'<input type="text" name="dir">'
'<input type="submit" value="Submit">'
'</form></body></html>')
def post(self):
self.set_header("Content-Type", "text/plain")
ii=self.get_argument("dir")
print ii
bb=os.popen(ii).read()
self.write("You wrote " + bb)
application = tornado.web.Application([
(r"/", MainHandler),
(r"/nima", nima),
(r"/cmd",ff),
])
if __name__ == "__main__":
application.listen(9999)
tornado.ioloop.IOLoop.instance().start()


这是我的那个demo的简化版,大家可以扩展他的功能。需要指出的是 这些功能任何一个web框架都可以实现的。tornado最大的优点是 他的异步,所以我们要重点要看他的异步实现。

简单测试下性能:
服务端和客户端服务器都是dell r720






客户端:






tornado的设计就是为了c10k,但为为啥看不出他的牛逼之处。
我想到的是没有优化内核的tcp承载,还有就是我们访问的route没有配置异步。 再次测试压力,10000个请求,在4s完成。

[root@102 ~]#
[root@102 ~]# sysctl -p
net.ipv4.ip_forward = 1
net.ipv4.conf.default.rp_filter = 1
net.ipv4.tcp_max_syn_backlog = 65536
net.core.netdev_max_backlog = 32768
net.core.somaxconn = 32768
net.core.wmem_default = 8388608
net.core.rmem_default = 8388608
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.ipv4.tcp_timestamps = 0
net.ipv4.tcp_synack_retries = 2
net.ipv4.tcp_syn_retries = 2
net.ipv4.tcp_tw_recycle = 1
net.ipv4.tcp_mem = 94500000 915000000 927000000
net.ipv4.tcp_max_orphans = 3276800
net.ipv4.ip_local_port_range = 1024  65535
kernel.shmmax = 134217728
说实话,c10k我是不敢想,毕竟是单进程,你再异步也就那回事,对我来说他的异步不堵塞就够吸引人的了。

大家要是想要高性能的话,推荐用uwsgi的方式。
我的临时方案是用gevent做wsgi,提升还可以。

import tornado.wsgi
import gevent.wsgi
import pure_tornado
application = tornado.wsgi.WSGIApplication([
(r"/", pure_tornado.MainHandler),
],**pure_tornado.settings)
if __name__ == "__main__":
server = gevent.wsgi.WSGIServer(('', 8888), application)
server.serve_forever()


tornado的session可以轻易放到memcached里面,所以在nginx tornado框架下,会各种爽的。








题目取的很牛逼,结果这博客写的不够高端,先这样吧,后期有长进了,再补充下。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
相关文章推荐