Notificações do Reliable Services

As notificações permitem que os clientes controlem as alterações que estão a ser efetuadas a um objeto no qual estão interessados. Dois tipos de objetos suportam notificações: Reliable State Manager e Reliable Dictionary.

Os motivos comuns para a utilização de notificações são:

  • Criar vistas materializadas, como índices secundários ou vistas filtradas agregadas do estado da réplica. Um exemplo é um índice ordenado de todas as chaves no Reliable Dictionary.
  • Enviar dados de monitorização, como o número de utilizadores adicionados na última hora.

As notificações são acionados como parte da aplicação de operações. Numa réplica primária, as operações são aplicadas após a confirmação do quórum como parte de transaction.CommitAsync() ou this.StateManager.GetOrAddAsync(). Nas réplicas secundárias, as operações são aplicadas no processamento de dados da fila de replicação. Por isso, as notificações devem ser processadas o mais rapidamente possível e os eventos síncronos não devem incluir operações dispendiosas. Caso contrário, pode afetar negativamente o tempo de processamento de transações, bem como as compilações de réplicas.

Notificações do Reliable State Manager

O Reliable State Manager fornece notificações para os seguintes eventos:

  • Transação
    • Consolidação
  • Gestor de estado
    • Reconstruir
    • Adição de um estado fiável
    • Remoção de um estado fiável

O Reliable State Manager controla as transações de voo atuais. A única alteração no estado de transação que faz com que uma notificação seja acionada é uma transação a ser consolidada.

O Reliable State Manager mantém uma coleção de estados fiáveis, como o Reliable Dictionary e o Reliable Queue. O Reliable State Manager aciona notificações quando esta coleção é alterada: é adicionado ou removido um estado fiável ou toda a coleção é reconstruída. A coleção Reliable State Manager é reconstruída em três casos:

  • Recuperação: quando uma réplica é iniciada, recupera o estado anterior do disco. No final da recuperação, utiliza NotifyStateManagerChangedEventArgs para acionar um evento que contém o conjunto de estados fiáveis recuperados.
  • Cópia completa: antes de uma réplica poder aderir ao conjunto de configuração, tem de ser criada. Por vezes, isto requer uma cópia completa do estado do Reliable State Manager da réplica primária para ser aplicada à réplica secundária inativa. O Reliable State Manager na réplica secundária utiliza NotifyStateManagerChangedEventArgs para acionar um evento que contém o conjunto de estados fiáveis que adquiriu a partir da réplica primária.
  • Restauro: em cenários de recuperação após desastre, o estado da réplica pode ser restaurado a partir de uma cópia de segurança através de RestoreAsync. Nestes casos, o Reliable State Manager na réplica primária utiliza NotifyStateManagerChangedEventArgs para acionar um evento que contém o conjunto de estados fiáveis que restaurou a partir da cópia de segurança.

Para se registar para notificações de transações e/ou notificações do gestor de estado, tem de se registar nos eventos TransactionChanged ou StateManagerChanged no Reliable State Manager. Um local comum para se registar com estes processadores de eventos é o construtor do seu serviço com estado. Quando se registar no construtor, não perderá nenhuma notificação causada por uma alteração durante a duração de IReliableStateManager.

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

O processador de eventos TransactionChanged utiliza NotifyTransactionChangedEventArgs para fornecer detalhes sobre o evento. Contém a propriedade de ação (por exemplo, NotifyTransactionChangedAction.Commit) que especifica o tipo de alteração. Também contém a propriedade de transação que fornece uma referência à transação que foi alterada.

Nota

Atualmente, os eventos TransactionChanged são gerados apenas se a transação for consolidada. Em seguida, a ação é igual a NotifyTransactionChangedAction.Commit. Mas, no futuro, poderão ser gerados eventos para outros tipos de alterações de estado de transação. Recomendamos que verifique a ação e processe o evento apenas se for um evento esperado.

Segue-se um exemplo de Processador de eventos 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);
    }
}

O processador de eventos StateManagerChanged utiliza NotifyStateManagerChangedEventArgs para fornecer detalhes sobre o evento. NotifyStateManagerChangedEventArgs tem duas subclasses: NotifyStateManagerRebuildEventArgs e NotifyStateManagerSingleEntityChangedEventArgs. Utilize a propriedade de ação em NotifyStateManagerChangedEventArgs para lançar NotifyStateManagerChangedEventArgs para a subclasse correta:

  • NotifyStateManagerChangedAction.Rebuild: NotifyStateManagerRebuildEventArgs
  • NotifyStateManagerChangedAction.Add e NotifyStateManagerChangedAction.Remove: NotifyStateManagerSingleEntityChangedEventArgs

