Este artigo foi traduzido por máquina.

Windows Azure Insider

Barramento de Serviço do Windows Azure: Padrões de mensagens usando sessões

Bruno Terkaly
Ricardo Villalobos

Baixar o código de exemplo

Em um de nossos artigos anteriores, discutimos a importância do uso de padrões de mensagens na nuvem para dissociar soluções e arquiteturas de software fácil-a escala de promover. (Consulte "Comparando Windows Azure filas e serviço de ônibus filas" em msdn.microsoft.com/magazine/jj159884.) Enfileiramento de mensagens é um desses padrões de mensagens, e a plataforma Windows Azure oferece duas opções principais para implementar essa abordagem: Serviços de armazenamento de fila e filas de ônibus de serviço, os quais abrangem cenários onde vários consumidores competem para receber e processar cada uma das mensagens em uma fila. Este é o modelo canônico para suportar cargas de trabalho variáveis na nuvem, onde receptores podem ser dinamicamente adicionados ou removidos com base no tamanho da fila, oferecendo um mecanismo de failover/balanceamento de carga para o back-end (ver Figura 1).


Figura 1 padrão enfileiramento de mensagens: Cada mensagem é consumida por um único receptor

Mesmo que o padrão de mensagens enfileiramento é uma ótima solução para uma dissociação simples, há situações onde cada receptor requer sua própria cópia da mensagem, com a opção de descartar algumas mensagens com base em regras específicas. Um bom exemplo deste tipo de cenário é mostrado em Figura 2, que ilustra um desafio comum que o varejo as empresas enfrentam ao enviar informações para vários ramos, como o mais recente catálogo de produtos ou uma lista de preços atualizada.


Figura 2 Publicador/Assinante de mensagens padrão: Cada mensagem pode ser consumida mais de uma vez

Para estas situações, o padrão de Publicador/Assinante é um ajuste melhor, onde os receptores simplesmente manifestar interesse em uma ou mais categorias de mensagem, conectando a uma assinatura independente que contém uma cópia do fluxo de mensagem. O barramento de serviços Windows Azure implementa o padrão de mensagens Publicador/Assinante através de tópicos e assinaturas, que aumenta muito a capacidade de controlar como as mensagens são distribuídas, com base em filtros e regras independentes. Neste artigo, explicaremos como aplicar esses recursos de barramento de serviços do Windows Azure, usando um simples cenário real, supondo que os seguintes requisitos:

  1. Produtos devem ser recebidos na ordem, com base na página de catálogo.
  2. Algumas das lojas não carregam a categorias específicas de catálogo e produtos nestas categorias devem ser filtrados para cada loja.
  3. Novas informações de catálogo não devem ser aplicadas no sistema de armazenamento até que todas as mensagens chegaram.

Todas as amostras de código para este artigo foram criadas com o Visual Studio 2012, usando c# como linguagem de programação. Você também vai precisar da versão 1.8 do SDK do Windows Azure para desenvolvedores .NET e acesso a uma assinatura do Windows Azure.

Criação de um plano de mensagens para o projeto

Antes de escrever qualquer código, você precisará definir as diferentes entidades (tópicos e inscrições) que se tornarão parte do fluxo de trabalho mensagens. Isso pode ser feito acessando o Portal do Windows Azure no manage.windowsazure.com. Fazer login com suas credenciais e siga estes passos:

  1. Clique no ícone criar nova na parte inferior esquerda do Portal de gerenciamento.
  2. Clique no ícone do APP de serviços e, em seguida, no tópico de barramento de serviço e finalmente em criar personalizado (consulte Figura 3).
  3. Na primeira tela de diálogo, digite o nome do tema e selecione o apropriado região e Windows Azure ID de assinatura. Se este é seu primeiro namespace na região selecionada, o assistente irá sugerir uma fila do namespace: [seu nome entidade]-ns. Você pode alterar esse valor.
  4. Clique em próxima marca (seta) para inserir as propriedades restantes. Você pode manter os valores padrão. Clique sobre a marca de verificação para criar o tópico.
  5. Clique no ícone na barra de navegação à esquerda para obter uma lista de namespaces do barramento de serviço. Observe que você não pode ver o namespace listado imediatamente. Demora alguns segundos para criar o espaço para nome e atualizar a interface do portal.
  6. Selecione o tópico que você criou na lista e clique na chave de acesso, que pode ser encontrado na parte inferior da tela. Grave a seqüência de caracteres de conexão completo para uso posterior.
  7. No topo da tela do Portal do Windows Azure, clique em assinaturas e, em seguida, em criar A nova assinatura. Na caixa de diálogo pop-up, digite um nome (no nosso exemplo, nós usamos o "Store1Sub") e clique na seta para continuar.
  8. Na tela seguinte, mantenha os valores padrão, mas certifique-se de marcar a opção de sessões de Enable. Clique sobre a marca de verificação para criar a assinatura. Sessões serão usado para recuperar mensagens em ordem seqüencial por assinantes.
  9. Repita as etapas 7 e 8 para cada uma das três lojas.


