這兩天在用 tornado 做一個 memcached 的 proxy,作為一個 Python 的高性能異步框架,tornado (實際是 epoll/kqueue… )的思想是——單線程+異步化,線程的運行時間不等待任何東西,這樣就要求 memcached 的訪問也必須異步化。如果線程在等待中消耗了,就無法達到高并發的目的,這個問題是無法通過簡單地交給線程池或什么其他東西來達到的。
于是,這里就不能用常用的 python-memcache 來做了,實際上有幾個基于 tornado 的 memcache 客戶端,這個是維護得相對好的一個,也是一年前的了,而且,有兩個問題:
這樣,在 server 或網絡出現問題的時候,就可能遇到*煩,所以,我的目的就是絞盡腦汁加入超時機制,這個初步做出來了,等把 get 之外的方法也都異步化之后就反饋出來。這里主要依賴的機制就是 tornado 的 stack context——再次聲明,我是這個方面的菜鳥,有什么不對的地方大家噓之余給指出來唄。
Stack context 的意圖就是為執行程序保存一個上下文,在需要的時候,可以回到這個上下文執行,包括異常,都可以更好地、統一地處理。這個功能的代碼不是很多,也比較清晰,但是文檔……嗯,至少我是沒看明白,結合 httpclient 的源碼作為例子,加上看 stack_context 的代碼,大概明白了是怎么用了。
首先,在希望抓住問題的入口的地方要留住上下文:
#...... context = partial(self._cleanup, fail_callback = fail_callback) with stack_context.StackContext(context): getattr(c, cmd)(*args, **kwargs)
這里,后面的執行內容,包括回調、觸發事件,都可以通過拋出異常退到這里,而管理異常的就是 context,這里,用 functools.partial 包裝了一下 _cleanup,_cleanup 的寫法大致是這樣的:
@contextlib.contextmanager def _cleanup(self, fail_callback = None): try: yield except _Error as e: print "gotcha", e if fail_callback: fail_callback(e.args)
這里,異常會被捕獲,并調用用戶指定的出錯回調函數進行處理。后面的代碼里,遇到故障,拋出異常就可以了,比如,可以用這個異常來返回超時:
def _on_timeout(self, server): self._timeout = None server.mark_dead('Time out') raise _Error('memcache call timeout')
這個異常是通過 io_loop 的 timeout 方法來觸發的:
self._timeout = self.io_loop.add_timeout( time.time() + self.request_timeout, stack_context.wrap(partial(self._on_timeout, server)))
這樣,就可以在異步程序里比較干凈地處理掉超時問題了。
這個代碼對我這個水平的初學者還是比較晦澀的,大家可以參考下 HTTPClient 的源碼,等我把這個 memcached client 的代碼改完之后,也會放出來供參考指正的。
—-
update2: 放這里了?https://github.com/gnawux/tornado-memcache?, get 測試過,其他還沒有,另外,我不是多個 server sharding 的應用場景,相關的還沒測試。
update :對于 timeout,設上了表忘了清除,如果是其他方式拋異常退出的話,也在拋異常的地方或者是最后處理異常的時候,把超時去掉
if self._timeout is not None: self.io_loop.remove_timeout(self._timeout) self._timeout = None
原文地址:Tornado 的 stack context, 感謝原作者分享。
聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。TEL:177 7030 7066 E-MAIL:11247931@qq.com