我如何 return 来自 Google App Engine 中延迟任务的数据
How do I return data from a deferred task in Google App Engine
原问题
我目前正在尝试升级我的 Web 应用程序的工作版本,我 运行 遇到了一个任务需要很长时间才能完成的问题HTTP 请求。该应用程序通过 HTTP Post 操作从 JavaScript 前端获取一个 JSON 列表,并且 returns 该列表的 sorted/sliced 版本。随着输入列表变长,排序操作需要更长的时间来执行(显然),所以在适当长的输入列表上,我达到 60 秒 HTTP 请求超时,应用程序失败。
我想开始使用延迟库来执行排序任务,但我不清楚在执行该任务后如何 store/retrieve 数据。这是我当前的代码:
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 前端如何知道操作何时完成?我已经阅读了 Google 网站上的文档,但我仍然不清楚如何完成此任务。
我是如何解决的
使用已接受答案中的基本大纲,我是这样解决这个问题的:
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 不熟悉,但这是一个相当笼统的问题,所以我可以给你一些建议。
您的总体思路是正确的,所以我将对其进行扩展。工作流程可能如下所示:
- 您收到创建阵容的请求。您在数据存储中为其创建一个新实体。它应该包含一个 ID(稍后您将需要它来检索结果)和一个状态 (PENDING|DONE|FAILED)。如果对您有用,您还可以保存请求中的数据。
- 您推迟计算并立即 return 响应。响应将包含任务的 ID。当计算完成后,它会将任务的结果保存在数据存储中并更新任务的状态。该结果将包含任务 ID,以便我们可以轻松找到它。
- 前端收到ID后,开始轮询结果。使用
setTimeout
或 setInterval
将带有任务 ID 的请求发送到服务器(这是一个单独的端点)。服务器检查任务的状态,如果完成则return返回结果(如果失败则出错)。
- 前端获取数据并停止轮询。
通常您不能再回复原始请求,因为原始请求的上下文消失了。 也许,if 你从请求处理程序 return 没有回复并且 if 不知何故' 终止来自客户端的连接并且 if 你能够以某种方式保留处理程序对象,以便稍后可以在另一个(内部)请求中恢复它并使用恢复的副本回复它符合最初的要求...充其量是一种远射。
一个选项是将操作拆分为一个序列:
- 开始操作的第一个请求
- 后续一个或多个轮询请求,直到操作完成并且结果可用
如果昂贵的操作主要在调用操作之前可用的数据上执行,则另一种方法可能是可行的。您可以重新组织应用程序逻辑,以便在相应数据可用时立即计算部分结果,以便在请求最终操作时仅对预先计算的部分结果进行操作。如果您愿意,可以打个比方 Google 搜索请求立即收到来自预先计算的索引的数据回复,而不是等待执行实际的网络搜索。
嗯,首先,让用户等待 1 分钟直到页面加载已经很糟糕了。一般来说,面向用户的 HTTP 请求应该不会超过 1 秒。 GAE 给出的那 60 秒——对于紧急情况来说已经太慷慨了。
我有几个建议,但不知道你的应用说出你需要什么:
- 预先计算。在用户请求之前加载、计算和存储
lineups
值。为此,您可以使用 GAE 后端实例,它可以 运行 超过 60 秒。
- 用户真的需要那么多数据吗?一般来说,如果有太多数据以至于计算机无法对其进行排序——它已经太多了,无法向用户显示。可能您的用户只需要查看其中的一小部分(例如前 10 名玩家,或一些汇总统计数据)。然后改进
makeLineups()
中使用的算法就可以解决问题。
- 推迟。如果您不能执行 1 或 2,那么您的选择是将计算推迟到任务 API。为此,您的前端应该:
- 使用任务队列排队任务:https://cloud.google.com/appengine/docs/python/taskqueue/
- 使用频道 API 向用户开放频道:https://cloud.google.com/appengine/docs/python/channel/
- 将该用户的 channel_id 保存到数据存储。
- 结束通话。在 UI 上向用户显示一条消息,例如 "please wait, we're crunching down the numbers"。
- 同时,GAE后端执行您入队的任务。该任务计算
makeLineups()
的值。完成后,该任务将从 Datastore 获取 channel_id 并将 lineups
. 的计算值发送到那里
- 用户前端收到价值并让用户开心。
- 代替任务 API 有新的后台线程,它可能更适合您的情况:https://cloud.google.com/appengine/docs/python/modules/#Python_Background_threads 基本上,您调用
background_thread.BackgroundThread()
,而不是将任务排入队列,其余的保持不变。 更新 这仅适用于后端模块(基本或手动缩放,而不是自动缩放)。在前端(默认)模块上,自定义线程不能超过 HTTP 请求,因此也限制为 60 秒。
如果有帮助请告诉我。
原问题
我目前正在尝试升级我的 Web 应用程序的工作版本,我 运行 遇到了一个任务需要很长时间才能完成的问题HTTP 请求。该应用程序通过 HTTP Post 操作从 JavaScript 前端获取一个 JSON 列表,并且 returns 该列表的 sorted/sliced 版本。随着输入列表变长,排序操作需要更长的时间来执行(显然),所以在适当长的输入列表上,我达到 60 秒 HTTP 请求超时,应用程序失败。
我想开始使用延迟库来执行排序任务,但我不清楚在执行该任务后如何 store/retrieve 数据。这是我当前的代码:
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 前端如何知道操作何时完成?我已经阅读了 Google 网站上的文档,但我仍然不清楚如何完成此任务。
我是如何解决的
使用已接受答案中的基本大纲,我是这样解决这个问题的:
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 不熟悉,但这是一个相当笼统的问题,所以我可以给你一些建议。
您的总体思路是正确的,所以我将对其进行扩展。工作流程可能如下所示:
- 您收到创建阵容的请求。您在数据存储中为其创建一个新实体。它应该包含一个 ID(稍后您将需要它来检索结果)和一个状态 (PENDING|DONE|FAILED)。如果对您有用,您还可以保存请求中的数据。
- 您推迟计算并立即 return 响应。响应将包含任务的 ID。当计算完成后,它会将任务的结果保存在数据存储中并更新任务的状态。该结果将包含任务 ID,以便我们可以轻松找到它。
- 前端收到ID后,开始轮询结果。使用
setTimeout
或setInterval
将带有任务 ID 的请求发送到服务器(这是一个单独的端点)。服务器检查任务的状态,如果完成则return返回结果(如果失败则出错)。 - 前端获取数据并停止轮询。
通常您不能再回复原始请求,因为原始请求的上下文消失了。 也许,if 你从请求处理程序 return 没有回复并且 if 不知何故' 终止来自客户端的连接并且 if 你能够以某种方式保留处理程序对象,以便稍后可以在另一个(内部)请求中恢复它并使用恢复的副本回复它符合最初的要求...充其量是一种远射。
一个选项是将操作拆分为一个序列: - 开始操作的第一个请求 - 后续一个或多个轮询请求,直到操作完成并且结果可用
如果昂贵的操作主要在调用操作之前可用的数据上执行,则另一种方法可能是可行的。您可以重新组织应用程序逻辑,以便在相应数据可用时立即计算部分结果,以便在请求最终操作时仅对预先计算的部分结果进行操作。如果您愿意,可以打个比方 Google 搜索请求立即收到来自预先计算的索引的数据回复,而不是等待执行实际的网络搜索。
嗯,首先,让用户等待 1 分钟直到页面加载已经很糟糕了。一般来说,面向用户的 HTTP 请求应该不会超过 1 秒。 GAE 给出的那 60 秒——对于紧急情况来说已经太慷慨了。
我有几个建议,但不知道你的应用说出你需要什么:
- 预先计算。在用户请求之前加载、计算和存储
lineups
值。为此,您可以使用 GAE 后端实例,它可以 运行 超过 60 秒。 - 用户真的需要那么多数据吗?一般来说,如果有太多数据以至于计算机无法对其进行排序——它已经太多了,无法向用户显示。可能您的用户只需要查看其中的一小部分(例如前 10 名玩家,或一些汇总统计数据)。然后改进
makeLineups()
中使用的算法就可以解决问题。 - 推迟。如果您不能执行 1 或 2,那么您的选择是将计算推迟到任务 API。为此,您的前端应该:
- 使用任务队列排队任务:https://cloud.google.com/appengine/docs/python/taskqueue/
- 使用频道 API 向用户开放频道:https://cloud.google.com/appengine/docs/python/channel/
- 将该用户的 channel_id 保存到数据存储。
- 结束通话。在 UI 上向用户显示一条消息,例如 "please wait, we're crunching down the numbers"。
- 同时,GAE后端执行您入队的任务。该任务计算
makeLineups()
的值。完成后,该任务将从 Datastore 获取 channel_id 并将lineups
. 的计算值发送到那里
- 用户前端收到价值并让用户开心。
- 代替任务 API 有新的后台线程,它可能更适合您的情况:https://cloud.google.com/appengine/docs/python/modules/#Python_Background_threads 基本上,您调用
background_thread.BackgroundThread()
,而不是将任务排入队列,其余的保持不变。 更新 这仅适用于后端模块(基本或手动缩放,而不是自动缩放)。在前端(默认)模块上,自定义线程不能超过 HTTP 请求,因此也限制为 60 秒。
如果有帮助请告诉我。