如何在谷歌应用引擎中返回延迟任务的数据



原始问题

我有一个web应用程序的工作版本,我正在尝试升级,但我遇到了一个问题,即在一个HTTP请求中完成一个任务需要太长时间。应用程序通过HTTPPost操作从JavaScript前端获取JSON列表,并返回该列表的排序/切片版本。随着输入列表越来越长,排序操作需要更长的时间来执行(显然),所以对于适当长的输入列表,我达到了60秒的HTTP请求超时,应用程序失败了。

我想开始使用延迟库来执行排序任务,但我不清楚在执行该任务后如何存储/检索数据。这是我当前的代码:

class getLineups(webapp2.RequestHandler):
  def post(self):
    jsonstring = self.request.body
    inputData = json.loads(jsonstring)
    playerList = inputData["pList"]
    positions = ["QB","RB","WR","TE","DST"]
    playersPos = sortByPos(playerList,positions)
    rosters, playerUse = getNFLRosters(playersPos, positions)
    try:
      # This step is computationally expensive, it will fail on large player lists.
      lineups = makeLineups(rosters,playerUse,50000)
      self.response.headers["Content-Type"] = "application/json"
      self.response.out.write(json.dumps(lineups))
    except:
      logging.error("60 second timeout reached on player list of length:", len(playerList))
      self.response.headers["Content-Type"] = "text/plain"
      self.response.set_status(504)
app = webapp2.WSGIApplication([
  ('/lineup',getLineups),
], debug = True)

理想情况下,我想用对延迟任务库的调用来替换整个try/except块:

deferred.defer(makeLineups,rosters,playerUse,50000)

但我不清楚如何才能从手术中得到结果。我想我必须将它存储在数据存储中,然后检索它,但我的JavaScript前端如何知道操作何时完成?我已经阅读了谷歌网站上的文档,但我仍然不清楚如何完成这项任务。

我是如何解决的

使用公认答案中的基本大纲,以下是我如何解决这个问题:

def solveResult(result_key):
  result = result_key.get()
  playersPos = sortByPos(result.playerList, result.positions)
  rosters, playerUse = getNFLRosters(playersPos,result.positions)
  lineups = makeLineups(rosters,playerUse,50000)
  storeResult(result_key,lineups)
@ndb.transactional
def storeResult(result_key,lineups):
  result = result_key.get()
  result.lineups = lineups
  result.solveComplete = True
  result.put()
class Result(ndb.Model):
  playerList = ndb.JsonProperty()
  positions = ndb.JsonProperty()
  solveComplete = ndb.BooleanProperty()
class getLineups(webapp2.RequestHandler):
  def post(self):
    jsonstring = self.request.body
    inputData = json.loads(jsonstring)
    deferredResult = Result(
      playerList = inputData["pList"],
      positions = ["QB","RB","WR","TE","DST"],
      solveComplete = False
    )
    deferredResult_key = deferredResult.put()
    deferred.defer(solveResult,deferredResult_key)
    self.response.headers["Content-Type"] = "text/plain"
    self.response.out.write(deferredResult_key.urlsafe())
class queryResults(webapp2.RequestHandler):
  def post(self):
    safe_result_key = self.request.body
    result_key = ndb.Key(urlsafe=safe_result_key)
    result = result_key.get()
    self.response.headers["Content-Type"] = "application/json"
    if result.solveComplete:
      self.response.out.write(json.dumps(result.lineups))
    else:
      self.response.out.write(json.dumps([]))

然后,Javascript前端轮询queryLineups URL一段固定的时间,如果时间限制到期或收到数据返回,则停止轮询。我希望这对其他试图解决类似问题的人有帮助。如果事情变得不稳定,我还有更多的工作要做,让它优雅地失败,但这很有效,只需要改进。

我不熟悉GAE,但这是一个相当普遍的问题,所以我可以给你一些建议。

你的总体想法是正确的,所以我只想对其进行扩展。工作流程可能如下所示:

  1. 您收到创建阵容的请求。您在数据存储中为它创建一个新实体。它应该包含一个ID(稍后需要它来检索结果)和一个状态(PENDING|DONE|FAILED)。如果对您有用,您也可以保存请求中的数据
  2. 您可以推迟计算并立即返回响应。响应将包含任务的ID。计算完成后,它将把任务的结果保存在数据存储中,并更新任务的状态。该结果将包含任务ID,这样我们就可以很容易地找到它
  3. 一旦前端接收到ID,它就开始轮询结果。使用setTimeoutsetInterval,您可以向服务器发送带有任务ID的请求(这是一个单独的端点)。服务器检查任务的状态,如果完成则返回结果(如果失败则返回错误)
  4. 前端获取数据并停止轮询

通常情况下,由于原始请求的上下文消失,您无法再回复原始请求也许如果您从请求处理程序返回而不回复,如果不知何故,这不会终止来自客户端的连接,>如果

一种选择是将操作拆分为一个序列:-启动操作的第一个请求-随后的一个或多个轮询请求,直到操作完成并且结果可用

如果昂贵的操作主要在调用操作之前的可用数据上执行,则另一种方法可能是可行的。您可以重新组织应用程序逻辑,以便在相应数据可用时立即计算部分结果,以便在请求最终操作时,它只对预先计算的部分结果进行操作。如果你愿意的话,一个类比是谷歌搜索请求立即收到来自预先计算的索引的数据回复,而不是等待实际的网络搜索执行。

首先,让用户等待1分钟直到页面加载已经很糟糕了。通常,面向用户的HTTP请求所需时间不应超过1。GAE给出的60秒对于危急情况来说已经太慷慨了。

我有几个建议,但我不知道你的申请说明你需要什么:

  1. 预先计算。在用户请求之前加载、计算并存储lineups值。为此,您可以使用GAE后端实例,该实例的运行时间可能超过60秒
  2. 用户真的需要那么多数据吗?一般来说,若数据太多以至于计算机无法对其进行排序,那个么向用户显示的数据就太多了。也许你的用户只需要看到其中的一小部分(比如前10名玩家,或者一些汇总统计数据)。然后对makeLineups()中使用的算法进行改进就可以达到目的
  3. 推迟。如果不能执行1或2,则可以选择将计算推迟到任务API。为此,您的前端应该:
  4. 使用任务队列登记任务:https://cloud.google.com/appengine/docs/python/taskqueue/
    • 使用channel API向用户打开通道:https://cloud.google.com/appengine/docs/python/channel/
    • 将该用户的channel_id保存到数据存储
    • 完成通话。在UI上,用户会看到一条类似"请稍候,我们正在计算数字"的消息
    • 同时,GAE后端执行您排队的任务。该任务计算makeLineups()的值。一旦完成,该任务将从数据存储中获取channel_id,并向那里发送计算出的lineups
    • 用户前端接收价值,让用户满意
  5. 除了任务API,还有新的后台线程,它们可能更容易、更适合您的情况:https://cloud.google.com/appengine/docs/python/modules/#Python_Background_threads基本上,您不将任务排入队列,而是调用'background_thread.BackgroundThread(),其余部分保持不变UPDATE这只适用于后端模块(基本或手动扩展,而不是自动扩展)。在前端(默认)模块上,自定义线程的寿命不能超过HTTP请求,因此也被限制为60秒

如果有帮助,请告诉我。

最新更新