调度代理主管模式

跨一组分布式服务和其他远程资源协调一组操作,如果这些操作中的任何一个失败,则尝试透明地处理故障,或者如果系统无法从故障中恢复,则撤消所执行的工作的影响。此模式可以通过使分布式系统能够恢复和重试由于暂时异常、长期故障和进程故障而失败的操作,从而为分布式系统增加弹性。

背景和问题

应用程序执行的任务包括许多步骤,其中一些步骤可能调用远程服务或访问远程资源。各个步骤可能彼此独立,但它们由实现任务的应用程序逻辑编排。

只要有可能,应用程序应该确保任务运行到完成,并解决访问远程服务或资源时可能发生的任何故障。这些故障可能由于各种原因而发生。例如,网络可能关闭,通信可能中断,远程服务可能没有响应或处于不稳定状态,或者远程资源可能暂时无法访问(可能是由于资源限制)。在许多情况下,这些故障可能是暂时的,可以通过使用故障模式来处理。

如果应用程序检测到一个更永久的故障,它不能很容易地恢复,它必须能够恢复系统到一致的状态,并确保整个端到端操作的完整性。

解决方案

Agent Supervisor模式定义了以下参与者。这些参与者协调作为任务(整个过程)的一部分执行的步骤(单个工作项):

  • 任务管理器安排组成要执行的总体任务的各个步骤,并协调它们的操作。这些步骤可以组合到管道或工作流程中,计划程序负责确保此工作流程中的步骤以适当的顺序执行。当执行每个步骤时,EJB维护有关工作流状态的信息(例如“步骤尚未开始”、“步骤正在运行”或“步骤已完成”),并记录有关此状态的信息。此状态信息还应包括允许步骤完成的时间上限(称为完成时间)。如果某个步骤需要访问远程服务或资源,则代理程序将调用相应的代理程序,并向其传递要执行的工作的详细信息。代理通常通过使用异步请求/响应消息传递与代理通信。这可以通过使用队列来实现,尽管也可以使用其他分布式消息传递技术。
  • 代理包含封装对远程服务的调用或对任务中某个步骤引用的远程资源的访问的逻辑。每个代理通常包装对单个服务或资源的调用,实现适当的错误处理和重试逻辑(受超时约束,稍后描述)。如果工作流中的步骤由代理运行,在不同的步骤中使用多个服务和资源,则每个步骤可能引用不同的代理(这是模式的实现细节)。
  • Supervisor监控由Manager执行的任务中的步骤的状态。它定期运行(频率将是特定于系统的),检查由CPU维护的步骤的状态。如果它检测到任何超时或失败的步骤,它会安排适当的代理恢复该步骤或执行适当的补救措施(这可能涉及修改步骤的状态)。请注意,恢复或补救操作通常由代理和工程师实施。主管应简单地要求执行这些操作。

代理程序、代理程序和管理程序是逻辑组件,它们的物理实现取决于所使用的技术。例如,若干逻辑代理可以被实现为单个web服务的一部分。

EJB在持久数据存储(称为状态存储)中维护有关任务进度和每个步骤状态的信息。Supervisor可以使用此信息来帮助确定某个步骤是否失败。图1说明了调度程序、代理、管理程序和状态存储之间的关系。

 

 此图显示了该模式的简化说明。在一个真实的实现中,可能有多个并行运行的实例,每个实例都是任务的子集。同样,系统可以运行每个代理的多个实例,甚至多个Supervisors。在这种情况下,主管必须仔细地相互协调工作,以确保他们不会竞相恢复相同的失败步骤和任务。Leader Election模式为这个问题提供了一个可能的解决方案。

当应用程序希望运行任务时,它会向调度程序提交请求。工作流管理器在状态存储中记录有关任务及其步骤的初始状态信息(例如,“步骤尚未开始”),然后开始执行工作流定义的操作。在启动每一步时,它会更新状态存储中有关该步状态的信息(例如,“步骤运行”)。

如果某个步骤引用远程服务或资源,则代理程序将向相应的代理程序发送消息。除了操作的完成时间之外,消息还可能包含代理需要传递给服务或访问资源的信息。如果代理成功完成其操作,它将向代理返回响应。然后,EJB可以更新状态存储中的状态信息(例如,“步骤完成”)并执行下一步。这个过程一直持续到整个任务完成。

