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框架下,会各种爽的。
题目取的很牛逼,结果这博客写的不够高端,先这样吧,后期有长进了,再补充下。