Compartir a través de

Reliable Services 通知

通知可让客户端跟踪对它们感兴趣的对象所进行的更改。 两种类型的对象支持通知:可靠状态管理器可靠字典

使用通知的常见原因如下:

  • 生成副本状态的具体化视图(例如次要索引)或聚合筛选视图。 可靠字典中所有键的排序索引便是其中一个例子。
  • 发送监视数据,例如过去一小时内添加的用户数目。

通知会在应用操作的过程中触发。 对于主要副本,在执行 transaction.CommitAsync()this.StateManager.GetOrAddAsync() 过程中,操作将在仲裁确认之后应用。 对于次要副本,操作在复制队列数据处理期间应用。 因此,应该尽快处理通知,且同步事件不应包含任何耗费资源的操作。 否则,可能会对事务处理时间和副本生成带来负面影响。

可靠状态管理器通知

可靠状态管理器为以下事件提供通知:

  • 事务
    • 提交
  • 状态管理器
    • 重新生成
    • 添加可靠状态
    • 删除可靠状态

可靠状态管理器会跟踪当前正在进行的事务。 唯一会导致通知触发的事务状态更改是提交事务。

可靠状态管理器会维护可靠状态的集合,例如可靠字典和可靠队列。 可靠状态管理器会在此集合更改时触发通知:添加或删除可靠状态,或者重新生成整个集合时。 可靠状态管理器集合会在以下三种情况下重新生成:

  • 恢复:当副本启动时,它会从磁盘恢复其先前的状态。 恢复结束时,它会使用 NotifyStateManagerChangedEventArgs 触发事件,其中包含一组已恢复的可靠状态。
  • 完整副本:必须先生成副本,它才能加入配置集。 有时,这需要将主要副本中可靠状态管理器状态的完整副本应用到空闲的次要副本。 次要副本上的可靠状态管理器会使用 NotifyStateManagerChangedEventArgs 触发事件,其中包含一组它从主要副本获取的可靠状态。
  • 还原:在灾难恢复方案中,副本的状态可通过 RestoreAsync 从备份还原。 在这种情况下,主要副本上的可靠状态管理器会使用 NotifyStateManagerChangedEventArgs 触发事件,其中包含一组它从备份还原的可靠状态。

若要注册事务通知和/或状态管理器通知,需要在可靠状态管理器上注册 TransactionChangedStateManagerChanged 事件。 注册这些事件处理程序的常见位置是有状态服务的构造函数。 在构造函数上注册时,也不会错过 IReliableStateManager 生存期内的更改所导致的任何通知。

public MyService(StatefulServiceContext context)
    : base(MyService.EndpointName, context, CreateReliableStateManager(context))
{
    this.StateManager.TransactionChanged += this.OnTransactionChangedHandler;
    this.StateManager.StateManagerChanged += this.OnStateManagerChangedHandler;
}

TransactionChanged 事件处理程序使用 NotifyTransactionChangedEventArgs 来提供有关事件的详细信息。 它包含用于指定更改类型的操作属性(例如,NotifyTransactionChangedAction.Commit)。 也包含提供对已更改事务的引用的事务属性。

注意

现在,只有提交事务才会引发 TransactionChanged 事件。 此操作等同于 NotifyTransactionChangedAction.Commit。 但是在未来,可能会有其他类型的事务状态更改可以引发事件。 建议检查操作,仅在预期的事件发生时处理事件。

以下是 TransactionChanged 事件处理程序示例。

private void OnTransactionChangedHandler(object sender, NotifyTransactionChangedEventArgs e)
{
    if (e.Action == NotifyTransactionChangedAction.Commit)
    {
        this.lastCommitLsn = e.Transaction.CommitSequenceNumber;
        this.lastTransactionId = e.Transaction.TransactionId;

        this.lastCommittedTransactionList.Add(e.Transaction.TransactionId);
    }
}

