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

高性能tornado框架简单实现restful接口及运维开发实例
2013-09-18 13:20:56
标签:tornado 入门 tornado 高性能 tornado restful python 异步 tornado 并发
原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 、作者信息和本声明。否则将追究法律责任。http://rfyiamcool.blog.51cto.com/1030776/1298669
Tornado 和现在的主流 Web 服务器框架(包括大多数 Python 的框架)有着明显的区别:它是非阻塞式服务器,而且速度相当快。得利于其 非阻塞的方式和对 epoll 的运用,Tornado 每秒可以处理数以千计的连接,这意味着对于实时 Web 服务来说,Tornado 是一个理想的 Web 框架。

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

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


怎么安装tornado,我想大家都懂。
1
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的方式
1
2
3
4
5
6
7
8
9
10
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的方式
1
2
3
4
5
6
7
8
9
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。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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状态码的处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@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()
更详细的参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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


再来一个例子
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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里面的
1
2
3
4
class MainHandler(tornado.web.RequestHandler):
    def get(self):
        items = ["Item 1", "Item 2", "Item 3"]
        self.render("template.html", title="My title", items=items)
模板里面的
1
2
3
4
5
6
7
8
9
10
11
12
<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用来支持异步。

1
2
3
4
5
6
7
8
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才有反应。。。
反应慢的原因是
1
2
3
4
5
6
7
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")

192939738.jpg
当他在堵塞的时候:
194056910.jpg
我们访问别的路由:大家看到没有,可以显示,说明是不堵塞的
194128468.jpg
我们针对堵塞的接口,并发下~
235054375.jpg
源地址 http://rfyiamcool.blog.51cto.com/1030776/1298669

可以用gen模块来搞
简单点说就是gen 给了我们用同步代码来做异步实现的可能。
1
2
3
4
5
6
7
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")

需要注意的是 下面这个是同步的机制
1
http_client = httpclient.HTTPClient()

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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
194808241.jpg
222023743.jpg
客户端:
195022786.jpg
195137924.jpg
tornado的设计就是为了c10k,但为为啥看不出他的牛逼之处。
我想到的是没有优化内核的tcp承载,还有就是我们访问的route没有配置异步。 再次测试压力,10000个请求,在4s完成。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[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,提升还可以。

1
2
3
4
5
6
7
8
9
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框架下,会各种爽的。
161546180.png
161546644.png



题目取的很牛逼,结果这博客写的不够高端,先这样吧,后期有长进了,再补充下。

本文出自 “峰云,就她了。” 博客,请务必保留此出处http://rfyiamcool.blog.51cto.com/1030776/1298669

猜你喜欢

转载自wangxiaoxu.iteye.com/blog/2029604
今日推荐