文章
· 九月 27 阅读大约需 11 分钟

使用 GitLab 持续交付 InterSystems 解决方案 – 第 11 部分:互操作性

欢迎来到我的 CI/CD 系列的下一个章节,我们将探讨使用 InterSystems 技术和 GitLab 进行软件开发可以采用的几种方式。

今天,我们来谈谈互操作性。

问题

当您有一个有效的互操作性生产时,您有两个独立的流程:一个是处理消息的可以正常运行的生产流程,另一个是更新代码、生产配置和系统默认设置的 CI/CD 流程。

显然,CI/CD 流程会影响互操作性。 但问题是:

  • 更新期间究竟发生了什么?
  • 我们需要做些什么以在更新期间尽可能缩短或消除生产停机时间?

术语

  • 业务主机 (BH) – 互操作性生产的一个可配置元素:业务服务 (BS)、业务流程(BP、BPL)或业务操作 (BO)。
  • 业务主机作业 (Job) – 运行业务主机代码并由互操作性生产管理的 InterSystems IRIS 作业。
  • 生产 – 业务主机的互联集合。
  • 系统默认设置 (SDS) – 特定于安装 InterSystems IRIS 的环境的值。
  • 有效消息 – 当前正在由某个业务主机作业处理的请求。 一个业务主机作业最多只能有一条有效消息。 没有有效消息的业务主机作业处于空闲状态。

发生了什么?

我们从生产生命周期开始。

生产启动

首先,可以启动生产。 每个命名空间只能同时运行一个生产,通常而言(除非您真正知道自己在做什么以及为什么这样做),每个命名空间内只能运行一个生产。 不推荐在一个命名空间中于两个或多个不同的生产之间来回切换。 启动生产会启动生产中定义的所有已启用的业务主机。 某些业务主机启动失败不会影响生产启动。

提示:

  • 可以从系统管理门户或者调用以下代码来启动生产:##class(Ens.Director).StartProduction("ProductionName")
  • 通过实现 OnStart 方法可以在生产启动时(在启动任何业务主机作业之前)执行任意代码
  • 生产启动是一个可审核事件。 您始终可以在审核日志中查看是谁在何时启动的生产。

生产更新

在生产启动后,Ens.Director 会持续监视正在运行的生产。 生产存在两种状态:目标状态,在生产类和系统默认设置中定义;以及运行状态 – 当前运行的作业及其创建时应用的设置。 如果所需状态与当前状态相同,则一切正常;但如果有差异,就可以(也应该)更新生产。 通常,您会在系统管理门户的“生产配置”页面上看到一个红色的 Update 按钮。

更新生产意味着尝试使当前生产状态与目标生产状态匹配。

当您运行 ##class(Ens.Director).UpdateProduction(timeout=10, force=0) 以更新生产时,它会为每个业务主机执行以下操作:

  1. 将有效设置与生产/SDS/类设置进行比较
  2. 当且仅当 (1) 显示不匹配时,业务主机会被标记为过时并需要更新。

为每个业务主机运行此操作后,UpdateProduction 会构建一组更改:

  • 要停止的业务主机
  • 要启动的业务主机
  • 要更新的生产设置

然后,应用这些更改。

通过这种方式,“更新”设置而不更改任何内容不会导致生产停机。

提示:

  • 可以从系统管理门户或者调用以下代码来更新生产:##class(Ens.Director).UpdateProduction(timeout=10, force=0)
  • 默认的系统管理门户更新超时为 10 秒。 如果您知道处理消息需要更长时间,请调用 Ens.Director:UpdateProduction 并设置更长的超时。
  • 更新超时是一个生产设置,您可以将其更改为更大的值。 此设置适用于系统管理门户。

代码更新

UpdateProduction 不会使用过时的代码更新 BH。 这是一种以安全为导向的行为,但如果您想要在底层代码更改时自动更新所有正在运行的 BH,请按以下步骤操作:

首先,按以下方式加载和编译:

do $system.OBJ.LoadDir(dir, "", .err, 1, .load)
do $system.OBJ.CompileList(load, "curk", .errCompile, .listCompiled)

现在,listCompiled 将包含由于 u 标志而实际编译的所有条目(使用 git 差异来尽可能减小加载集)。 使用此 listCompiled 获取所有已编译类的 $lb:

set classList = ""
set class = $o(listCompiled(""))
while class'="" { 
  set classList = classList _ $lb($p(class, ".", 1, *-1))
  set class=$o(listCompiled(class))
}

然后,计算需要重启的 BH 的列表:

