高效能tornado框架簡單實現restful介面及運維開發例項
Tornado 和現在的主流 Web 伺服器框架(包括大多數 Python 的框架)有著明顯的區別:它是非阻塞式伺服器,而且速度相當快。得利於其 非阻塞的方式和對 epoll 的運用,Tornado 每秒可以處理數以千計的連線,這意味著對於實時 Web 服務來說,Tornado 是一個理想的 Web 框架。
有個朋友讓我搞搞tornado框架,說實話,這個框架我用的不多。。。
請大家多關注下,我的原文部落格,地址是 blog.xiaorui.cc
我就把自己的一些個運維研發相關的例子,分享給大家。
怎麼安裝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")
當他在堵塞的時候:
我們訪問別的路由:大家看到沒有,可以顯示,說明是不堵塞的
我們針對堵塞的介面,併發下~
源地址 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
客戶端:
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框架下,會各種爽的。
題目取的很牛逼,結果這部落格寫的不夠高階,先這樣吧,後期有長進了,再補充下。
轉載於:https://blog.51cto.com/rfyiamcool/1298669