StateManagerChanged 事件处理程序使用 NotifyStateManagerChangedEventArgs 来提供有关事件的详细信息。 NotifyStateManagerChangedEventArgs 有两个子类:NotifyStateManagerRebuildEventArgsNotifyStateManagerSingleEntityChangedEventArgs。 使用 NotifyStateManagerChangedEventArgs 中的操作属性将 NotifyStateManagerChangedEventArgs 转换为正确的子类:

  • NotifyStateManagerChangedAction.RebuildNotifyStateManagerRebuildEventArgs
  • NotifyStateManagerChangedAction.AddNotifyStateManagerChangedAction.RemoveNotifyStateManagerSingleEntityChangedEventArgs

以下是 StateManagerChanged 通知处理程序示例。

public void OnStateManagerChangedHandler(object sender, NotifyStateManagerChangedEventArgs e)
{
    if (e.Action == NotifyStateManagerChangedAction.Rebuild)
    {
        this.ProcessStateManagerRebuildNotification(e);

        return;
    }

    this.ProcessStateManagerSingleEntityNotification(e);
}

可靠字典通知

可靠字典为以下事件提供通知:

  • 重新生成:在 ReliableDictionary 从恢复的或复制的本地状态或备份恢复到可读中间恢复状态时调用。 然后,将在重新生成操作完成之前应用此恢复状态创建后的事务记录。 这些记录的应用将提供清除、添加、更新和/或删除通知。
  • 清除:在通过 ClearAsync 方法清除 ReliableDictionary 的状态后调用。
  • 添加:在向 ReliableDictionary 添加项之后调用。
  • 更新:在更新 IReliableDictionary 中的项之后调用。
  • 删除:在删除 IReliableDictionary 中的项之后调用。

若要获取可靠字典通知,需在 DictionaryChanged 上注册 IReliableDictionary 事件处理程序。 注册这些事件处理程序的常见位置是在 ReliableStateManager.StateManagerChanged 添加通知中。 在将 IReliableDictionary 添加到 IReliableStateManager 时注册,可确保不会错过任何通知。

private void ProcessStateManagerSingleEntityNotification(NotifyStateManagerChangedEventArgs e)
{
    var operation = e as NotifyStateManagerSingleEntityChangedEventArgs;

    if (operation.Action == NotifyStateManagerChangedAction.Add)
    {
        if (operation.ReliableState is IReliableDictionary<TKey, TValue>)
        {
            var dictionary = (IReliableDictionary<TKey, TValue>)operation.ReliableState;
            dictionary.RebuildNotificationAsyncCallback = this.OnDictionaryRebuildNotificationHandlerAsync;
            dictionary.DictionaryChanged += this.OnDictionaryChangedHandler;
        }
    }
}

注意

ProcessStateManagerSingleEntityNotification 是上述 OnStateManagerChangedHandler 示例所调用的示例方法。

上述代码会设置 IReliableNotificationAsyncCallback 接口以及 DictionaryChanged。 由于 NotifyDictionaryRebuildEventArgs 包含需要以异步方式枚举的 IAsyncEnumerable 接口,因此会通过 RebuildNotificationAsyncCallback 而不是 OnDictionaryChangedHandler 来触发重新生成通知。

public async Task OnDictionaryRebuildNotificationHandlerAsync(
    IReliableDictionary<TKey, TValue> origin,
    NotifyDictionaryRebuildEventArgs<TKey, TValue> rebuildNotification)
{
    this.secondaryIndex.Clear();

    var enumerator = e.State.GetAsyncEnumerator();
    while (await enumerator.MoveNextAsync(CancellationToken.None))
    {
        this.secondaryIndex.Add(enumerator.Current.Key, enumerator.Current.Value);
    }
}

注意

在上述代码中,在处理重新生成通知的过程中,会先清除所维护的聚合状态。 由于正在利用新状态重新生成可靠集合,因此与以前的所有通知不相关。