SELECT %DLIST(Name) bhList
FROM Ens_Config.Item 
WHERE 1=1
  AND Enabled = 1
  AND Production = :production
  AND ClassName %INLIST :classList

最后,在获取 bhList 后,停止并启动受影响的主机:

for stop = 1, 0 {
  for i=1:1:$ll(bhList) {
    set host = $lg(bhList, i)
    set sc = ##class(Ens.Director).TempStopConfigItem(host, stop, 0)
  }
  set sc = ##class(Ens.Director).UpdateProduction()
}

生产停止

生产可以停止,这意味着向所有业务主机作业发送关闭请求(如果存在,则在它们处理完有效消息后安全地关闭)。

提示:

  • 可以从系统管理门户或者调用以下代码来停止生产:##class(Ens.Director).StopProduction(timeout=10, force=0)
  • 默认的系统管理门户停止超时为 120 秒。 如果您知道处理消息需要更长时间,请调用 Ens.Director:StopProduction 并设置更长的超时。
  • 关闭超时是一个生产设置。 您可以将其更改为更大的值。 此设置适用于系统管理门户。
  • 通过实现 OnStop 方法可以在生产停止时执行任意代码
  • 生产停止是一个可审核事件,您始终可以在审核日志中查看是谁在何时停止的生产。

重要的一点是,生产是业务主机的总和:

  • 启动生产意味着启动所有已启用的业务主机。
  • 停止生产意味着停止所有正在运行的业务主机。
  • 更新生产意味着计算出过时的业务主机的子集,因此首先停止它们,然后立即重新启动。 此外,新增的业务主机只会启动,从生产中删除的业务主机只会停止。

这会将我们带到业务主机的生命周期。

业务主机启动

业务主机由相同的业务主机作业组成(根据池大小设置的值)。 启动业务主机意味着启动所有业务主机作业。 它们会并行启动。

单个业务主机作业的启动方式如下:

  1. 互操作性作业是一个将成为业务主机作业的新进程。
  2. 新进程注册为互操作性作业。
  3. 业务主机代码和适配器代码加载到进程内存中。
  4. 与业务主机和适配器相关的设置加载到内存中。 优先级顺序如下:
    a. 生产设置(覆盖系统默认设置和类设置)。
    b. 系统默认设置(覆盖类设置)。
    c. 类设置。
  5. 作业准备就绪并开始接受消息。

在完成 (4) 后,作业无法更改设置或代码,因此当您导入新的/相同的代码和新的/相同的系统默认设置时,它不会影响当前正在运行的互操作性作业。

业务主机停止

停止业务主机作业意味着:

  1. 互操作性命令作业停止接受更多消息/输入。
  2. 如果存在有效消息,业务主机作业具有一定的超时秒数来处理该消息(完成消息会结束 BO 的 OnMessage 方法,BS 的 OnProcessInput,BPL BP 的状态 S<int> 方法以及 BP 的 On* 方法)。
  3. 如果在超时前有效消息未被处理且 force=0,则生产更新会在该业务主机上失败(您会在 SMP 中看到一个红色的 Update 按钮)。
  4. 如果此列表中的任何一项为真,则表示停止成功:
    • 没有有效消息
    • 有效消息在 timeout 之前处理完
    • 有效消息在超时之前未处理完但 force=1
  5. 作业的互操作性取消注册并停止。

业务主机更新

业务主机更新意味着停止当前运行的业务主机作业并启动新作业。

业务规则、路由规则和 DTL

所有业务主机会在新版本的业务规则、路由规则和 DTL 可用时立即开始使用它们。 在这种情况下,不需要重启业务主机。

离线更新

不过,有时生产更新需要单个业务主机停机。

规则取决于新代码

考虑接下来的情况。 您有一个当前的路由规则 X,它根据任意标准将消息路由到业务流程 A 或 B。
在新提交中,您同时添加:

  • 业务流程 C
  • 新版本的路由规则 X,可以将消息路由到 A、B 或 C。

在此场景下,您不能先加载规则,然后再更新生产。 原因在于,新编译的规则会立即开始将消息路由到业务流程 C,而 InterSystems IRIS 可能尚未编译该规则,或者互操作性尚未更新以供使用。
在这种情况下,您需要禁用包含路由规则的业务主机,更新代码,更新生产,然后再次启用业务主机。

注:

  • 如果您使用生产部署文件更新生产,它会自动禁用/启用所有受影响的 BH。
  • 对于 InProc 调用的主机,编译会使调用方持有的特定主机的缓存失效。

业务主机之间的依赖关系