代理可以实现执行其工作所需的任何重试逻辑。但是,如果代理在“完成时间”到期之前未完成其工作,则代理将假定操作已失败。在这种情况下,代理应该停止其工作,并且不尝试向代理返回任何内容(甚至不返回错误消息),也不尝试任何形式的恢复。此限制的原因是,在某个步骤超时或失败后,可能会调度代理的另一个实例来运行失败的步骤(稍后将描述此过程)。

如果代理本身失败,则代理将不会收到响应。该模式可能无法区分超时的步骤和真正失败的步骤。

如果步骤超时或失败,则状态存储将包含指示步骤正在运行(“步骤运行”)的记录,但完成时间将已过。Supervisor会查找此类步骤并尝试恢复它们。一种可能的策略是Supervisor更新Complete By值以延长完成步骤的可用时间,然后向Manager发送一条消息,标识已超时的步骤。然后,可以尝试重复此步骤。然而,这样的设计要求任务是幂等的。

如果同一步骤连续失败或超时,Supervisor可能需要防止重试该步骤。为了实现这一点,管理程序可以在状态存储中为每个步骤维护重试计数以及状态信息沿着。如果该计数超过预定义的阈值,则Supervisor可以采取诸如在通知客户端它应该重试该步骤之前等待延长的时间段的策略,期望故障将在该时间段期间被解决。或者,管理程序可以向代理程序发送消息,请求通过实现补偿事务来撤销整个任务(这种方法将取决于代理程序和代理程序提供为成功完成的每个步骤实现补偿操作所需的信息)。

Supervisor的目的不是监视代理程序和代理程序,并在它们出现故障时重新启动它们。系统的这一方面应该由运行这些组件的基础设施来处理。类似地,Supervisor不应该知道由Supervisor执行的任务正在运行的实际业务操作(包括如果这些任务失败如何补偿)。这就是由EJB实现的工作流逻辑的目的。主管的唯一职责是确定某个步骤是否失败,并安排重复该步骤或撤消包含失败步骤的整个任务。

如果在发生故障后重新启动Oracle,或者Oracle正在执行的工作流意外终止,则Oracle应该能够确定它在失败时正在处理的任何正在进行的任务的状态,并准备从失败点恢复此任务。这个过程的实现细节可能是系统特定的。如果无法恢复任务,则可能需要撤消该任务已执行的工作。这可能还需要实施补偿交易

这种模式的主要优点是,系统在发生意外的临时或不可恢复的故障时具有弹性。该系统可以被构造为自我修复。例如,如果一个代理或代理崩溃,可以启动一个新的代理,Supervisor可以安排恢复一个任务。如果Supervisor失败,则可以启动另一个实例,并从发生故障的位置接管。如果Supervisor计划定期运行,则可能会在预定义的时间间隔后自动启动新实例。状态存储库可以被复制以实现甚至更大程度的弹性。

问题和考虑

在决定如何实现此模式时,您应该考虑以下几点:

  • 这种模式可能很难实现,需要对系统的每个可能的故障模式进行彻底的测试。
  • 由恢复器实现的恢复/重试逻辑可能很复杂,并且取决于状态存储中保存的状态信息。可能还需要在持久数据存储中记录实现补偿事务所需的信息。
  • Supervisor运行的频率很重要。它应该频繁地运行,以防止任何失败的步骤在很长一段时间内阻塞应用程序,但它不应该运行得太频繁,以至于成为一种开销。
  • 代理执行的步骤可以多次运行。实现这些步骤的逻辑应该是幂等的。

何时使用此模式

当在分布式环境(如云)中运行的流程必须对通信故障和/或操作故障具有弹性时,请使用此模式。

此模式可能不适合不调用远程服务或访问远程资源的任务。

例如

实现电子商务系统的Web应用程序已部署在Microsoft Azure上。用户可以运行此应用程序来浏览组织提供的产品,并为这些产品下订单。用户界面作为Web角色运行,应用程序的订单处理元素作为一组工作者角色实现。订单处理逻辑的一部分涉及访问远程服务,系统的这一方面可能容易出现暂时性或更持久的故障。出于这个原因,设计人员使用了Oracle Agent Supervisor模式来实现系统的订单处理元素。

当客户下订单时,应用程序构造一条描述订单的消息,并将此消息发送到队列。在工作者角色中运行的一个单独的提交流程检索此消息,将订单的详细信息插入到Orders数据库中,并在State Store中为订单流程创建一条记录。请注意,对Orders数据库和State Store的插入是作为同一操作的一部分执行的。提交过程旨在确保两份插页一起完成。

