[轉]Tornado get/post請求非同步處理架構分析

來源:互聯網
上載者:User

標籤:

轉自: http://m.blog.csdn.net/blog/joeyon/41956027首先說下環境,python2.7,Tornado3.0

然後本文討論的不是Tornado底層如何用epoll處理socket通訊,而是如何在應用程式層非同步處理get/post請求。下面是本文:

 

同時在get或者post方法處理上應用@tornado.web.asynchronous和 @tornado.gen.engine裝飾器,可以非常方便的和有callback參數的非同步方法呼叫配合實現非阻塞請求處理。 這裡不想說官方的那個例子,因為官方的例子給的本來就是用非同步http用戶端來拉取資料然後自己轉到回呼函數執行,討論http用戶端的代碼不在本文範圍內。於是我用一個簡單的函數來示範,且這樣更能說明問題。 class  RegisterHandler(basehandler.BaseHandler):     @tornado.web.asynchronous     @tornado.gen.engine    def  get( self, *arg, **args):        response = yield tornado.gen.Task( self.method_while)        print  "response",response         self.finish()       def  method_while( self, *arg, **args):        callback(1)  這是我寫的一個請求處理的Handler,get請求上加了這兩個裝飾器,第一個裝飾器標明這個get函數是不會自動斷掉輸出資料流的,需要顯式的調用finish方法。這個裝飾器需要和@tornado.gen.engine一塊用。 重點到了,來看@tornado.gen.engine的源碼def  engine(func):     @functools.wraps(func)    def  wrapper(*args, **kwargs):        runner = None         def  handle_exception(typ, value, tb):            if runner is not None:                return runner.handle_exception(typ, value, tb)            return False        with ExceptionStackContext(handle_exception) as deactivate:            gen = func(*args, **kwargs)            if isinstance(gen, types.GeneratorType):                runner = Runner(gen, deactivate)                runner.run()                return            assert gen is None, gen            deactivate()    return wrapper(我把原來有的注釋刪掉了) 我們直接看with語句的內部就可以了:gen = func(*args, **kwargs)if isinstance(gen, types.GeneratorType):
runner = Runner(gen, deactivate)runner.run()return