业务主机之间的依赖关系至关重要。 假设您有业务流程 A 和 B,其中 A 向 B 发送消息。
在新提交中,您同时添加:

  • 新版本的流程 A,可以在向 B 发送的请求中设置新属性 X
  • 新版本的流程 B,可以处理新属性 X

在此场景中,我们必须首先更新流程 B,然后更新流程 A。 您可通过以下两种方式之一完成此操作:

  • 在更新期间禁用业务主机
  • 将更新拆分为两步:首先,仅更新流程 B,然后在单独的更新中开始从流程 A 向流程 B 发送消息。

这个主题一个更具挑战性的变体是,新版本的流程 A 和流程 B 与旧版本不兼容,这需要业务主机停机。

队列

如果您知道更新后某个业务主机将无法处理旧消息,则需要确保在更新前该业务主机队列为空。 为此,请禁用所有向该业务主机发送消息的业务主机,并等到其队列变空。

BPL 业务流程中的状态更改

首先,简单介绍一下 BPL BP 的运作方式。 在您编译 BPL BP 后,会在与完整 BPL 类同名的软件包中创建两个类:

  • Thread1 类包含方法 S1、S2、... SN,对应于 BPL 中的活动
  • Context 类包含所有上下文变量,以及 BPL 将执行的下一个状态(即 S5

此外,BPL 类是持久类,可以存储当前正在处理的请求。

BPL 的工作方式是在 Thread 类中执行 S 方法并相应地更新 BPL 类表、Context 表和 Thread1 表,其中一条“正在处理”的消息是 BPL 表中的一行。 请求处理完后,BPL 会删除 BPL、ContextThread 条目。 由于 BPL BP 是异步的,通过在 S 调用之间保存信息并在不同请求之间切换,一个 BPL 作业可同时处理多个请求。
例如,BPL 处理一个请求,直至其到达 sync 活动 – 等待来自 BO 的回答。 它会将当前上下文保存到磁盘中,同时将 %NextState 属性(位于 Thread1 类中)设置为响应活动 S 方法,并在 BO 回答前继续处理其他请求。 BO 回答后,BPL 会将上下文加载到内存中,并执行与 %NextState 属性中保存的状态对应的方法。

现在,当我们更新 BPL 时会发生什么?
首先,我们需要检查是否至少满足以下两个条件之一:

  • 更新期间,上下文表为空,这意味着没有正在处理的有效消息。
  • 新状态与旧状态相同,或者新状态是在旧状态之后添加的。

如果至少满足一个条件,我们就可以开始更新。 要么没有需要更新后 BPL 处理的更新前请求,要么状态是在结束时添加的,这意味着旧请求也可以进入其中(假设更新前请求与更新后 BPL 活动和处理兼容)。

但是,如果您有正在处理的有效请求,而 BPL 更改了状态顺序,该怎么办? 理想情况下,如果您可以等待,则禁用 BPL 调用方并等待队列为空。 验证上下文表是否也为空。 请记住,队列只显示未处理的请求,而上下文表存储正在处理的请求,因此您可能会遇到一个非常繁忙的 BPL 显示零队列大小的情况,这是正常的。
之后,禁用 BPL,执行更新并启用所有之前禁用的业务主机。

如果无法实现(通常是在 BPL 非常长的情况下,例如,我记得我更新过一个花了大约一周时间处理请求的 BPL,或者更新窗口太短),请使用 BPL 版本控制

或者,您也可以编写一个更新脚本。 在此更新脚本中,将旧的后续状态映射到新的后续状态并在 Thread1 表上运行,以便更新的 BPL 可以处理旧请求。 当然,在更新期间必须禁用 BPL。
也就是说,这是一种极为罕见的情况,通常您不必这样做,但如果您需要这样做,方法就是如此。

结论

为了将底层代码更改后实现生产所需的操作数降到最低,互操作性实现了一种复杂的算法。 每次 SDS 更新时,调用具有安全超时的 UpdateProduction。 对于每次代码更新,您都需要决定一种更新策略。

通过使用 git 差异减少编译的代码量有助于缩短编译时间,但利用自身“更新”代码并重新编译它或使用相同的值“更新”设置不会触发或要求进行生产更新。

更新和编译业务规则、路由规则和 DTL 可使它们在未进行生产更新的情况下立即可用。

最后,生产更新是一项安全操作,通常不需要停机。

链接

在本文撰写期间,@James MacKeith@Dmitry Zasypkin@Regilo Regilio Guedes de Souza 提供了宝贵的帮助,作者对此深表感谢。

讨论 (0)0
登录或注册以继续