Tornado是異步非阻塞Web框架,能抗住每秒可以處理數(shù)以千計(jì)的連接。背后使用了Epoll,是一個(gè)高性能Web框架。
但是在使用Tornado作為框架來(lái)做業(yè)務(wù)邏輯編寫(xiě),就會(huì)發(fā)現(xiàn),雖然能抗住數(shù)以千計(jì)的連接,Tornado只有一個(gè)主線(xiàn)程。而編寫(xiě)業(yè)務(wù)肯定需要有耗時(shí)的過(guò)程邏輯,比如數(shù)據(jù)庫(kù)的操作。這個(gè)時(shí)候,單線(xiàn)程的Tornado就特別容易阻塞在耗時(shí)邏輯上。
要想很好的適用業(yè)務(wù)邏輯,我的設(shè)計(jì)是基于Tornado引入線(xiàn)程池,并且能方便操作數(shù)據(jù)庫(kù),對(duì)Sqlalchemy包裝一下。目前這種設(shè)計(jì)經(jīng)歷了1年多的項(xiàng)目運(yùn)行,穩(wěn)定運(yùn)行。特別適合高并發(fā)不是特別要求高的業(yè)務(wù)情況。
多線(xiàn)程裝飾器
建立線(xiàn)程池很簡(jiǎn)單,編寫(xiě)裝飾器 @thread_executor,這樣就可以很輕易的掛載裝飾器來(lái)把業(yè)務(wù)邏輯壓入線(xiàn)程池中運(yùn)行。
executor = ThreadPoolExecutor(8)
def thread_executor(fn):
@functools.wraps(fn)
def wrapper(self, *args, **kwargs):
future = executor.submit(fn, self, *args, **kwargs)
return future
return wrapper
多線(xiàn)程帶Session數(shù)據(jù)庫(kù)操作裝飾器
sqlalchemy_session = sessionmaker(bind=engine)
def thread_db_session_executor(fn):
"""
fn 是一個(gè)方法, @thread_db_session_executor 裝飾后,將自動(dòng)轉(zhuǎn)入線(xiàn)程池里面運(yùn)行。
fn 的參數(shù)會(huì)自動(dòng)追加一個(gè)session,為sqlalchemy的DB數(shù)據(jù)操作。 例如 原為fn(a,b) 會(huì)變成fn(a,b,session)
fn 運(yùn)行完成后 session自動(dòng)關(guān)閉,請(qǐng)不要再fn內(nèi)關(guān)閉。
如果 fn 運(yùn)行異常,數(shù)據(jù)庫(kù)會(huì)自動(dòng)回滾,fn返回值變成異常對(duì)象。
"""
@thread_executor
@functools.wraps(fn)
def wrapper(self, *args, **kwargs):
start_time = time.time()
try:
session = sqlalchemy_session()
new_fn = functools.partial(fn, session=session)
result = new_fn(self, *args, **kwargs)
except Exception as e:
try:
logging.exception(
"\n%s \n--function-->>\n%s -args: %s \n%s -kwargs: %s\n<<--function--\n"
% (str(e), fn.__name__, str(args), fn.__name__, str(kwargs))
)
session.rollback()
except Exception as e2:
pass
result = e
finally:
session.close()
return result
return wrapper
封裝Tornado的RequestHandler(json版本)
class SimpleThreadDBSessionHandler(tornado.web.RequestHandler):
"""
SimpleThreadDBSessionHandler 實(shí)例化后,會(huì)在線(xiàn)程池里面獲得一個(gè)線(xiàn)程,并且獲得DB的一個(gè)session(操作數(shù)據(jù)庫(kù))。
重載 on_post, on_get 來(lái)實(shí)現(xiàn)業(yè)務(wù),方法執(zhí)行線(xiàn)程安全。執(zhí)行完成后,線(xiàn)程自動(dòng)回收,session自動(dòng)關(guān)閉。
post request body 是json格式
post get response body 是json
返回結(jié)果 也是 json
"""
@tornado.web.asynchronous
@tornado.gen.coroutine
def post(self):
try:
post_body_data = self.measure_post_body()
result = yield self._on_post(post_body_data)
except Exception as e:
result = e
print e
if isinstance(result, Exception) or result is None:
exception_response_result = self.exception_response(result)
if exception_response_result is None:
raise result
else:
result = exception_response_result
self.write(self.measure_response_body(result))
self.finish()
@thread_db_session_executor
def _on_post(self, post_body_data, session):
return self.on_post(post_body_data, session)
def on_post(self, post_body_data, session):
"""
請(qǐng)重載此函數(shù)處理
"""
raise tornado.web.HTTPError(405)
def measure_post_body(self):
"""
處理 request body的數(shù)據(jù) 轉(zhuǎn)成json格式字典
:return:
"""
return utils_json_string_to_dict(self.request.body.decode('utf-8'))
通過(guò)以上的編寫(xiě),在編寫(xiě)業(yè)務(wù)邏輯的時(shí)候,使用就特別方便。
class TestHandler(SimpleThreadDBSessionHandler):
def on_post(self, post_body_data, session):
user = session.query(User).filter(User.id == 1).first()
return {
'user_name': user.nickname
}
業(yè)務(wù)邏輯(POST) 只要在def on_post(self, post_body_data, session)里面實(shí)現(xiàn)就可以了,
on_post 帶有一個(gè)操作數(shù)據(jù)庫(kù)的session,并且通過(guò)使用@thread_db_session_executor的,on_post的邏輯就放入線(xiàn)程池里面運(yùn)行,完成以后再返回到主線(xiàn)程里面返回。
就算中間發(fā)生異常,異常也會(huì)被捕捉,數(shù)據(jù)庫(kù)回滾。