Figura 3 Criando um novo tópico de barramento de serviço usando o Windows Azure Portal

Uma vez que foram criadas os tópicos e assinaturas, você também pode acessá-los diretamente no Visual Studio. Para fazer isso, abra o Server Explorer (View | Gerenciador de servidores) e expanda o nó de barramento de serviços do Windows Azure (ver Figura 4). Botão direito do mouse sobre o nó de barramento de serviços do Windows Azure e selecione Adicionar nova conexão. Digite o nome do Namespace, o nome do emissor (geralmente "proprietário") e chave de acesso do emissor você gravou quando o namespace Windows Azure foi criado no portal.


Figura 4 Criando um tópico de barramento de serviço e assinaturas usando ferramentas do Visual Studio

Tenha em mente que é possível criar e gerenciar essas entidades usando classes no namespace Microsoft.ServiceBus.Messaging, incluindo TopicClient e SubscriptionClient, que são usados neste artigo.

Uma vez que a estrutura básica do fluxo de trabalho de mensagens foi criada, nós vai simular tráfego usando dois aplicativos de console criados em Visual Studio, conforme mostrado no Figura 5. O primeiro aplicativo de console, MSDNSender, irá enviar o catálogo de produtos. O segundo, MSDN­receptor, receberá as informações em cada uma das lojas. Nós vamos analisar o código nas seções a seguir. No padrão de Pub/Sub, o MSDNSender é o editor e o MSDNReceiver é o Assinante.


Figura 5 solução do Visual Studio para simular o cenário do catálogo de produtos

Enviando o catálogo de produtos da sede

Como você pode ver em Figura 2, quartel-general (o editor) envia mensagens para um tópico. Essa lógica é representada pelo código no arquivo principal, Program. cs, uma parte do projeto MSDNSender. CS encapsula a lógica e o código para enviar uma lista de produtos como mensagens individuais para o tópico. Vamos dar uma olhada as seções diferentes, começando com o método Main. Observe que primeiro vamos criar um cliente para o tema, como segue:

// Create a topicClient using the
// Service Bus credentials
TopicClient topicClient =
  TopicClient.CreateFromConnectionString(
  serviceBusConnectionString, topicName);

Depois de um topicClient é criado, o editor pode enviar mensagens usá-lo. A lista de produtos a ser enviado é armazenada em um arquivo XML chamado ProductsCatalog.xml, que contém uma lista de 10 entidades de produto que será transformada em uma matriz de objetos. Os produtos então irão obter mapeados para as classes de catálogo e produto armazenadas no arquivo de exemplo:

// Deserialize XML file with Products, and store them in an object array
Catalog catalog = null;
string path = "ProductsCatalog.xml";
XmlSerializer serializer = new XmlSerializer(typeof(Catalog));
StreamReader reader = new StreamReader(path);
catalog = (Catalog) serializer.Deserialize(reader);
reader.Close();

Cada produto na matriz catálogo apresenta a estrutura mostrada na Figura 6.

Representação de classe Figura 6 produtos no catálogo

public class Product
  {
    [System.Xml.Serialization.XmlElement("ProductId")]
    public string ProductId { get; set; }
    [System.Xml.Serialization.XmlElement("ProductName")]
    public string ProductName { get; set; }
    [System.Xml.Serialization.XmlElement("Category")]
    public string Category { get; set; }
    [System.Xml.Serialization.XmlElement("CatalogPage")]
    public int CatalogPage { get; set; }
    [System.Xml.Serialization.XmlElement("MSRP")]
    public double MSRP { get; set; }
    [System.Xml.Serialization.XmlElement("Store")]
    public string Store { get; set; }
  }

