mbaynton/batch-framework

一个API和基础算法,用于高效处理可以分解为小工作单元的长运行任务。

1.0.0 2018-12-21 15:26 UTC

This package is auto-updated.

Last update: 2024-09-29 05:08:43 UTC


README

Build Status Coverage Status

这个库提供基础算法和结构,以支持将可分解为小工作单元的长运行任务通过在Web服务器上调用PHP脚本来逐步处理。这避免了在Web执行环境中常见的脚本执行时间和网络超时限制。

它强调框架本身的最低开销,以便尽可能快地完成任务。

特性包括

  • 在Web环境中运行时,支持在整个请求生命周期中处理工作单元批次。这防止了单个响应和Web服务器进程运行时间超过预期。
  • 根据过去工作单元的运行时间来高效地确定何时停止运行更多工作单元,以便请求在目标时间内完成。
  • 关注最小化涉及请求之间的移交所涉及的状态数据和访问后端存储的次数。
  • 支持并行执行那些可并行化的尴尬问题,例如,在执行过程中,各个工作单元之间不需要相互通信或协调的情况。有关并行化详情,请参阅。
  • 不需要使用特定的PHP框架,但了解控制器和服务设计模式。

作为库,它不提供“开箱即用”的功能。

依赖

  • PHP 5.4+
  • Psr\Http\Message\ResponseInterface 可通过Composer获得,以及该接口的任何实现。

文档/示例

这里的文档将帮助您开始编写与该框架一起工作的代码。如果您遇到此处信息的不完整或疑问,您可能想参考GitHub上的Curator应用程序,它使用并编写了此框架。

文档针对 v1.0.0 版本准确无误。

术语及其定义

  • 可运行:
    用户实现的一个类,它模拟一个长运行的任务。可运行实例模拟并提供单个工作单元的实现。它的 run() 方法执行实际的工作/计算,以推进任务的进度。
  • 可运行迭代器:
    一个PHP \Iterator(请扩展 AbstractRunnableIterator),它根据给定的输入(例如 Runner rank 和先前 Runner 实现上已执行的可运行数量)生成适合于应执行的整体任务的段的可运行实例。
  • 运行器:
    服务器端代码运行任务。运行器为新的可运行实例泵送可运行迭代器,启动它们,监控可运行的运行时间和剩余时间,以决定何时停止,将可运行和任务执行事件分发给任务和控制器回调,并启动可运行和任务中间结果的聚合。
  • 运行器ID:一个整数,唯一标识一个给定的逻辑 运行器。客户端应创建与框架当前 任务实例状态 支持的相应 运行器 请求一样多的请求,最初将客户端尚未使用的唯一整数ID分配给这些请求。
  • 运行器实现:
    从逻辑上讲,该框架试图创建一种假象,即由xRunners(如果x > 1则并发执行)执行nRunnable工作单元。然而,为了防止启动Runnable的HTTP请求在期望的时间内未完成,框架可能会停止启动新的Runnable,让Runner提前停止工作,并通知客户端使用相同的Runner id发起后续请求。每个由携带相同Runner idRunner启动并处理的HTTP请求被称为该id的化身。所有Runner的化身也将共享相同的Runner rank
  • Runner rank:
    在任务中唯一标识一个给定Runner的数字。如果你的任务只支持一个并发Runner,则该值始终为0。如果你的Task声明支持n个并发Runner,则该值从0n-1不等。与Runner id不同,其范围始终是0n-1
  • Task:
    用户实现的一个类,用于模拟长时间运行的任务。该Task充当Runnable Iterator的工厂,告诉框架如何处理Runnable的结果,如果Runnable遇到可抛出的错误或异常,可以介入,提供方法将多个Runnable结果简化为更简单的中间结果,并提供方法将完整的Runnable结果转换为Psr\Http\Message\ResponseInterface
  • Task实例状态:
    用户实现的一个类,用于模拟长时间运行的任务。Task实例状态捕捉了给定任务执行的变量属性,例如操作输入的位置、当前运行此Task的用户(以PHP会话id表示)、估计的Task大小(以Runnable的数量表示)以及Task支持的并发Runner数量。通常,可以扩展TaskInstanceState类,该类处理大多数事情,但你的任务独特输入除外。请注意,此类不打算用于捕获Runnable输出。

此框架主要在AbstractRunner类中提供了一个Runner的实现。利用此库的完整系统通常包括扩展AbstractRunner的实体,以与你的应用程序的持久层(例如数据库)接口,以及使用HttpRunnerControllerTrait的控制器或其他脚本,用于处理传入请求并与你的应用程序会话层接口。

编写长时间运行的任务通常涉及设置以下组件

  • TaskInterface的实现。
  • 扩展AbstractRunnableIterator以提供Runnables
  • 执行工作单元的RunnableInterface的实现。
  • 扩展TaskInstanceState以提供针对特定任务的输入属性。

并行化:使用多个runner

严格来说,此框架支持同时执行同一任务的多个runnable。但是,为了执行并发runnable,还需要大量的其他代码支持。

  • 你的AbstractRunner扩展必须以并发安全的方式实现其方法,特别是AbstractRunner::retrieveRunnerState()AbstractRunner::finalizeRunner()应在并发执行同一任务实例的多个实例时,以不会导致损坏或丢失写入的方式读取和写入其底层存储。
  • 必须编写你的客户端以发送多个并发批量runner请求。
  • 您想要执行的工作必须是令人尴尬地可并行化的。每个可运行的任务可以产生输出,但可运行的任务不能将其他可运行任务的输出作为输入,或者在其他可运行任务访问共享资源时相互干扰。
  • 您的任务实例状态getNumRunners()必须返回大于1,以声明对超过1个运行器的支持。
  • 您通过Task构建的可运行迭代器必须考虑运行器排名,并尽可能均匀地将总可运行的部分分配给每个运行器排名,每个可运行的工作单位恰好分配给一个运行器
  • 您的整体应用程序(请求控制器等)不应受同一用户发起的多个同时请求的影响,并且在可运行执行时不应持有PHP会话锁

为什么任务的最终结果总是HTTP响应?

将批量运行的总体结果打包成标准的HTTP响应格式,使应用程序能够接收请求并决定是否将请求延迟到批量任务。在任一情况下,客户端期望的HTTP响应最终都会生成。当客户端使用支持请求中间件和Promise模式的库实现时,这种方法效果很好。请求中间件会监视原始响应,这些响应表明需要批量任务。它不会用这个不完整的原始响应解决客户端应用程序代码的Promise,而是启动运行器请求,直到获得结果HTTP响应,然后最终用这个响应解决原始Promise。

许可证

MIT