如何在 App Engine 上运行后台任务?
8 回答
您可以使用任务队列 Python API。
GAE 是构建可扩展 Web 应用程序的非常有用的工具。许多人指出的限制很少是不支持后台任务,缺乏周期性任务以及对每个HTTP请求花费多少时间的严格限制,如果请求超过该时间限制,则操作将被终止,这使得耗时的任务无法运行.
如何运行后台任务?
在 GAE 中,代码仅在有 HTTP 请求时执行。代码需要多长时间有严格的时间限制(我认为是 10 秒)。因此,如果没有请求,则不会执行代码。建议的解决方法之一是使用外部框连续发送请求,因此有点创建后台任务。但是为此我们需要一个外部盒子,现在我们依赖于一个额外的元素。另一种选择是发送 302 重定向响应,以便客户端重新发送请求,这也使我们依赖于外部元素,即客户端。如果那个外部盒子是 GAE 本身呢?每个使用过不支持循环结构的函数式语言的人都知道替代方案,即递归是循环的替代品。那么,如果我们完成部分计算并在同一个 url 上执行一个 HTTP GET 并以非常短的时间(比如 1 秒)呢?这会在 apache 上运行的 php 代码上创建一个循环(递归)。
<?php $i = 0; if(isset($_REQUEST["i"])){ $i= $_REQUEST["i"]; 睡眠(1); } $ch = curl_init("http://localhost".$_SERVER["PHP_SELF"]."?i=".($i+1)); curl_setopt($ch, CURLOPT_HEADER, 0); curl_setopt($ch, CURLOPT_TIMEOUT, 1); curl_exec($ch); 打印“你好世界\n”; ?>
一些这在 GAE 上不起作用。那么如果我们在其他一些 url 上做 HTTP GET 说 url2 哪个在第一个 url 上做 HTTP GET 呢?这似乎在 GAE 中有效。这个代码看起来像这样。
类 FirstUrl(webapp.RequestHandler): 定义获取(自我): self.response.out.write("ok") 时间.sleep(2) urlfetch.fetch("http://"+self.request.headers["HOST"]+'/url2') 类SecondUrl(webapp.RequestHandler): 定义获取(自我): self.response.out.write("ok") 时间.sleep(2) urlfetch.fetch("http://"+self.request.headers["HOST"]+'/url1') application = webapp.WSGIApplication([('/url1', FirstUrl), ('/url2', SecondUrl)]) 定义主(): run_wsgi_app(应用程序) 如果 __name__ == "__main__": 主要的()
由于我们找到了一种运行后台任务的方法,让我们为周期性任务(计时器)和一个跨越许多 HTTP 请求(foreach)的循环结构构建抽象。
计时器
现在构建计时器是直截了当的。基本思想是有一个计时器列表和每个应该被调用的时间间隔。一旦我们达到那个间隔,就调用回调函数。我们将使用 memcache 来维护计时器列表。为了找出何时调用回调,我们将在 memcache 中存储一个 key,其间隔为过期时间。我们定期(比如 5 秒)检查该键是否存在,如果不存在则调用回调并再次设置该键的间隔。
def 计时器(函数,间隔): timerlist = memcache.get('timer') 如果(无 == 计时器列表): 计时器列表 = [] timerlist.append({'func':func, 'interval':interval}) memcache.set('timer-'+func, '1', 间隔) memcache.set('timer', timerlist) 定义检查计时器(): timerlist = memcache.get('timer') 如果(无 == 计时器列表): 返回假 对于计时器列表中的电流: if(None == memcache.get('timer-'+current['func'])): #重置间隔 memcache.set('timer-'+current['func'], '1', current['interval']) #调用回调函数 尝试: eval(当前['func']+'()') 除了: 经过 返回真 返回假
Foreach
当我们想要进行长时间的计算时,例如对 1000 个数据库行进行一些操作或获取 1000 个 url 等,这是需要的。基本思想是在 memcache 中维护回调和参数列表,并且每次使用参数调用回调。
def foreach(函数,参数): looplist = memcache.get('foreach') 如果(无 == 循环列表): 循环列表 = [] looplist.append({'func':func, 'args':args}) memcache.set('foreach', looplist) 定义检查循环(): looplist = memcache.get('foreach') 如果(无 == 循环列表): 返回假 if((len(looplist) > 0) 和 (len(looplist[0]['args']) > 0)): arg = looplist[0]['args'].pop(0) func = looplist[0]['func'] if(len(looplist[0]['args']) == 0): looplist.pop(0) if((len(looplist) > 0) 和 (len(looplist[0]['args']) > 0)): memcache.set('foreach', looplist) 别的: memcache.delete('foreach') 尝试: eval(func+'('+repr(arg)+')') 除了: 经过 返回真 别的: 返回假 # 代替 # foreach 范围内的索引(0, 1000): # someoperaton(index) # 我们会说 # foreach('someoperaton', range(0, 1000))
现在构建一个每隔一小时获取 url 列表的程序是直截了当的。这是代码。
def getone(网址): 尝试: 结果 = urlfetch.fetch(url) 如果(结果.status_code == 200): memcache.set(url, '1', 60*60) #处理结果.内容 除了 : 经过 def getallurl(): #要获取的url列表 urllist = ['http://www.google.com/', 'http://www.cnn.com/', 'http://www.yahoo.com', 'http://news.google. com'] 提取列表 = [] 对于 urllist 中的 url: 如果(memcache.get(url) 为无): fetchlist.append(url) #这相当于 #for fetchlist 中的 url: getone(url) if(len(fetchlist) > 0): foreach('getone', fetchlist) #注册定时器回调 计时器('getallurl',3*60)
完整的代码在这里http://groups.google.com/group/httpmr-discuss/t/1648611a54c01aa 我已经在 appengine 上运行这段代码几天了,没有太多问题。
警告:我们大量使用 urlfetch。每天 urlfetch 的限制是 160000。所以要小心不要达到这个限制。
您可以在此处找到有关 Python App Engine 中 cron 作业的更多信息。
即将推出的运行时版本将具有某种周期性执行引擎 a'la cron。在 AppEngine 组上查看此消息。
所以,所有的 SDK 部分似乎都可以工作,但我的测试表明这还没有在生产服务器上运行——我设置了一个“每 1 分钟”的 cron 来记录它运行时的日志,并且它还没有被调用
很难说什么时候可以使用,虽然......
使用延迟 Python 库是使用基于 TaskQueue API 构建的 Python 在 Appengine 上执行后台任务的最简单方法。
from google.appengine.ext import deferred
def do_something_expensive(a, b, c=None):
logging.info("Doing something expensive!")
# Do your work here
# Somewhere else
deferred.defer(do_something_expensive, "Hello, world!", 42, c=True)
如果您想运行后台定期任务,请参阅此问题(AppEngine cron)
如果您的任务不是周期性的,请参阅任务队列 Python API或任务队列 Java API
应用引擎中内置了一个 cron 工具。
请参考: https ://developers.google.com/appengine/docs/python/config/cron?hl=en