Dentro do loop de matriz, uma chamada para o método CreateMessage extrai diferentes propriedades dos objetos de produto e atribui-los para a mensagem ser enviada. Duas propriedades requerem atenção extra:

if (isLastProductInArray)
  message.Properties.Add("IsLastMessageInSession", "true");
message.SessionId = catalogName;

Sessões são extremamente importantes, pois permitem que o receptor determinar se todas as mensagens que pertencem a um grupo específico de lógico chegaram. Nesse caso, definindo a propriedade de mensagem de SessionId, nós estiver especificando que o receptor não deve usar as informações de catálogo até depois que chegaram todas as mensagens com o mesmo valor de catalogName. Além disso, para o último produto da matriz, estamos adicionando uma nova propriedade: IsLastMessageInSession, o que permitirá que os receptores determinar se a última mensagem na sessão chegou, e o catálogo podem ser totalmente processada. Figura 7 mostra MSDNSender executando.


Figura 7 execução do projeto MSDNSender

Receber o catálogo de produtos usando assinaturas nas lojas

Agora que o catálogo e os produtos foram enviados para o tópico e copiados para diferentes assinaturas, vamos voltar nossa atenção para o projeto de MSDNReceiver, onde as mensagens são recebidas e processadas. Observe que no método Main Program. cs, o código cria um cliente para a inscrição com base na informação fornecida pelo usuário através de um Read­linha de comando. Os usuários deverão digitar seu número de loja, que reflete as mensagens que deseja receber. Em suma, cada loja do ramo está preocupada apenas com mensagens que se aplicam a essa loja:

Console.WriteLine("Enter Store Number");
  string storeNumber = Console.ReadLine();
  Console.WriteLine("Selecting Subscription for Store...");
  // Create a Subscription Client to the Topic
  SubscriptionClient subscriptionClient =
    SubscriptionClient.CreateFromConnectionString(
    serviceBusConnectionString, topicName,
    "Store" + storeNumber.Trim() + "Sub",
    ReceiveMode.PeekLock);

Porque nós estamos recebendo mensagens de assinaturas com sessões (conforme explicado na seção anterior), devemos solicitar o próximo um usando a seguinte linha de código:

MessageSession sessionReceiver =
  subscriptionClient.AcceptMessageSession(TimeSpan.FromSeconds(5));

Basicamente, isso significa que o cliente irá verificar se há quaisquer mensagens de ser processado na subscrição — aqueles cuja Propriedade SessionId não é nula — e se não há tais mensagens são encontradas dentro de um período de cinco segundos, a solicitação será tempo ­fora, encerrar o aplicativo receptor. Por outro lado, se uma sessão for encontrada, será chamado o método ReceivingSessionMessages. Antes nós saltar para este pedaço de código, vamos discutir o conceito de estado de sessão, o que permite que o desenvolvedor armazenar informações que podem ser usadas enquanto são recebidas mensagens que pertencem à mesma transação. Neste caso, estamos usando estado de sessão para "lembrar" a última página do catálogo que foi recebida, bem como as mensagens — produtos — que chegou fora de ordem.

Com isso, aqui é o fluxo de trabalho no código:

  1. A mensagem atual é recebida no ReceiveSession­método de mensagens (ver Figura 8), que se baseia no método ProcessMessage (Figura 9) para processá-lo.
  2. Dentro do método ProcessMessage, se a mensagem for fora de seqüência, ele automaticamente é adiado e seu ID armazenado no estado de sessão. Caso contrário, tem marcado como "completo" e removido da assinatura. Além disso, o próximo esperada seqüência — página do catálogo — é armazenado na sessão.
  3. Após a mensagem recebida atual foi processada, o código subseqüente em ReceiveSessionMessages verifica IDs de mensagens diferidos na sessão e tenta processá-los novamente com base na página de catálogo mais recente.
  4. Uma vez que todas as mensagens foram recebidas para a sessão, o receptor está fechado.

Figura 8 o método de ReceivedSessionMessages no código

