WCF – Reliable Messages


Ao consumir um serviço WCF podemos interagir com o mesmo através de diferentes mecanismos, tais como, request-reply ou one-way (os tipos de mensagens suportados pelo WCF já foram discutidos detalhadamente neste artigo). Sabemos que, independente do tipo que você utilize, a mensagem trafega entre o cliente e o serviço através da rede, utilizando o protocolo especificado pelo binding. Com isso, uma das principais preocupações que se tem é com relação a garantia de entrega da mensagem ao seu destinatário, pois problemas com a rede podem acontecer, fazendo com que a mensagem seja interceptada ou simplesmente perdida. A finalidade deste artigo é apresentar uma técnica disponibilizada pelo WCF, para evitar que problemas como estes comprometam a consistência e execução de um serviço.

Quando desejamos enviar uma mensagem para um determinado serviço, queremos que ela chegue até o mesmo e, caso isso não aconteça, talvez precisamos reenviá-la. Mas como saber se ela chegou ou não, para tomar a decisão se devemos ou não reenviá-la? Geralmente, alguns protocolos fornecem a garantia de entrega da mensagem, como é o caso do TCP e IPC. Mesmo quando o protocolo garante nativamente a entrega da mensagem, ele somente irá assegurar a entrega ponto-a-ponto, ou seja, se houver intermediários, corremos o risco da mensagem ser perdida.

Felizmente o WCF implementa um padrão proposto pela OASIS (Organization for the Advancement of Structured Information Standards), chamado WS-ReliableMessaging (WS-RM). Essa especificação define um protoloco interoperável para transmissão de mensagens entre um único remetente para um único destinatário, garantindo que a mensagem será entregue (sem duplicação), independentemente de quantos roteadores intermediam a ligação entre eles (end-to-end). Além da garantia de entrega, este protocolo ainda fornece uma outra importante funcionalidade: você pode, opcionalmente, garantir que a entrega das mensagens aconteça na mesma ordem em que elas partiram do cliente (falaremos mais sobre ela ainda neste artigo).

Funcionamento

O funcionamento das reliable messages é um pouco complexo, mas o protocolo WS-RM em conjunto com o WCF o abstrai, facilitando o processo para aqueles que desenvolvem e administram o serviço. De qualquer forma, veremos a seguir como o processo ocorre nos bastidores do WCF (tanto no cliente quanto no serviço) para suportar essa funcionalidade.

Quando invocamos um método a partir do proxy, uma mensagem é enviada até o serviço com as informações (parâmetros, credenciais, etc.) para que o mesmo seja processado. Se temos a reliable message habilitada, não enviaremos apenas uma mensagem para o serviço (a operação que queremos invocar), mas várias outras que consistem, basicamente, na verificação para saber se a mensagem chegou ou não até o seu destino. Depois que o cliente envia a mensagem da operação, ele fica questionando o serviço para saber se a mensagem chegou até lá. O cliente espera essa notificação por um tempo (configurável) e, se o tempo exceder e a notificação não chegar, o WCF entende que ela não foi entregue, podendo agora, reenviá-la. Caso a notificação venha dentro do tempo esperado, o cliente sabe que a mensagem foi transferida com sucesso.

É importante dizer que o protocolo WS-RM foi desenhado para controlar a garantia de entrega de uma ou uma seqüência de mensagens SOAP entre dois endpoints, independentemente de como eles estão conectados, ou melhor, de quantos intermediários existam entre eles e de quais protocolos estão envolvidos. Utilizaremos a imagem abaixo para ilustrar como o processo acontece, exibindo os responsáveis que fazem isso acontecer.

Figura 1 – Funcionamento das reliable messages.