Segue-se um exemplo de StateManagerChanged notification handler.

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

        return;
    }

    this.ProcessStateManagerSingleEntityNotification(e);
}

Notificações do Dicionário Fiável

O Reliable Dictionary fornece notificações para os seguintes eventos:

  • Recompilação: chamada quando ReliableDictionary recuperou o respetivo estado a partir de um estado ou cópia de segurança local recuperado ou copiado.
  • Claro: chamado quando o estado de ReliableDictionary foi limpo através do método ClearAsync .
  • Adicionar: chamado quando um item foi adicionado ao ReliableDictionary.
  • Atualização: chamada quando um item em IReliableDictionary foi atualizado.
  • Remover: chamado quando um item em IReliableDictionary foi eliminado.

Para obter notificações do Dicionário Fiável, tem de se registar no processador de eventos DictionaryChanged em IReliableDictionary. Um local comum para se registar com estes processadores de eventos é na notificação de adição ReliableStateManager.StateManagerChanged . Registar quando IReliableDictionary é adicionado a IReliableStateManager garante que não perderá nenhuma notificação.

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;
        }
    }
}

Nota

ProcessStateManagerSingleEntityNotification é o método de exemplo que o exemplo de OnStateManagerChangedHandler anterior chama.

O código anterior define a interface IReliableNotificationAsyncCallback , juntamente com DictionaryChanged. Uma vez que NotifyDictionaryRebuildEventArgs contém uma interface IAsyncEnumerable , que tem de ser enumerada de forma assíncrona, as notificações de reconstrução são acionadas através de RebuildNotificationAsyncCallback em vez de 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);
    }
}

Nota

No código anterior, como parte do processamento da notificação de reconstrução, primeiro o estado agregado mantido é limpo. Uma vez que a coleção fiável está a ser reconstruída com um novo estado, todas as notificações anteriores são irrelevantes.

O processador de eventos DictionaryChanged utiliza NotifyDictionaryChangedEventArgs para fornecer detalhes sobre o evento. NotifyDictionaryChangedEventArgs tem cinco subclasses. Utilize a propriedade de ação em NotifyDictionaryChangedEventArgs para lançar NotifyDictionaryChangedEventArgs para a subclasse correta:

  • NotifyDictionaryChangedAction.Rebuild: NotifyDictionaryRebuildEventArgs
  • NotifyDictionaryChangedAction.Clear: NotifyDictionaryClearEventArgs
  • NotifyDictionaryChangedAction.Add: NotifyDictionaryItemAddedEventArgs
  • NotifyDictionaryChangedAction.Update: NotifyDictionaryItemUpdatedEventArgs
  • NotifyDictionaryChangedAction.Remove: NotifyDictionaryItemRemovedEventArgs
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;
    }
}

Recomendações

  • Conclua os eventos de notificação o mais rápido possível.
  • Não execute operações dispendiosas (por exemplo, operações de E/S) como parte de eventos síncronos.
  • Verifique o tipo de ação antes de processar o evento. Poderão ser adicionados novos tipos de ação no futuro.

Seguem-se alguns aspetos a ter em conta:

  • As notificações são acionados como parte da execução de uma operação. Por exemplo, uma notificação de restauro é acionada como o último passo de uma operação de restauro. Um restauro não será concluído até que o evento de notificação seja processado.
  • Uma vez que as notificações são acionados como parte das operações de aplicação, os clientes só veem notificações para operações consolidadas localmente. E como as operações são garantidas apenas para serem consolidadas localmente (por outras palavras, registadas), podem ou não ser anuladas no futuro.
  • No caminho de refazer, é acionada uma única notificação para cada operação aplicada. Isto significa que, se a transação T1 incluir Create(X), Delete(X) e Create(X), receberá uma notificação para a criação de X, uma para a eliminação e outra para a criação novamente, por essa ordem.
  • Para transações que contêm várias operações, as operações são aplicadas pela ordem pela qual foram recebidas na réplica primária do utilizador.
  • Como parte do processamento de falsos progressos, algumas operações podem ser anuladas em réplicas secundárias. São levantadas notificações para tais operações de anulação, ao reverter o estado da réplica para um ponto estável. Uma diferença importante de anular notificações é que os eventos que têm chaves duplicadas são agregados. Por exemplo, se a transação T1 estiver a ser anulada, verá uma única notificação para Eliminar(X).

Passos seguintes