Reliable Services 生命周期

Reliable Services 是 Azure Service Fabric 中可用的编程模型之一。 了解 Reliable Services 的生命周期时,最重要的是要理解基本生命周期事件。 事件的确切顺序取决于配置详细信息。

一般情况下,Reliable Services 生命周期包括以下事件:

  • 在启动期间:
    • 构造服务。
    • 服务可能会构造并返回零个或多个侦听器。
    • 打开返回的任何侦听器,以便与服务通信。
    • 调用服务的 runAsync 方法,使服务能够执行长时间运行的工作或后台工作。
  • 在关闭期间:
    • 取消传递给 runAsync 的取消令牌,同时关闭侦听器。
    • 销毁服务对象本身。

根据可靠服务是无状态服务还是有状态服务,Reliable Services 中事件的顺序可能略有变化。

此外,对于有状态服务,必须处理主副本交换方案。 在执行此序列期间,主副本的角色将转移到另一个副本(或者转移回来),而无需关闭服务。

最后,必须考虑错误或失败条件。

无状态服务启动

无状态服务的生命周期非常直接了当。 下面是事件的顺序:

  1. 构造服务。
  2. 调用 StatelessService.createServiceInstanceListeners(),打开返回的所有侦听器。 对每个侦听器调用 CommunicationListener.openAsync()
  3. 然后并行:
    • 调用服务的 runAsync 方法 (StatelessService.runAsync())。
    • 如果存在,则调用服务本身的 onOpenAsync 方法。 具体而言,即调用 StatelessService.onOpenAsync()。 这是一种不常见的重写,但这种调用是可行的。

无状态服务关闭

关闭无状态服务,将遵循相同的模式,但顺序相反:

  1. 关闭任何打开的侦听器。 对每个侦听器调用 CommunicationListener.closeAsync()
  2. 取消传递给 runAsync() 的取消令牌。 检查取消令牌的 isCancelled 属性是否返回 true,如果已调用,则令牌的 throwIfCancellationRequested 方法会引发 CancellationException
  3. runAsync() 完成后,调用服务的 StatelessService.onCloseAsync() 方法(如果存在)。 再次强调,这是一种不常见的重写,但它可以用于安全地关闭资源、停止后台处理、完成外部状态保存或关闭现有连接。
  4. 完成 StatelessService.onCloseAsync() 后,销毁服务对象。

有状态服务启动

有状态服务的模式与无状态服务类似,只是稍有不同。 启动有状态服务时,事件的顺序如下:

  1. 构造服务。
  2. 调用 StatefulServiceBase.onOpenAsync()。 此调用是服务中不常见的重写。
  3. 调用 StatefulServiceBase.createServiceReplicaListeners()
    • 如果服务是主要服务,则打开所有返回的侦听器。 对每个侦听器调用 CommunicationListener.openAsync()
    • 如果服务是辅助服务,则只打开已标记为 listenOnSecondary = true 的侦听器。 在辅助服务上打开的侦听器较不常见。
  4. 然后并行:
    • 如果该服务目前是主要服务,则调用该服务的 StatefulServiceBase.runAsync() 方法。
    • 调用 StatefulServiceBase.onChangeRoleAsync()。 此调用是服务中不常见的重写。

注意

对于新的辅助副本,调用 StatefulServiceBase.onChangeRoleAsync() 两次。 步骤 2 之后,当它变为空闲辅助副本时调用一次,然后在步骤 4 期间,当它变为活动辅助副本时,再调用一次。 有关副本和实例生命周期的详细信息,请参阅副本和实例生命周期

有状态服务关闭

与无状态服务一样,关闭期间的生命周期事件与启动期间是相同的,但顺序相反。 关闭有状态服务时,将发生以下事件:

  1. 关闭任何打开的侦听器。 对每个侦听器调用 CommunicationListener.closeAsync()
  2. 取消传递给 runAsync() 的取消令牌。 调用取消令牌的 isCancelled() 属性返回 true,如果已调用,则令牌的 throwIfCancellationRequested() 方法会引发 OperationCanceledException。 Service Fabric 等待 runAsync() 完成。

备注

仅当此副本是主副本时,才需要等待 runAsync 完成。

  1. runAsync() 完成后,调用服务的 StatefulServiceBase.onCloseAsync() 方法。 此调用是一种不常见的重写,但是可行的。
  2. 完成 StatefulServiceBase.onCloseAsync() 后,销毁服务对象。

有状态服务主副本交换

运行有状态服务时,只对该有状态服务的主副本打开通信侦听器并调用 runAsync 方法。 会构造辅助副本,但不会对其执行进一步的调用。 在运行有状态服务时,当前用作主副本的副本可能会更改。 对有状态副本可见的生命周期事件取决于该副本在交换期是降级还是升级了。

对于降级的主副本