Para que isso funcione, será necessário estabelecer uma conexão com o serviço e, neste primeiro momento, um identificador será criado (CreateSequence) para correlacionar as mensagens. Quando este identificador for criado, o serviço o retorna para o cliente (CreateSequenceResponse) que irá embutí-lo em mensagens subseqüentes. Podemos perceber que ao invocar uma operação, o remetente cria um cache temporário para efetuar o rastreamento das mensagens e, a partir deste momento, o proxy é responsável por gerenciar o envio da operação e aguardar pela notificação do recebimento. Da mesma forma, o servidor também cria um cache para receber as mensagens e entregá-las para o serviço. O cache do lado do serviço terá maior utilidade quando trabalharmos com mensagens ordenadas (mais abaixo).

Ao receber a mensagem, o serviço retorna para o cliente uma mensagem contendo o elemento SequenceAcknowledgement nos headers da resposta, indicando que o serviço a recebeu. Esse elemento traz várias informações e, entre elas, temos: Identifier, AcknowledgementRange e BufferRemaining. A primeira propriedade refere-se ao identificador; já a segunda traz dois números inteiros que representam a primeira e a última mensagem processada (permitindo ao cliente remover mensagens que já estão do outro lado); finalmente, a propriedade BufferRemaining indica a quantidade disponível dentro do buffer de mensagens. A LastMessage, como o próprio nome indica, indica ao serviço que uma última mensagem será enviada e, finalmente, a mensagem TerminateSequence que encerra o processo.

Observação Importante: Quando definimos o modo de gerenciamento de instância do serviço como PerSession e habilitamos a funcionalidade de garantia de entrega, essa combinação é referida como Reliable Sessions. O protocolo WS-RM não necessita que o serviço/binding suporte sessões para funcionar, bem como o suporte à sessão não necessita do protocolo WS-RM habilitado. Ainda através da figura que vimos acima, visualizamos duas operações sendo realizadas (o que pode caracterizar uma sessão), mas é importante dizer que todos os demais passos irão ocorrer, independente da sessão estar ou não habilitada.

Configuração

A configuração das reliable messages é realizada sob o binding onde o serviço será exposto. É importante dizer que nem todas as configurações suportadas estão diretamente disponíveis através do binding, ou seja, será necessária a criação de um binding customizado para editar as configurações padrão. Os bindings expõem uma propriedade chamada ReliableSession, do tipo OptionalReliableSession que, em seu construtor, recebe uma instância da classe ReliableSessionBindingElement, contida no namespace System.ServiceModel.Channels, onde podemos efetivamente customizar o comportamento deste tipo de mensagem. A tabela abaixo exibe as propriedades expostas pela classe ReliableSessionBindingElement e que estão disponíveis para uso:

Propriedade Descrição
AcknowledgementInterval Recebe um Timespan que representa um intervalo de tempo em que o serviço aguarda para enviar a notificação de recebimento (acknowledgment). A valor padrão é de 2 segundos. Antes do serviço enviar instantaneamente a notificação de recebimento, ele aguarda este intervalo com a finalidade de agrupar o máximo de mensagens, melhorando a escalabilidade e reduzindo o tráfego de informações.
FlowControlEnabled Trata-se de um mecanismo que assegura que o remetente não envia mais mensagens quando o buffer de mensagens do serviço chega ao seu limite. Essa quantidade é informada ao remetente através da mensagem SequenceAcknowledgement, através da propriedade BufferRemaining. Essa propriedade é do tipo booleana e, quando definida como True (valor padrão), irá parar de enviar mensagens enquanto o buffer do serviço estiver cheio.
InactivityTimeout Uma propriedade que recebe um Timespan representando a duração da sessão. Se nenhuma mensagem for transmitida (incluindo mensagens de infraestrutura, como acknowledgements) durante este período, a sessão será descartada. O valor padrão é 10 minutos.
MaxPendingChannels Quando temos reliable sessions habilitadas no serviço, diferentes clientes podem estabelecer a comunicação ao mesmo tempo. Ao estabelecer a conexão, há um handshake inicial (sequences, etc.) e, após isso, o channel é colocado em uma fila com status de pendente. Esta propriedade indica quantos channels podem ser colocados neste estado e, quando omitido, o padrão é 4. Se essa fila estiver cheia qualquer tentativa de nova conexão será rejeitada.
MaxRetryCount Número inteiro que especifica a quantidade máxima de tentativas de reenvio. Enquanto o remetente não recebe a notificação de que a mensagem foi recebida pelo destinatário, o WCF reenvia a mensagem até que este limite seja atendido. Se, mesmo depois das tentativas ele não receber a notificação de recebimento, uma exceção será disparada. O valor padrão desta propriedade é 8 tentativas.
MaxTransferWindowSize Outra propriedade do tipo inteiro que define a quantidade de mensagens que o buffer pode acomodar. Do lado do cliente, esse buffer aguarda as notificações do serviço; já do lado do serviço, esse buffer acumula as mensagens para garantir que elas sejam processadas na mesma ordem que elas foram enviadas. O valor padrão para esta propriedade é 8.
Ordered Propriedade booleana que indica se as mensagens serão ou não ordenadas. O padrão é True. Maiores detalhes sobre essa técnica, serão abordados mais tarde, ainda neste artigo.