gen就是我們的get方法,gen=fun(*args,**kwargs)相當於gen=get(*args,**kwargs),get方法執行結果返回給gen?no~no!get方法裡有yield,意味著get(*args,**kwargs)返回的是一個產生器類型GeneratorType的資料,是不會馬上執行的,需要調用他的next或者send方法讓其執行到下一個yield方法處。關於產生器以及它的send和next的具體用法不在本文的討論範圍內,請查閱官方文檔。 可以看到它new了一個Runner,gen傳了進去,然後執行run方法,我們跟進Runner類的run方法的代碼:def  run( self ):        if  self.running or  self.finished:#判斷是否是在運行中或者已經結束,如果是立即返回            return        try:             self.running = True #讓狀態為運行中            while True:#進入迴圈                if  self.exc_info is None:#上次迴圈沒有異常                    try:                        if not  self.yield_point.is_ready():#判斷key是否可用                            return                        next =  self.yield_point.get_result()#擷取yield運算式的結果給next                    except Exception:                         self.exc_info = sys.exc_info()                try:                    if  self.exc_info is not None:                         self.had_exception = True                        exc_info =  self.exc_info                         self.exc_info = None                        yielded =  self.gen.throw(*exc_info)                    else:                        yielded =  self.gen.send(next)#如果上次沒異常,get函數繼續執行,這裡把Task對象返回給yielded,相當於要迭代使用這個Task(最開頭標紅的程式碼片段),每次執行到yield都要把task重新賦給yielded                except StopIteration:#如果沒有可以執行的了,返回                     self.finished = True                    if  self.pending_callbacks and not  self.had_exception:                        raise LeakedCallbackError(                             "finished without waiting for callbacks %r" %                             self.pending_callbacks)                     self.deactivate_stack_context()                     self.deactivate_stack_context = None                    return                except Exception:                     self.finished = True                    raise                if isinstance(yielded, list):                    yielded = Multi(yielded)                if isinstance(yielded, YieldPoint):#如果yielded為Task類型(Task繼承於YieldPoint)                     self.yield_point = yielded                    try:                         self.yield_point.start( self)#執行task的start方法                    except Exception:                         self.exc_info = sys.exc_info()                else:                     self.exc_info = (BadYieldError( "yielded unknown object %r" % yielded),)        finally:             self.running = False 具體步驟可以詳見上述代碼中的注釋,這裡我們就繼續看task的start方法:class  Task(YieldPoint):(省略其他代碼)    def  start( self, runner):         self.runner = runner         self.key = object()        runner.register_callback(  self.key)         self.kwargs[ "callback"] = runner.result_callback( self.key)         self.func(* self.args, **  self.kwargs)(省略其他代碼)  這裡的func方法就是最上面的method_while,傳了一個callback參數進去:runner.result_callback(  self.key),然後執行該方法。為了討論這個callback參數的作用,我們假設這個method_while方法裡,沒有用到這個參數,即沒有執行callback:start方法在method_while執行完後就返回了,繼續執行Runner的run方法裡的那個迴圈的第二遍,它會直接在這一行if not  self.yield_point.is_ready():#判斷key是否可用     return直接返回,原因就是在results 裡這個key對應的值為None。 這裡涉及到了兩個名詞,results 和key,result是在哪裡的呢?是Runner的一個欄位,從下面的Runner的建構函式裡可以看到results是個字典 :class  Runner(object):    def  __init__( self, gen, deactivate_stack_context):        (省略其他代碼)                self.pending_callbacks = set()                self.results = {}       (省略其他代碼) 那麼key是怎麼設定到results這個字典裡的呢?是在Task的start方法裡,runner.register_callback(  self.key),具體代碼可以再看上面的Task類。這個register_callback是有點繞的,它將key設定到pending_callbacks裡面,這個pending_callbacks是一個set類型,即不可重複的集合,有這個集合的作用是為了每次都要從這裡面判斷key是否存在,key存在說明有個Task要執行。在上面的run方法中的判斷key是否可用的時候,is_ready每次都會先判斷key是否在這個pending_callbacks,沒有直接報異常,有的話才會去results去取結果。我們可以看到results一直是空啊,什麼時候設定的值呢??就是剛剛我們在method_while裡沒有執行的callback方法,即runner.result_callback(  self.key):def  result_callback( self , key):        def  inner(*args, **kwargs):            if kwargs or len(args) > 1:                result = Arguments(args, kwargs)            elif args:                result = args[ 0]            else:                result = None             self.set_result(key, result)        return inner 可以看出來,他把callback實際調用時候的參數列表又寫進了results裡了,返回給yield運算式的值response。所以我們發現,一定要在自訂的方法中執行參數中的callback,把你想返回的資料寫進callback的參數裡,yield運算式的值就會是這個參數了,然後run方法繼續執行到下一個yield直到get方法完成。 比如最上面那個例子,列印結果為:response 1  最後來說這個非同步方案有什麼用呢?一說到非同步,新手肯定會以為是get/post方法阻塞了也沒關係,一個get請求過來,get處理函數如果阻塞了,是必然導致整個伺服器阻塞的!不要以為這個是相對web前端請求的並行非同步解決方案,單線程的Tornado畢竟是單線程,方法阻塞,必然導致伺服器阻塞,毋庸置疑的。 這個非同步是相對與get請求來說的,get方法在沒有加裝飾器@tornado.web.asynchronous的情況下,get方法結束後,請求會自動斷開,但是加上這個裝飾器後,get請求可以直接return且保持串連,直到顯式的調用finish方法才會關閉輸出資料流。這樣就萌生了非同步(非同步:把事情交給除了自己之外的人來做,自己不管)解決方案,get方法直接return,交給別的函數來做,做完了再回來。加上@tornado.gen.engine只是把上面這個思想實現了下,將代碼濃縮到最小而已,這並不表示非同步處理的函數或者這個get裡有死迴圈的話也能同時讓伺服器處理別的請求,你查詢資料庫花費10000s,那麼你的伺服器就肯定要死10000s的,這種問題的解決方案有四個:crouchDB(有http介面,非同步httpclient調用),最佳化當前資料庫,非同步httpclient調webservice,開線程。推薦第二個第三個,其次第四個,最後第一個(原因不解釋,貌似crouchDB口碑不好,我也不愛用,mongo黨飄過)。 就算是非同步httpclient,就像下面這樣
def handle_request(response):    if response.error:        print "Error:", response.error    else:        print response.bodyhttp_client = AsyncHTTPClient()http_client.fetch("http://www.google.com/", handle_request)
也只是fetch過後不管socket如何拉資料而已,回調也是要在本線程執行的!在別處如果有耗時操作或者死迴圈的,它拉過來的資料是永遠進不到handle_request函數中的。

[轉]Tornado get/post請求非同步處理架構分析

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.