Service Fabric 需要使用降级的主副本,以停止处理消息,并停止任何后台工作。 此步骤与关闭服务时类似。 一个差别在于,在此情况下不会销毁或关闭服务,因为它保留为次要副本。 将发生以下事件:

  1. 关闭任何打开的侦听器。 对每个侦听器调用 CommunicationListener.closeAsync()
  2. 取消传递给 runAsync() 的取消令牌。 检查取消标记的 isCancelled() 方法是否返回 true。 如果已调用,则令牌的 throwIfCancellationRequested() 方法将引发 OperationCanceledException。 Service Fabric 等待 runAsync() 完成。
  3. 将打开标记为 listenOnSecondary = true 的侦听器。
  4. 调用服务的 StatefulServiceBase.onChangeRoleAsync()。 此调用是服务中不常见的重写。

对于升级的次要副本

同样,Service Fabric 需要提升的次要副本,才能开始侦听网络上的消息,并启动需要完成的任何后台任务。 此过程与创建服务时类似。 不同之处在于副本本身已存在。 将发生以下事件:

  1. 为所有打开的侦听器调用 CommunicationListener.closeAsync()(使用 listenOnSecondary = true 标记)
  2. 随即将打开所有通信侦听器。 对每个侦听器调用 CommunicationListener.openAsync()
  3. 然后并行:
    • 调用服务的 StatefulServiceBase.runAsync() 方法。
    • 调用 StatefulServiceBase.onChangeRoleAsync()。 此调用是服务中不常见的重写。

注意

createServiceReplicaListeners 仅调用一次,副本升级或降级过程中不再调用;使用相同的 ServiceReplicaListener 实例,但在关闭之前的实例后会创建新的 CommunicationListener 实例(通过调用 ServiceReplicaListener.createCommunicationListener 方法)。

有状态服务关闭和主副本降级期间的常见问题

Service Fabric 更改有状态服务的主副本的原因有多种。 最常见的原因是群集重新均衡应用程序升级。 这些在操作期间,请务必使服务遵循 cancellationToken。 这在正常服务在关闭期间也适用,比如删除服务时。

如果服务不完全处理取消,可能会导致若干问题。 这些操作的速度之所以缓慢,是因为 Service Fabric 要等待服务正常停止。 最终导致升级超时失败,然后回滚。 未能遵循取消令牌也可能导致群集不均衡。 群集将由于节点变热而不均衡。 但是,将它们移到其他位置耗时过长,因此无法重新均衡服务。

由于服务有状态,所以它们也可能使用 Reliable Collections。 在 Service Fabric 中,主副本降级后,首先会撤销基础状态的写入访问权限。 这会导致可能影响服务生命周期的另外一系列问题。 集合将根据计时和是否已移动或关闭副本返回异常。 请务必正确处理这些异常。

由 Service Fabric 引发的异常可能是永久的 (FabricException) 或临时的 (FabricTransientException)。 应记录并引发永久异常。 可以基于重试逻辑重试临时异常。

测试和验证 Reliable Services 时,处理因结合使用 ReliableCollections 和服务生命周期事件而产生的异常是一个重要环节。 建议始终在负载范围内运行服务。 还应执行升级和混沌测试,然后再部署到生产环境。 以下基本步骤有助于确保已正确实现服务和处理生命周期事件。

有关服务生命周期的说明

  • runAsync() 方法和 createServiceInstanceListeners/createServiceReplicaListeners 调用都是可选的。 一项服务可能符合其中一项、两项或均不符合。 例如,如果服务执行的所有工作都只是为了响应用户调用,则无需实现 runAsync()。 只需提供通信侦听器及其关联的代码。 同样,创建和返回通信侦听器是可选的。 该服务可能具有仅后台工作要做,因此它只需实现 runAsync()
  • 服务成功完成 runAsync() 并从中返回即可。 这不会被视为失败条件。 它表示服务后台工作的完成。 对于有状态可靠服务,如果服务已从主副本降级,然后重新升级为主副本,则会再次调用 runAsync()
  • 如果服务因引发某种意外的异常从 runAsync() 退出,将导致失败。 已关闭服务对象,并已报告运行状况错误。
  • 虽然从这些方法返回没有时间限制,但会立即丧失写入的能力。 因此,无法完成任何实际工作。 建议尽快在收到取消请求后返回。 如果服务在合理的时间内未响应这些 API 调用,Service Fabric 可能会强行终止服务。 通常,只有在应用程序升级期间或删除服务时,才发生这种情况。 此超时默认为 15 分钟。
  • onCloseAsync() 路径中的故障将导致 onAbort() 调用。 这一调用是最后一个机会,服务会尽最大努力清理并释放其占用的资源。 当在节点上检测到永久性故障时,或者当 Service Fabric 由于内部错误而无法可靠地管理服务实例的生命周期时,通常会调用此方法。
  • 当有状态服务副本要更改角色(例如,更改为主要副本或次要副本)时,调用 OnChangeRoleAsync()。 主副本将指定为写状态(允许创建和写入可靠集合)。 辅助副本将指定为读取状态(只能从现有的可靠集合读取)。 有状态服务中的大部分工作在主副本执行。 辅助副本可执行只读验证、报表生成、数据挖掘或其他只读作业。

后续步骤