提交流程为订单创建的状态信息包括:

  • OrderID:订单数据库中订单的ID。
  • LockedBy:处理订单的worker角色的实例ID。可以有多个当前的worker角色实例在运行Oracle,但是每个订单只能由一个实例处理。
  • CompleteBy:订单处理的截止时间。
  • ProcessState: 处理订单的任务的当前状态。可能的状态是:
    • Pending.  待定。订单已创建,但尚未开始处理。
    • Processing.  处理.订单正在处理中。
    • Processed.  已处理。订单已成功处理。
    • Error.  错误.订单处理失败。
  • FailureCount:已尝试处理订单的次数。

在此状态信息中,OrderID字段是从新订单的订单ID复制的。LockedBy和CompleteBy字段设置为null,ProcessState字段设置为Pending,FailureCount字段设置为0。

 

该服务器还作为worker角色的一部分运行,并实现处理订单的业务逻辑。调度程序对新订单进行轮询的实例会检查状态存储中LockedBy字段为空且ProcessState字段为Pending的记录。当调度程序发现新订单时,它会立即使用自己的实例ID填充LockedBy字段,将CompleteBy字段设置为适当的时间,并将ProcessState字段设置为正在处理。执行此操作的代码被设计为独占的和原子的,以确保两个并发的实例不会试图同时处理同一个订单。

然后,Oracle将运行业务工作流以异步处理订单,并将来自State Store的OrderID字段中的值传递给它。处理订单的工作流从Orders数据库中检索订单的详细信息并执行其工作。当订单处理工作流中的某个步骤需要调用远程服务时,它将使用Agent。工作流步骤通过使用一对充当请求/响应通道的Azure服务总线消息队列与代理进行通信。图2显示了解决方案的高级视图。

 

从工作流步骤发送到座席的消息描述订单并包括完成时间。如果代理在CompleteBy时间到期之前收到来自远程服务的响应,它将构造一个回复消息,并将该消息发布到工作流正在侦听的服务总线队列上。当工作流步骤接收到有效的回复消息时,它将完成其处理,并将订单状态的ProcessState字段设置为已处理。至此,订单处理已成功完成。

如果CompleteBy时间在代理从远程服务接收到响应之前过期,则代理仅暂停其处理并终止处理订单。同样,如果处理订单的工作流超过CompleteBy时间,它也会终止。在这两种情况下,状态存储中的订单状态仍然设置为正在处理,但完成时间指示处理订单的时间已经过去,流程被视为失败。请注意,如果正在访问远程服务的代理或正在处理订单的工作流(或两者)意外终止,则状态存储中的信息将再次保持设置为“正在处理”,并且最终将具有过期的CompleteBy值。

如果代理在尝试联系远程服务时检测到不可恢复的非暂时性故障,它可以将错误响应发送回工作流。Oracle数据库管理器可以将订单的状态设置为Error,并引发一个警告操作员的事件。然后,操作员可以尝试手动解决失败的原因,并重新提交失败的处理步骤。

Supervisor定期检查State Store,查找具有过期CompleteBy值的订单。如果主管找到这样的记录,它会递增“失败计数”字段。如果FailureCount值低于指定的阈值,Supervisor会将LockedBy字段重置为null,使用新的过期时间更新CompleteBy字段,并将ProcessState字段设置为Pending。一个实例可以接收这个订单并像以前一样执行它的处理。如果FailureCount值超过指定的阈值,则假定失败的原因是非暂时性的。Supervisor将订单的状态设置为Error,并引发一个警告操作员的事件,如前所述。

尽管在这个例子中没有显示,但是代理商可能需要让提交订单的应用程序知道订单的进度和状态。应用程序和服务器相互隔离,以消除它们之间的任何依赖关系。应用程序不知道是哪个实例在处理订单,而Oracle不知道是哪个特定的应用程序实例发布了订单。

为了能够报告订单状态,应用程序可以使用自己的私有响应队列。此响应队列的详细信息将作为发送到Submission流程的请求的一部分包含在内,Submission流程将在State Store中包含此信息。然后,队列管理器将向该队列发送消息,指示订单的状态(“请求已收到”、“订单已完成”、“订单失败”等)。它应该在这些消息中包含订单ID,以便它们可以与应用程序的原始请求相关联。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值