Tornado用回调代替gen

来源:互联网 发布:网络歌手招聘 编辑:程序博客网 时间:2024/05/17 12:05

Tornado利用python的yield机制,用gen模块可以用同步的代码逻辑书写异步调用的代码。一般的,在程序开发过程中,方便的书写逻辑必然会带来运行上的额外开销。笔者的一个整合型爬虫服务设计大量的异步调用逻辑,出现HTTP超时的比例大概为1%,查看被调用的服务日志未出现超时,怀疑是gen的协程机制未有能使IOLoop的读事件及时响应(注:此问题还未能验证)。


下面就将常见的两种异步调用场景从Tornado的gen同步逻辑改为普通的callback异步逻辑


1. 多次重试异步调用

我们访问的外部接口可能由于各种原因需要在发生错误后重试N次,用Tornado的gen来写应该是下面这样:

[python] view plaincopy在CODE上查看代码片派生到我的代码片
  1. import tornado.web  
  2. import tornado.gen  
  3.   
  4.   
  5. class TestHandler(tornado.web.RequestHandler):                                                                              
  6.                                                                                                                            
  7.     @tornado.web.asynchronous  
  8.     @tornado.gen.engine                                                                                                     
  9.     def post(self):  
  10.         """Tornado handler post 入口                                                                                       
  11.         """  
  12.           
  13.         try:                                                                                                                
  14.             ...                                                                                                             
  15.             some biz code here                                                                                              
  16.             ...                                                                                                             
  17.               
  18.             retry_n = 3                                                                                                     
  19.             while retry_n > 0:                                                                                              
  20.                 response = yield tornado.gen.Task(http_client.fetch, request)                                               
  21.                 if response.code == 200:                                                                                    
  22.                     break  
  23.                 retry_n -= 1  
  24.             else:     
  25.                 #超过了重试的最大次数,抛出异常                                                                             
  26.                 raise ServiceError                                                                                          
  27.   
  28.             ...  
  29.             some biz code here  
  30.             ...  
  31.             self.finish(ok)  
  32.   
  33.         except:  
  34.             ....  

如果改为普通的异步callback写法,应该为:

[python] view plaincopy在CODE上查看代码片派生到我的代码片
  1. import tornado.web  
  2.   
  3. class TestHandler(tornado.web.RequestHandler):  
  4.  
  5.     @tornado.web.asynchronous  
  6.     def post(self):  
  7.         """Tornado handler post 入口 
  8.         """  
  9.   
  10.         ...  
  11.         some biz code here  
  12.         ...  
  13.   
  14.         try:  
  15.             notify(3)  
  16.         except:  
  17.             ...  
  18.   
  19.     def post1(self, data):  
  20.         """继续处理post未完成的步骤 
  21.         """  
  22.   
  23.         ...  
  24.         some biz code here  
  25.         ...  
  26.   
  27.         self.finish('ok')  
  28.   
  29.     def notify(self, retry_t):  
  30.         """循环访问异步接口 
  31.         retry_t 标志访问的次数 
  32.         """  
  33.   
  34.         if retry_t <= 0:  
  35.             #超过了重试的最大次数,抛出异常  
  36.             raise ServiceError  
  37.   
  38.         def fetch_cb(data):  
  39.             """异步http client回调,如果code不为200,继续调用notify 
  40.             """  
  41.   
  42.             if data.code == 200:  
  43.                 self.post1(data.body)  
  44.             else:  
  45.                 self.notify(retry_t-1)  
  46.   
  47.         # 异步的http client  
  48.         http_client.fetch(request, fetch_cb)  


2. 异步调用列表

另一个典型的场景是有一系列的异步调用,希望所有的调用都返回结构之后在开始后面的代码流程

用tornado的gen逻辑来实现就是下面的写法:

[python] view plaincopy在CODE上查看代码片派生到我的代码片
  1. import tornado.web  
  2. import tornado.gen  
  3.   
  4.   
  5. class TestHandler(tornado.web.RequestHandler):  
  6.  
  7.     @tornado.web.asynchronous  
  8.     @tornado.gen.engine  
  9.     def post(self):  
  10.         """Tornado handler post 入口 
  11.         """  
  12.   
  13.         try:  
  14.             ...  
  15.             some biz code here  
  16.             ...  
  17.   
  18.             response = yield tornado.gen.Task[(http_client.fetch, request1),  
  19.                                             http_client.fetch, request2),  
  20.                                             http_client.fetch, request3),  
  21.                                             http_client.fetch, request4)]  
  22.   
  23.             ...  
  24.             some biz code here  
  25.             ...  
  26.   
  27.             self.finish(ok)  
  28.   
  29.         except:  
  30.             ....  

如果改为普通的异步callback写法,应该为:

[python] view plaincopy在CODE上查看代码片派生到我的代码片
  1. import tornado.web  
  2.   
  3. class TestHandler(tornado.web.RequestHandler):  
  4.  
  5.     @tornado.web.asynchronous  
  6.     def post(self):  
  7.         """Tornado handler post 入口 
  8.         """  
  9.   
  10.         ...  
  11.         some biz code here  
  12.         ...  
  13.   
  14.         try:  
  15.             notify = notify_list(4self.post1)  
  16.   
  17.             # 异步的http client  
  18.             http_client.fetch(request1, notify.notify_list_cb)  
  19.             http_client.fetch(request2, notify.notify_list_cb)  
  20.             http_client.fetch(request3, notify.notify_list_cb)  
  21.             http_client.fetch(request4, notify.notify_list_cb)  
  22.   
  23.         except:  
  24.             ...  
  25.   
  26.     def post1(self, data):  
  27.         """继续处理post未完成的步骤 
  28.         """  
  29.   
  30.         ...  
  31.         some biz code here  
  32.         ...  
  33.   
  34.         self.finish('ok')  
  35.   
  36. class NotifyList(object):  
  37.     """用于处理回调列表的类 
  38.     """  
  39.   
  40.     def __init__(self, caller_n, final_cb):  
  41.         """ 
  42.             caller_n 回调列表的长度 
  43.             final_cb 全部回调完成后的最终出口回调 
  44.         """  
  45.   
  46.         self._caller_n = caller_n  
  47.         self._data_list = []  
  48.         self._final_cb = final_cb  
  49.   
  50.     def notify_list_cb(self, data):  
  51.   
  52.         self._caller_n -=1  
  53.         if self._caller_n == 0:  
  54.             self._final_cb(self._data_list)  
  55.         else:  
  56.             self._data_list.append(data)  
0 0