Assim como quase tudo no WCF, a configuração das reliable messages pode ser realizada tanto de forma imperativa quanto declarativa. Como já foi dito acima, a configuração é uma característica do binding, e é através dele que iremos conseguir alterar qualquer uma das propriedades que vimos na tabela acima.

Os bindings NetTcpBinding, WSHttpBinding, WSFederationHttpBinding e WSDualHttpBinding suportam as reliable messages e permitem que você habilite ou desabilite, defina um tempo de timeout por inatividade e especifique se as mensagens serão ou não ordenadas. O código abaixo ilustra as duas formas de como como podemos proceder para configurar as reliable messages no binding:

WSHttpBinding ws = new WSHttpBinding();
ws.ReliableSession.Enabled = true;
ws.ReliableSession.Ordered = true;
ws.ReliableSession.InactivityTimeout = TimeSpan.FromMinutes(5);

 

<wsHttpBinding>
    <binding name="BindingConfig">
        <reliableSession
                enabled="true"
                ordered="true"
                inactivityTimeout="00:05:00" />
    </binding>
</wsHttpBinding>

Uma outra alternativa no modo imperativo é que os bindings possuem uma versão do construtor onde já podemos definir a propriedade Enabled da ReliableSession. As outras propriedades que vimos na tabela acima não estão expostas diretamente através do binding. Apesar de muitas vezes as configurações padrões serem suficientes, em algum momento talvez seja necessário alterá-las e, para que isso seja possível, somente poderemos efetuar essa modificação através de um binding customizado, como por exemplo:

using System;
using System.ServiceModel;
using System.ServiceModel.Channels;

ReliableSessionBindingElement rsbe = new ReliableSessionBindingElement();
rsbe.AcknowledgementInterval = TimeSpan.FromSeconds(5);
rsbe.FlowControlEnabled = true;
rsbe.InactivityTimeout = TimeSpan.FromMinutes(5);
rsbe.MaxPendingChannels = 10;
rsbe.MaxRetryCount = 10;
rsbe.MaxTransferWindowSize = 10;
rsbe.Ordered = true;

CustomBinding cb =
    new CustomBinding(
        new HttpTransportBindingElement(),
        new TextMessageEncodingBindingElement(),
        rsbe);

 

<customBinding>
  <binding name="BindingConfig">
    <reliableSession
      acknowledgementInterval="00:00:05"
      flowControlEnabled="true"
      inactivityTimeout="00:05:00"
      maxPendingChannels="10"
      maxRetryCount="10"
      maxTransferWindowSize="10"
      ordered="true"/>
  </binding>
</customBinding>