DictionaryChanged 事件处理程序使用 NotifyDictionaryChangedEventArgs 来提供有关事件的详细信息。 NotifyDictionaryChangedEventArgs 有五个子类。 使用 NotifyDictionaryChangedEventArgs 中的操作属性将 NotifyDictionaryChangedEventArgs 转换为正确的子类:

  • NotifyDictionaryChangedAction.RebuildNotifyDictionaryRebuildEventArgs
  • NotifyDictionaryChangedAction.ClearNotifyDictionaryClearEventArgs
  • NotifyDictionaryChangedAction.AddNotifyDictionaryItemAddedEventArgs
  • NotifyDictionaryChangedAction.UpdateNotifyDictionaryItemUpdatedEventArgs
  • NotifyDictionaryChangedAction.RemoveNotifyDictionaryItemRemovedEventArgs
public void OnDictionaryChangedHandler(object sender, NotifyDictionaryChangedEventArgs<TKey, TValue> e)
{
    switch (e.Action)
    {
        case NotifyDictionaryChangedAction.Clear:
            var clearEvent = e as NotifyDictionaryClearEventArgs<TKey, TValue>;
            this.ProcessClearNotification(clearEvent);
            return;

        case NotifyDictionaryChangedAction.Add:
            var addEvent = e as NotifyDictionaryItemAddedEventArgs<TKey, TValue>;
            this.ProcessAddNotification(addEvent);
            return;

        case NotifyDictionaryChangedAction.Update:
            var updateEvent = e as NotifyDictionaryItemUpdatedEventArgs<TKey, TValue>;
            this.ProcessUpdateNotification(updateEvent);
            return;

        case NotifyDictionaryChangedAction.Remove:
            var deleteEvent = e as NotifyDictionaryItemRemovedEventArgs<TKey, TValue>;
            this.ProcessRemoveNotification(deleteEvent);
            return;

        default:
            break;
    }
}

建议

  • 尽快完成通知事件。
  • 不要执行任何耗费资源的操作(例如 I/O 操作)作为同步事件的一部分。
  • 处理事件之前,先检查操作类型。 未来可能会添加新的操作类型。

需谨记以下几点:

  • 通知会在执行操作的过程中触发。 例如,在还原操作期间将触发还原通知。 必须先处理通知事件,然后才会继续还原操作。
  • 由于通知会在应用操作的过程中触发,因此,客户端只会看见本地提交操作的通知。 而且因为操作只保证会在本地提交(亦即记录),所以它们不一定可在未来恢复。
  • 在恢复路径上,会针对每个应用的操作触发单个通知。 这表示,如果事务 T1 包含 Create(X)、Delete(X) 和 Create(X),将依次收到一个针对 X 创建的通知,一个针对删除的通知,然后再收到一个针对创建的通知。
  • 对于包含多个操作的事务,操作将按用户在主要副本上收到它们的顺序应用。
  • 在处理错误进度的过程中,某些操作在次要副本上可能会撤消。 通知会针对这类恢复操作加以触发,将副本状态回滚到稳定的时间点。 恢复通知的一个重要区别,是具有重复键的事件会聚合在一起。 例如,如果恢复事务 T1,会看到一条针对 Delete(X) 的通知。

后续步骤

已知问题

  • 在某些情况下,重新生成期间可能会跳过某些事务通知。 在这种情况下,仍存在正确的值,仍可读取或迭代该值,仅缺少通知。 为了恢复内存中状态,可靠集合会使用定期压缩到检查点文件中的预写日志。 在还原期间,首先从触发重新生成通知的检查点文件加载基本状态。 然后应用日志中保存的事务,每个事务都会触发自己的清除、添加、更新或删除通知。 此问题可能源于在还原后快速占用新检查点的争用条件。 如果检查点在应用日志之前完成,则内存中状态将设置为新检查点的状态。 虽然在这种情况下状态是正确的,但这意味着尚未应用的日志中的事务不会发送通知。