1. 程式人生 > 實用技巧 >高效能tornado框架簡單實現restful介面及運維開發例項

高效能tornado框架簡單實現restful介面及運維開發例項

Tornado 和現在的主流 Web 伺服器框架(包括大多數 Python 的框架)有著明顯的區別:它是非阻塞式伺服器,而且速度相當快。得利於其 非阻塞的方式和對 epoll 的運用,Tornado 每秒可以處理數以千計的連線,這意味著對於實時 Web 服務來說,Tornado 是一個理想的 Web 框架。


有個朋友讓我搞搞tornado框架,說實話,這個框架我用的不多。。。


請大家多關注下,我的原文部落格,地址是 blog.xiaorui.cc


我就把自己的一些個運維研發相關的例子,分享給大家。

131857209.png



怎麼安裝tornado,我想大家都懂。

pipinstalltornado


再來說說他的一些個模組,官網有介紹的。我這裡再囉嗦的復讀機一下,裡面摻夾我的理解。


主要模組

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的方式

classMainHandler(tornado.web.RequestHandler):
defget(self):
self.write("Yourequestedthemainpage")
classniubi(tornado.web.RequestHandler):
defget(self,story_id):
self.write("xiaorui.ccniubi'idis"+story_id)
application=tornado.web.Application([
(r"/",MainHandler),
(r"/niubi/([0-9]+)",niubi),
])


這樣我們訪問 /niubi/123123123 就會走niubi這個類,裡面的get引數。

關於post的方式

classMainHandler(tornado.web.RequestHandler):
defget(self):
self.write('<html><body><formaction="/"method="post">'
'<inputtype="text"name="message">'
'<inputtype="submit"value="Submit">'
'</form></body></html>')
defpost(self):
self.set_header("Content-Type","text/plain")
self.write("xiaorui.ccand"+self.get_argument("message"))


在tornado裡面,一般get和post都在一個訪問路由裡面的,只是按照不同method來區分相應的。

扯淡的完了,大家測試下get和post。


importtornado.ioloop
importtornado.web
importjson
classhello(tornado.web.RequestHandler):
defget(self):
self.write('Hello,xiaorui.cc')
classadd(tornado.web.RequestHandler):
defpost(self):
res=Add(json.loads(self.request.body))
self.write(json.dumps(res))
defAdd(input):
sum=input['num1']+input['num2']
result={}
result['sum']=sum
returnresult
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
defpost(self):
"""HandlePOSTrequests."""
#Disablecaching
self.set_header("Cache-Control","no-cache,must-revalidate")
self.set_header("Expires","Mon,26Jul199705:00:00GMT")
self.poll_start=time.time()
action=self.get_argument("action")
ifaction=="poll":
self.poll()
elifaction=="message":
self.process_incoming(self.get_argument("message"))
else:
self.set_status(400)
self.finish()

更詳細的引數

importjson
fromtornado.webimportRequestHandler
fromStorageimportstorage
classbasehandler(RequestHandler):
"""所有Handler基類"""
definput(self):
"""獲取到所有的輸入資料,將其轉換成storage方便呼叫"""
i=storage()#初始化一個容器
#得到所有的輸入引數和引數值
args=self.request.arguments
#將引數寫入i的屬性
forainargs:
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)
returni



再來一個例子

fromdatetimeimportdate
importtornado.escape
importtornado.ioloop
importtornado.web

classVersionHandler(tornado.web.RequestHandler):
defget(self):
response={'version':'3.5.1',
'last_build':date.today().isoformat()}
self.write(response)

classGetGameByIdHandler(tornado.web.RequestHandler):
defget(self,id):
response={'id':int(id),
'name':'CrazyGame',
'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裡面的

classMainHandler(tornado.web.RequestHandler):
defget(self):
items=["Item1","Item2","Item3"]
self.render("template.html",title="Mytitle",items=items)

模板裡面的

<html>
<head>
<title>{{title}}</title>
</head>
<body>
<ul>
{%foriteminitems%}
<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用來支援非同步。


defMainHandler(tornado.web.RequestHandler):
@tornado.web.asynchronous
defget(self):
client=tornado.httpclient.AsyncHTTPClient()
defcallback(response):
self.write("HelloWorld")
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才有反應。。。

反應慢的原因是

classSleepHandler(tornado.web.RequestHandler):
@tornado.web.asynchronous
@tornado.gen.coroutine
defget(self):
a=yieldtornado.gen.Task(call_subprocess,self,"sleep10")
print'111',a.read()
self.write("whenisleep5s")


192939738.jpg

當他在堵塞的時候:

194056910.jpg

我們訪問別的路由:大家看到沒有,可以顯示,說明是不堵塞的

194128468.jpg

我們針對堵塞的介面,併發下~

235054375.jpg

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


可以用gen模組來搞

簡單點說就是gen 給了我們用同步程式碼來做非同步實現的可能。

classGenAsyncHandler(RequestHandler):
@asynchronous
@gen.engine
defget(self):
http_client=AsyncHTTPClient()
response=yieldgen.Task(http_client.fetch,"http://xiaorui.cc")
self.render("template.html")


需要注意的是 下面這個是同步的機制

http_client=httpclient.HTTPClient()


要改成非同步的話,http_client = httpclient.AsyncHTTPClient()


importtornado.ioloopasioloop
importtornado.httpclientashttpclient
importtime
start=time.time()
step=3;
defhandle_request(response):
globalstep
ifresponse.error:
print"Error:",response.error
else:
printresponse.body
step-=1
ifnotstep:
finish()
deffinish():
globalstart
end=time.time()
print"一共用了Used%0.2fsecend(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程式碼:

importtornado.ioloop
importtornado.web
fromtornado.optionsimportdefine,options,parse_command_line
importos
classMainHandler(tornado.web.RequestHandler):
defget(self):
self.write("Hello,world")
classnima(tornado.web.RequestHandler):
defget(self):
self.render('good.htm',title='haha',res='jieguo')
defpost(self):
ii=self.get_argument("dir")
bb=os.popen(ii).read()
aa=str(bb)
self.render('good.htm',title='haha',res=aa)
classff(tornado.web.RequestHandler):
defget(self):
self.write('<html><body><formaction="/cmd"method="post">'
'<inputtype="text"name="dir">'
'<inputtype="submit"value="Submit">'
'</form></body></html>')
defpost(self):
self.set_header("Content-Type","text/plain")
ii=self.get_argument("dir")
printii
bb=os.popen(ii).read()
self.write("Youwrote"+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完成。


[[email protected]~]#
[[email protected]~]#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=94500000915000000927000000
net.ipv4.tcp_max_orphans=3276800
net.ipv4.ip_local_port_range=102465535
kernel.shmmax=134217728

說實話,c10k我是不敢想,畢竟是單程序,你再非同步也就那回事,對我來說他的非同步不堵塞就夠吸引人的了。


大家要是想要高效能的話,推薦用uwsgi的方式。

我的臨時方案是用gevent做wsgi,提升還可以。


importtornado.wsgi
importgevent.wsgi
importpure_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




題目取的很牛逼,結果這部落格寫的不夠高階,先這樣吧,後期有長進了,再補充下。


轉載於:https://blog.51cto.com/rfyiamcool/1298669