Habilitar a reliable message sob um determinado binding afetará o documento que descreve o serviço (WSDL), publicando nele o suporte a esse tipo de mensagem, para que os clientes consigam configurar corretamente o proxy. Algumas dessas configurações são propagadas para o cliente, como é o caso das propriedades InactivityTimeout e AcknowledgementInterval, enquanto as outras tratam-se de configurações exclusivas ao binding.

Ao invocar o método exposto pelo proxy, aparentemente apenas uma mensagem será enviada para o respectivo serviço mas, como notamos na imagem acima, várias outras mensagens são enviadas e recebidas entre o cliente e o serviço para garantir a entrega da mensagem que o cliente quer efetivamente enviar. Essas mensagens “extras” são criadas pelo próprio runtime do WCF quando habilitamos essa funcionalidade. Caso você queira visualizar para se certificar de que isso está realmente ocorrendo, basta você habilitar o tracing no cliente e conseguirá armazenar as mensagens que estão sendo trocadas. A imagem abaixo, ilustra isso (apesar de importante, o corpo da mensagem não será mostrado aqui por questões de espaço):

Figura 2 – Resultado capturado pelo tracing.

Mensagens Ordenadas

O que vimos até o momento é garantia de entrega, fornecida pelo protocolo WS-RM, e é importante ratificar que, quando habilitamos essa funcionalidade em um serviço que é exposto como PerSession, ela é referida como reliable session. Neste momento entra em cena uma configuração adicional que permite a entrega das mensagens na mesma ordem em que elas saíram. Nem sempre podemos assumir que a primeira mensagem chegará ao seu destino antes da segunda, pois elas podem optar por caminhos diferentes.

Quando esta opção está habilitada, as mensagens são enviadas para o serviço e são armazenadas no cache (ou fila) do mesmo. Se a mensagem chega na ordem correta, ela é imediatamente encaminhada para o serviço. Caso contrário, ela aguardará na fila esperando as demais mensagens para compor a seqüencia e, finalmente, ser encaminhada para o serviço. Imagine que o cliente envie as mensagens 1, 2, 3 e 4 e o serviço recebe as mensagens 1, 2 e 4, ou seja, está faltando a mensagem 3. Neste caso, a mensagem 1 e 2 serão encaminhadas para o serviço, enquanto a mensagem 4 irá aguardar a chegada da mensagem 3 para, depois disso, ser submetida para a execução.

Para habilitar este recurso (que já é o padrão) e configurá-lo, podemos utilizar o atributo DeliveryRequirementsAttribute sob a interface que representa o contrato ou sob a classe que o implementa. Onde definir dependerá do caso pois, se aplicá-lo no contrato, em qualquer classe que o implementar, ele seguirá essas configurações; se aplicar na classe que representa o serviço, então você terá uma flexibilidade para determinarem qual dos contratos você deseja aplicar essa técnica. Esse atributo fornece a propriedade RequireOrderedDelivery que é um valor booleano que indica se está ou não habilitado, e a propriedade TargetContract, que espera um objeto do tipo Type, que determina em qual contrato essa técnica será aplicada. A segunda propriedade somente faz sentido quando o atributo é aplicado na classe do serviço. O trecho de código abaixo exibe como proceder para efetuar essa configuração:

using System;
using System.ServiceModel;

[ServiceContract]
[DeliveryRequirements(RequireOrderedDelivery = true)]
public interface IContrato
{
    [OperationContract(IsOneWay = true)]
    void EnviarInformacao(string valor);
}

Conclusão: Este artigo demonstrou importantes funcionalidades que garantem a entrega e o processamento ordenado das mensagens e, como vimos, tudo isso é possível graças ao protocolo WS-ReliableMessaging. Com a implementação do protocolo WS-RM no WCF, a Microsoft conseguiu abstrair todo o trabalho complexo, permitindo aos desenvolvedores, com apenas algumas configurações simples, fazer com que ela entre em funcionamento, sem a necessidade de conhecer profundamente os detalhes que são necessários para que isso aconteça.

Deixe uma resposta