在Google应用引擎上使用任务队列时,如何确定任务的优先级?

时间:2016-07-25 11:57:57

标签: google-app-engine task-queue

我试图解决以下问题:

  1. 我有一系列"任务"我想执行
  2. 我有固定数量的工作人员来执行这些工作程序(因为他们使用urlfetch调用外部API,并且对此API的并行调用数量有限)
  3. 我想要这些"任务"尽快执行"" (即最小延迟)
  4. 这些任务是较大任务的一部分,可以根据原始任务的大小进行分类(即,一个小的原始任务可能会产生1到100个任务,一个中等的100到1000个,一个大的任务超过1000个)。
  5. 棘手的部分:我希望有效地完成所有这些工作(即最小延迟并尽可能多地使用并行API调用 - 不超过限制),但同时尝试阻止大量任务由" large"生成延迟从"小"生成的任务的原始任务原始任务。

    换句话说:我希望有一个"优先级"用" small"分配给每个任务。具有更高优先级的任务,从而防止“大”"任务。

    有些搜索似乎没有表明预制的任何内容都可用,所以我想出了以下内容:

    • 创建三个推送队列:tasks-smalltasks-mediumtasks-large
    • 为每个请求设置最大并发请求数,使得total是最大并发API调用数(例如,如果最大并发API调用数为200,我可以设置tasks-small * max_concurrent_requests为30,tasks-medium 60和tasks-large 100)
    • 排队任务时,请检查否。每个队列中的待处理任务(使用QueueStatistics类之类的东西),如果其他队列未被100%利用,则将任务排入队列,否则只需将队列中的任务排入队列即可。

    例如,如果我们将任务T1作为小任务的一部分,请首先检查tasks-small是否有空闲"插槽"并将它排列在那里。否则请检查tasks-mediumtasks-large。如果它们都没有空闲插槽,那么无论如何都会将它排入tasks-small并在处理之前添加任务后处理它(注意:这不是最佳的,因为如果"插槽"释放在其他队列中,他们仍然无法处理来自tasks-small队列的待处理任务)

    另一种选择是使用PULL队列并拥有一个中央协调员"根据优先级从该队列中拉出并调度它们,但这似乎会增加一点延迟。

    然而,这似乎有点hackish,我想知道是否有更好的选择。

    编辑:经过一些思考和反馈后,我想以下列方式考虑使用PULL队列:

    • 有两个PULL队列(medium-taskslarge-tasks
    • 有一个并发为1的调度程序(PUSH)队列(因此​​任何时候只能运行一个调度任务)。调度任务以多种方式创建:
      • 每分钟一次的cron工作
      • 将中/大任务添加到推送队列后
      • 工作任务完成后
    • 有一个工人(PUSH)队列,其并发数等于工作人员数

    工作流程:

    • 小任务直接添加到工作队列
    • 调度程序任务,无论何时触发,都执行以下操作:
      • 估算自由工作者的数量(通过查看工作队列中正在运行的任务的数量)
      • 任何"免费"插槽从中型/大型任务PULL队列中获取任务并将其排入工作者(或者更确切地说:将其添加到工作程序PUSH队列,这将导致它被执行 - 最终 - 在工作者身上)。

    一旦实施并且至少经过适度测试,我会向您报告。

3 个答案:

答案 0 :(得分:1)

小/中/大原始任务队列本身无济于事 - 一旦原始任务入队,它们将继续产生工作任务,甚至可能破坏工作人员任务队列大小限制。因此,您需要调整/控制原始任务的排队。

我会跟踪数据存储区/ GCS中的“todo”原始任务,并且仅当相应的队列大小足够低(1个或可能是2个待处理作业)时才将这些原始任务排入,来自重复任务,cron作业或延迟任务(取决于您需要执行原始任务排队的速率),它将像推送队列调度程序一样实现所需的调步和优先级逻辑,但没有额外的延迟你提到过。

答案 1 :(得分:1)

我没有使用过拉队列,但根据我的理解,它们可以很好地适合你的用例。你可以定义3个拉队列,并让X个工作人员从他们那里拉出任务,首先尝试"小"排队然后继续前进到#34; medium"如果它是空的(其中X是你的最大并发)。你不应该需要一个中央调度员。

然而,即使没有任务(或X?),您也可以为X / threadsPerMachine工作人员付费,或者将其缩小&你自己。

所以,这是另一个想法:使用正确的maximum concurrency制作单个推送队列。收到新任务后,将其信息推送到数据存储区,然后排队通用作业。然后,该通用作业将查询数据存储区,以优先级顺序查找任务,执行它找到的第一个任务。这样,即使该作业已经从大型任务中排队,下一个作业仍将执行短任务。

答案 2 :(得分:0)

编辑:我现在迁移到一个更简单的解决方案,类似于@ eric-simonton所描述的:

  • 我有多个PULL队列,每个优先级一个
  • 许多工作人员使用端点(处理程序)
  • 处理程序生成一个随机数,并执行一个简单的“如果小于0.6,首先尝试小队列,然后尝试大队列,否则反之亦然(大而小)”
  • 如果工作人员没有完成任务或错误,他们会进行半随机指数退避,直到达到最大超时(即,他们每隔1秒开始拉动一次,并在每次空拉达30秒后大约加倍超时)

最后一点是必要的 - 除其他原因外 - 因为PULL队列中的拉/秒数限制为10k / s:https://cloud.google.com/appengine/docs/python/taskqueue/overview-pull#Python_Leasing_tasks

我实现了UPDATE中描述的解决方案:

  • 两个PULL队列(中型任务和大型任务)
  • 并发度为1
  • 的调度程序(PUSH)队列
  • 一个工作(PUSH)队列,其并发数等于工作人员数

有关详细信息,请参阅问题。一些说明:

  • 由于最终的一致性,任务可见性有一些延迟(即调度员任务有时甚至不会从拉队列中看到任务,即使它们被插在一起) - 我通过添加5秒的倒计时来解决调度程序任务,还添加一个每分钟添加一个调度程序任务的cron作业(因此,如果原始调度程序任务没有从拉取队列中“看到”任务,则另一个将在稍后出现)
  • 确保为每项任务命名以消除双重调度的可能性
  • 您不能从PULL队列中租用0项: - )
  • 批处理操作有一个上限,因此您必须在批处理任务队列调用上进行自己的批处理
  • 似乎没有办法以编程方式获取队列的“最大并行度”值,因此我不得不在调度程序中对其进行硬编码(以计算它可以安排多少任务)
  • 如果队列中已有一些(至少10个)
  • ,则不添加调度程序任务