Orchestrator 的一个妙处是它可以自动处理 Runbook 的多实例线程,或者根据您的需要将它们排队。例如,您可以将处理新用户的 Runbook 添加到 Active Directory,对于大公司而言,可能存在多个并发请求,因此可能需要运行 Runbook 的多个实例。或者,您可能有一个更改系统的事务类型的流程,并希望确保在多个用户请求更改时将这些请求排队并按顺序运行,以避免相互之间发生潜在冲突。通过 Runbook 的“属性”对话框中的“作业并发”选项卡可以轻松控制此 Runbook 行为:

默认情况下,该值设置为 1,这意味着一个 Runbook 将运行一个实例,并将所有其他请求排队。如果希望增加此数字,以允许多个并发实例,只需将该数字调大一些即可。相当容易,不是吗?

但是,如果您有一些作业需要作为单一实例运行,但希望在当前作业完成之前忽略所有其他请求,应该怎么办?此情况的一个示例是云服务的自动横向扩展。让我们假定您在使用 Operations Manager 监视一个服务,一个性能警报触发一个 Runbook,开始横向扩展该服务。该横向扩展进程可能需要一些时间,并且在该进程完成之后可能还需要一段时间,以使性能平均数下降到一个可接受的范围(我们称其为“冷却”期间)。无论出于何种原因,都可能会再次触发警报,告诉 Orchestrator 纵向扩展该服务。如果已经有一个 Runbook 正在纵向扩展该服务,您不希望接受任何其他请求,当然也不希望只是将它们排队。您知道您只是想忽略或丢弃任何请求,直到此 Runbook 完成之后为止。那么如何处理此情况?

注意:与任何示例一样,我向您展示的是一个相对简单的视图,在现实世界中,您拥有的可能是一些更为详细的逻辑。不过,我认为通过这个基本示例可以学到这种理念,并在自己的环境中根据情况举一反三。

首先,创建一个新的 Runbook。现在,如果希望避免将 Runbook 作业的请求排队,我们首先需要做的是增加同步作业的数量。在上面所示的对话框中,我们将该数字增加到了 10(这样,如果后续请求快速连发,我们可以处理它们而无需排队)。

接下来,将“初始化数据”活动和“查询数据库”活动拖放到 Runbook 并将它们链接起来。

我们在这里完成的工作基本上是,在 Orchestrator 数据库中查询包含此 Runbook 中“初始化数据”活动的活动 ID 的任何活动作业 (TimeEnded = NULL)。但是我们需要采取迂回的办法完成此工作。在运行时我们无法访问 Runbook 的 ID,因此我们需要使用连接两个表的查询从活动 ID 中推测该 ID,如下图所示:

您可能会注意到,查询中执行了一些奇怪的字符串替换。此问题是由于每个活动的活动 ID 属性都格式化为带花括号的 GUID 形式,如下所示:

{5AC4D430-BF24-4E70-B117-C3ABB69A06F5}

不过,数据库中的数据格式不带花括号,因此需要重新格式化 ID 以剥离花括号。幸运的是,SQL 提供了一些格式化字符串的简单方法,因此我们不必在这两个活动之间放置其他活动来转换字符串。

我要再向 Runbook 添加一些其他活动,以便它可以执行一些操作。

我在其中放置了一个“sleep”(休眠)活动,以保持该作业运行 60 秒,与此同时我激活更多实例来测试此功能。我还添加了一个“发送平台事件”活动,以便在故意丢弃新作业时我可以得到通知。我还从 Query Database 活动中修改了链接条件,以相应地路由进程流。对于指向 [Sleep 60](休眠 60 秒)活动(以及指向“real”Runbook 的操作部分)的链接,我将链接条件设置为:如果此 Runbook 只有一个正在运行的实例(包括此 Runbook),则执行操作。

对于“发送平台事件”活动链接,我将其设置为:如果存在此 Runbook 的多个最新实例,则执行此路由。

在 Runbook 中检查之后,我切换到 Orchestration 控制台并浏览到该 Runbook。然后在“操作”窗格上,我单击“启动 Runbook”,然后单击“开始”按钮,重复了几遍同样的操作。在刷新视图之后,可以看到我的原始实例仍在运行,但我启动的其他两个实例已经完成。

在左窗格中单击“事件”,可以看到早先完成的两个 Runbook 生成了预期的事件。

在页面底部,可以看到此消息的描述:

现在,就我个人而言,如果在 Runbook 的预期完整操作完成之前停止了它,我不一定希望该 Runbook 显示为成功完成。我可能想让它显示为一个警告,以便引起我的注意,而不像事件通知那样(当前每种情况各不相同)。因此,在此情况下,我可以创建一个自定义活动,该活动不执行任何操作,只是从 Runbook 抛出警告,导致该 Runbook 停止并带一个警告状态。我将在以后的博客中提供此代码的示例。