static void ReceiveSessionMessages(MessageSession receiver)
  {
    // Read messages from subscription until subscription is empty
    Console.WriteLine("Reading messages from subscription {0}", 
      receiver.Path);
    Console.WriteLine("Receiver Type:" + receiver.GetType().Name);
    Console.WriteLine("Receiver.SessionId = " + receiver.SessionId);
    SequenceState sessionState = GetState(receiver);
    BrokeredMessage receivedMessage;
    while ((receivedMessage = receiver.Receive()) != null)
    {
      string sessionId = receiver.SessionId;
      ProcessMessage(receivedMessage, ref sessionState, receiver);
      while (sessionState.GetNextOutOfSequenceMessage() != -1)
      {
        // Call back deferred messages
        Console.WriteLine("Calling back for deferred message: Category {0},
          Message sequence {1}", receiver.SessionId,
            sessionState.GetNextSequenceId());
        receivedMessage = receiver.Receive(
          sessionState.GetNextOutOfSequenceMessage());
        ProcessMessage(receivedMessage, ref sessionState, receiver);
      }
      if (receivedMessage.Properties.ContainsKey(
        "IsLastMessageInSession"))
        break;
    }
    SetState(receiver, null);
    receiver.Close();
  }

Figura 9 o método ProcessMessage no código

static void ProcessMessage(BrokeredMessage message, ref SequenceState sessionState,
  MessageSession session = null)
  {
    if (session != null)
    {
      int messageId = Convert.ToInt32(message.Properties["CatalogPage"]);
      if (sessionState.GetNextSequenceId() == messageId)
      {
        OutputMessageInfo("RECV: ", message, "State: " + "RECEIVED");
        sessionState.SetNextSequenceId(messageId + 1);
        message.Complete();
        SetState(session, sessionState);
      }
      else
      {
        Console.WriteLine("Deferring message: Category {0}, Catalog Page {1}",
          session.SessionId, messageId);
        sessionState.AddOutOfSequenceMessage(messageId, 
          message.SequenceNumber);
        message.Defer();
        SetState(session, sessionState);
      }
    }
    Thread.Sleep(receiverDelay);
  }

Tenha em mente que para este projeto, adiada mensagem IDs são armazenados no estado de sessão e poderiam ser potencialmente perdidos. Em um ambiente de produção, recomendamos o uso de algum tipo de armazenamento persistente (Windows Azure tabelas é uma opção) para essa finalidade. Observe que, se a mensagem contém a propriedade de IsLastMessage­SessionInSession (definido durante o processo de envio), o loop de sessão é encerrado. O console de saída para o projeto de MSDNReceiver pode ser visto em Figura 10.


Figura 10 execução do projeto MSDNReceiver

Assinaturas de barramento de serviços do Windows Azure dar-lhe a capacidade de criar regras específicas que Filtragem as mensagens antes que eles são consumidos. Nesse caso, seria relativamente fácil criar uma regra que segrega os produtos por categoria ou por número de loja (que nós ignorada neste projeto). Regras podem ser criadas programaticamente, diretamente no portal do Windows Azure, ou através de ferramentas do Visual Studio.

Conclusão

O barramento de serviços Windows Azure oferece uma implementação incrivelmente robusta e flexível do padrão publicação/assinatura. Muitos cenários diferentes podem ser tratados com o uso de tópicos e assinaturas. A capacidade de suportar vários remetentes transmitindo mensagens para vários receptores, combinadas com a capacidade de mensagens logicamente grupo e classificação, que abre um mundo de possibilidades para o desenvolvedor moderno. Além disso, ser capaz de aproveitar uma sessão persistente para controlar estado torna simples logicamente agrupar mensagens e controlar sua seqüência. Em um mundo onde ambientes distribuídos são a norma, compreender como usar padrões de mensagens e as ferramentas ao redor deles é crucial para arquitetos de software hoje trabalhando na nuvem.

Bruno Terkaly é desenvolvedor e divulgador da Microsoft. Seu profundo conhecimento é fruto de anos de experiência no campo, escrevendo código com uma grande quantidade de plataformas, linguagens, estruturas, SDKs, bibliotecas e APIs. Ele passa seu tempo escrevendo código, escrevendo blogs e fazendo apresentações ao vivo sobre como criar aplicativos baseados na nuvem, especificamente usando a plataforma Windows Azure.

Ricardo Villalobos is a seasoned software architect with more than 15 years of experience designing and creating applications for companies in the supply chain management industry. Detentor de diferentes certificações técnicas, e mestre em administração pela Universidade de Dallas, ele trabalha como arquiteto de nuvem no grupo de incubação do Windows Azure CSV para a Microsoft.

Agradecemos ao seguinte especialista técnico pela revisão deste artigo: Abhishek Lal