Manter afinidade entre um grupo de assinaturas e o servidor de caixa de correio no Exchange

Saiba mais sobre como manter a afinidade entre um grupo de assinaturas e o servidor da caixa de correio.

Afinidade é a associação de uma sequência de mensagens de solicitação e resposta com um servidor de caixa de correio específico. Para a maioria das funcionalidades no Exchange, a afinidade é tratada pelo servidor. No entanto, as notificações são uma exceção. O cliente é responsável por manter a afinidade com o servidor mailbox para assinaturas de notificação. Essa afinidade permite que o balanceador de carga e os servidores de Acesso ao Cliente entre o cliente e o servidor roteiem assinaturas de notificação e solicitações relacionadas ao servidor mailbox que mantém a assinatura. Sem afinidade, a solicitação pode ser roteada para um servidor de caixa de correio diferente que não inclua as assinaturas do cliente, o que pode fazer com que um erro ErrorSubscriptionNotFound seja retornado.

Como a afinidade é mantida?

A afinidade no Exchange é baseada em cookie. O cliente dispara a criação do cookie incluindo cabeçalhos específicos na solicitação de assinatura e, em seguida, a resposta da assinatura contém o cookie. Em seguida, o cliente envia esse cookie em solicitações subsequentes para garantir que a solicitação seja roteada para o servidor de caixa de correio direito.

Mais especificamente, a afinidade no Exchange é tratada pelo seguinte:

  • X-AnchorMailbox – um cabeçalho HTTP incluído na solicitação de assinatura inicial. Ele identifica a primeira caixa de correio em um grupo de caixas de correio que compartilham afinidade com o mesmo servidor de caixa de correio.

  • X-PreferServerAffinity — Um cabeçalho HTTP incluído na solicitação de assinatura inicial com o cabeçalho X-AnchorMailbox e é definido como true para indicar que o cliente está solicitando que a afinidade seja mantida com o servidor da Caixa de Correio.

  • X-BackEndOverrideCookie – Um cookie que está incluído na resposta inicial da assinatura e contém um cookie que o balanceador de carga e o servidor do Client Access usam para rotear solicitações subsequentes para o mesmo servidor da caixa de correio.

Como fazer manter a afinidade usando a API Gerenciada do EWS ou o EWS?

Você pode usar as mesmas etapas para manter a afinidade com várias assinaturas de caixa de correio e seus servidores de caixa de correio, independentemente de você estar usando notificações por streaming, pull ou push, e independentemente de você estar mirando um servidor local do Exchange ou Exchange Online.

  1. Para cada caixa de correio, chame Autodiscover e obtenha as configurações de usuário GroupingInformation e ExternalEwsUrl. Para o SOAP Autodiscover, use o elemento Configuração e, para Autodiscover POX, use o elemento GroupingInformation .

  2. Usando as configurações GroupingInformation e ExternalEwsUrl das respostas autodiscover, coloque as caixas de correio com o mesmo valor concatenado ExternalEwsUrl e GroupingInformation no mesmo grupo. Se algum grupo tiver mais de 200 caixas de correio, quebre os grupos ainda mais para que cada grupo não tenha mais de 200 caixas de correio.

  3. Crie e use um objeto ExchangeService para o restante do procedimento. Quando você usa o mesmo objeto ExchangeService , cookies e cabeçalhos (quando são definidos) são mantidos automaticamente. Observe que, se você não pretende agrupar assinaturas de streaming em uma única conexão, será livre para criar um objeto ExchangeService diferente para cada usuário representado.

  4. Envie uma solicitação de assinatura para o usuário cujo nome de usuário aparece primeiro quando todos os usuários do grupo são classificados em ordem alfabética (vamos nos referir a esse usuário como o usuário da caixa de correio âncora). Faça o seguinte:

  • Inclua o cabeçalho X-AnchorMailbox com um valor definido como o endereço SMTP do usuário da caixa de correio âncora.

  • Inclua o cabeçalho X-PreferServerAffinity com um valor definido como true.

  • Use a função ApplicationImpersonation (o tipo ExchangeImpersonation ).

  1. Na resposta da assinatura, obtenha o valor X-BackEndOverrideCookie. Inclua esse valor em cada uma das solicitações de assinatura subsequentes para usuários nesse grupo.

  2. Para cada usuário adicional no grupo, envie uma solicitação de assinatura e faça o seguinte:

  • Inclua o cabeçalho X-AnchorMailbox com um valor definido como o endereço SMTP do usuário da caixa de correio âncora do grupo.

  • Inclua o cabeçalho X-PreferServerAffinity com um valor definido como true.

  • Inclua o X-BackEndOverrideCookie que foi retornado na resposta de assinatura do usuário da caixa de correio âncora.

  • Use a função ApplicationImpersonation (o tipo ExchangeImpersonation ).

    Observe que o servidor usa os valores X-PreferServerAffinity e X-BackendOverrideCookie juntos para executar o roteamento para o servidor de caixa de correio. O cabeçalho X-AnchorMailbox também é necessário, mas é ignorado pelo servidor se os outros dois valores forem válidos. Se X-AnchorMailbox e X-PreferServerAffinity estiverem em uma solicitação e X-BackendOverrideCookie não estiver incluído, o valor X-AnchorMailbox será usado para rotear as solicitações.

    Como os valores X-PreferServerAffinity e X-BackendOverrideCookie executam o roteamento, se a caixa de correio âncora se mover para outro grupo ou servidor, a lógica não será alterada porque o X-BackendOverrideCookie encaminhará a solicitação para o servidor correto para o grupo.

  1. Envie uma única solicitação GetStreamingEvents ou GetEvents para o grupo e faça o seguinte:
  • Inclua os valores SubscriptionId retornados em cada uma das respostas de assinatura individuais para caixas de correio no grupo.

  • Se houver mais de 200 assinaturas para o grupo, crie várias solicitações. O número máximo de valores SubscriptionId a serem incluídos em uma solicitação é 200.

  • Se você precisar de mais conexões do que está disponível para a caixa de correio de destino, use a conta de serviço para representar a caixa de correio âncora do grupo; caso contrário, não use representação. Idealmente, você deseja representar uma caixa de correio exclusiva por solicitação GetStreamingEvents ou GetEvents para que você nunca encontre limites de limitação.

  • Use ApplicationImpersonation se precisar de mais conexões do que estão disponíveis para a caixa de correio de destino; caso contrário, não use ApplicationImpersonation.

  • Inclua o cabeçalho X-PreferServerAffinity e defina-o como true. Esse valor será incluído automaticamente se você estiver usando o objeto ExchangeService que você criou na etapa 2.

  • Inclua o X-BackEndOverrideCookie para o grupo (o X-BackEndOverrideCookie que foi retornado na resposta de assinatura do usuário da caixa de correio âncora). Esse valor será incluído automaticamente se você estiver usando o objeto ExchangeService que você criou na etapa 2.

  1. Passe os eventos retornados para um thread separado para processamento.

Quais valores de limitação preciso levar em consideração?

Ao planejar sua implementação de notificação, você deseja levar em consideração dois valores: o número de conexões e o número de assinaturas. A tabela a seguir lista os valores padrão para cada configuração de limitação e como as configurações são usadas. Para cada valor, o orçamento é alocado para a caixa de correio de destino. Por esse motivo, usar a representação para obter conexões adicionais é uma etapa necessária em muitos cenários.

Tabela 1. Valores de limitação padrão

Área de consideração Configuração de limitação Valor padrão Descrição
Conexões de streaming
Limite de conexão suspenso padrão
10 para Exchange Online
3 para Exchange 2013
O número máximo de conexões de streaming simultâneas que uma conta pode ter aberta no servidor ao mesmo tempo. Para trabalhar dentro desse limite, use uma conta de serviço com a função ApplicationImpersonation atribuída para as caixas de correio de destino e represente o primeiro usuário em cada grupo de ID de assinatura ao obter eventos transmitidos.
Conexões pull ou push
EWSMaxConcurrency
27
O número máximo de conexões de pull ou push simultâneas (solicitações recebidas, mas ainda não respondidas) que uma conta pode ter aberto no servidor ao mesmo tempo.
Assinaturas
EWSMaxSubscriptions
20 para Exchange Online
5000 para Exchange 2013
O número máximo de assinaturas não inexpidas que uma conta pode ter ao mesmo tempo. Esse valor é decremente decremente quando a assinatura é criada no servidor.

O exemplo a seguir mostra como os orçamentos são tratados entre qualquer caixa de correio de destino e a conta de serviço que tem a função ApplicationImpersonation atribuída para as caixas de correio de destino.

  • ServiceAccount1 (sa1) representa muitos usuários (m1, m2, m3 e assim por diante) e cria assinaturas para cada caixa de correio. Observe que quando as assinaturas são criadas, o proprietário da assinatura é sa1, portanto, quando sa1 abre uma conexão com as assinaturas, o EWS impõe que as assinaturas sejam de propriedade da sa1.

  • Sa1 pode abrir a conexão das seguintes maneiras:

  1. Sem representação, portanto, a conexão é cobrada contra sa1.

  2. Ao representar qualquer um dos usuários – m1, por exemplo – para que a conexão seja cobrada em relação a uma cópia do orçamento do m1. (O próprio M1 pode abrir dez conexões usando Exchange Online e todas as contas de serviço que representam m1 podem abrir dez conexões usando o orçamento copiado.)

  • Se o limite de conexão for atingido, as seguintes soluções alternativas estarão disponíveis:

    • Se a opção 1 for usada, o administrador poderá criar várias contas de serviço para representar usuários adicionais.

    • Se a opção 2 for usada, o código poderá representar outro usuário – m2, por exemplo.

Exemplo: manter a afinidade entre um grupo de assinaturas e o servidor da caixa de correio

Ok, vamos vê-lo em ação. O exemplo de código a seguir mostra como agrupar usuários e usar os cabeçalhos X-AnchorMailbox e X-PreferServerAffinity e o cookie X-BackendOverrideCookie para manter a afinidade com o servidor da caixa de correio. Como os cabeçalhos e o cookie são de importância primária na história de afinidade, este exemplo se concentra nas solicitações e respostas XML do EWS. Para usar a API Gerenciada do EWS para criar o corpo das solicitações e respostas da assinatura, consulte Notificações de fluxo sobre eventos de caixa de correio usando notificações EWS no Exchange e Pull sobre eventos de caixa de correio usando EWS no Exchange. Esta seção inclui etapas adicionais específicas para manter a afinidade e adicionar os cabeçalhos às suas solicitações.

Este exemplo tem quatro usuários: alfred@contoso.com, alisa@contoso.com, ronnie@contoso.come sadie@contoso.com. A figura a seguir mostra as configurações GroupingInformation e ExternalEwsUrl Autodiscover para os usuários .

Figura 1. Configurações de descoberta automática usadas para agrupar caixas de correio

A table that shows the GroupingInformation and ExternalEwsUrl values for each of the users.

Usando as configurações das respostas autodiscover, as caixas de correio são agrupadas pelo valor concatenado das configurações GroupingInformation e ExternalEwsUrl. Neste exemplo, Alfred e Sadie têm os mesmos valores, então eles estão em um grupo, e Alisa e Ronnie compartilham os mesmos valores, então eles estão em outro grupo.

Figura 2. Criando grupos de caixas de correio

A table that shows how mailbox groups are created using Autodiscover settings.

Para a finalidade deste exemplo, nos concentraremos no Grupo A. Usaríamos as mesmas etapas para o grupo B, mas usaríamos um valor X-AnchorMailbox diferente para esse grupo.

Usando ApplicationImpersonation, crie a solicitação de assinatura para a caixa de correio âncora (alfred@contoso.com), com o cabeçalho X-AnchorMailbox definido para o endereço de email e um valor de cabeçalho X-PreferServerAffinity de true. Definir esses dois valores de cabeçalho disparará o servidor para criar um X-BackEndOverrideCookie para a resposta.

Se você estiver usando a API Gerenciada do EWS, use o método HttpHeadersAdd para adicionar os dois cabeçalhos à sua solicitação de assinatura, conforme mostrado.

service.HttpHeaders.Add("X-AnchorMailbox", Mailbox.SMTPAddress);
service.HttpHeaders.Add("X-PreferServerAffinity", "true");

Então a solicitação de assinatura do Alfred é assim.

POST https://outlook.office365.com/EWS/Exchange.asmx HTTP/1.1
Content-Type: text/xml; charset=utf-8
Accept: text/xml
User-Agent: ExchangeServicesClient/15.00.0516.014
X-AnchorMailbox: alfred@contoso.com
X-PreferServerAffinity: true
Host: outlook.office365.com
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:m="https://schemas.microsoft.com/exchange/services/2006/messages" xmlns:t="https://schemas.microsoft.com/exchange/services/2006/types" xmlns:soap="https://schemas.xmlsoap.org/soap/envelope/">
  <soap:Header>
    <t:RequestServerVersion Version="Exchange2013" />
    <t:ExchangeImpersonation>
      <t:ConnectingSID>
        <t:SmtpAddress>alfred@contoso.com</t:SmtpAddress>
      </t:ConnectingSID>
    </t:ExchangeImpersonation>
  </soap:Header>
  <soap:Body>
    <m:Subscribe>
      <m:StreamingSubscriptionRequest>
        <t:FolderIds>
          <t:DistinguishedFolderId Id="inbox" />
        </t:FolderIds>
        <t:EventTypes>
          <t:EventType>NewMailEvent</t:EventType>
        </t:EventTypes>
      </m:StreamingSubscriptionRequest>
    </m:Subscribe>
  </soap:Body>
</soap:Envelope>

A mensagem XML a seguir é a resposta à solicitação de assinatura de Alfred e inclui o X-BackEndOverrideCookie. Reenvia este cookie para todas as solicitações subsequentes para usuários nesse grupo. Observe que a resposta também contém cookies adicionais, como o cookie exchangecookie usado pelo Exchange 2010. Exchange Online, Exchange Online como parte de Office 365 e versões do Exchange a partir do Exchange 2013, ignore exchangecookie se ele for incluído em solicitações de assinatura subsequentes.

HTTP/1.1 200 OK
Content-Type: text/xml; charset=utf-8
Set-Cookie: exchangecookie=ddb8c383aef34c7694132aa679744feb; expires=Thu, 25-Sep-2014 18:42:45 GMT; path=/;
    HttpOnly
Set-Cookie: X-BackEndOverrideCookie=CO1PR06MB222.namprd06.prod.outlook.com~1941996295; path=/; secure; HttpOnly
Set-Cookie: X-BackEndCookie=alfred@contoso.com=Ox8XKzcXLxg==; 
    expires=Wed, 25-Sep-2013 18:52:49 GMT; path=/EWS; secure; HttpOnly
<?xml version="1.0" encoding="utf-8"?>
<s:Envelope xmlns:s="https://schemas.xmlsoap.org/soap/envelope/">
  <s:Header>
    <h:ServerVersionInfo MajorVersion="15"
                         MinorVersion="0"
                         MajorBuildNumber="775"
                         MinorBuildNumber="7"
                         Version="V2_4"
                         xmlns:h="https://schemas.microsoft.com/exchange/services/2006/types"
                         xmlns="https://schemas.microsoft.com/exchange/services/2006/types"
                         xmlns:xsd="http://www.w3.org/2001/XMLSchema"
                         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/>
  </s:Header>
  <s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <m:SubscribeResponse xmlns:m="https://schemas.microsoft.com/exchange/services/2006/messages"
                         xmlns:t="https://schemas.microsoft.com/exchange/services/2006/types">
      <m:ResponseMessages>
        <m:SubscribeResponseMessage ResponseClass="Success">
          <m:ResponseCode>NoError</m:ResponseCode>
          <m:SubscriptionId>JgBjbzFwcjA2bWIyMjIubmFtcHJkMDYucHJvZC5vdXRsb29rLmNvbRAAAAAUeGk+7JFdSaFM8/NI/gQQpVdgZX6H0Ag=</m:SubscriptionId>
        </m:SubscribeResponseMessage>
      </m:ResponseMessages>
    </m:SubscribeResponse>
  </s:Body>
</s:Envelope>

Usando o X-BackEndOverrideCookie da resposta de Alfred e o cabeçalho X-AnchorMailbox, a solicitação de assinatura é criada para Sadie, o outro membro do Grupo A. A solicitação de assinatura da Sadie é assim.

POST https://outlook.office365.com/EWS/Exchange.asmx HTTP/1.1
Content-Type: text/xml; charset=utf-8
Accept: text/xml
User-Agent: ExchangeServicesClient/15.00.0516.014
X-AnchorMailbox: alfred@contoso.com
X-PreferServerAffinity: true
Host: outlook.office365.com
Cookie: X-BackEndOverrideCookie=CO1PR06MB222.namprd06.prod.outlook.com~1941996295
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:m="https://schemas.microsoft.com/exchange/services/2006/messages" xmlns:t="https://schemas.microsoft.com/exchange/services/2006/types" xmlns:soap="https://schemas.xmlsoap.org/soap/envelope/">
  <soap:Header>
    <t:RequestServerVersion Version="Exchange2013" />
    <t:ExchangeImpersonation>
      <t:ConnectingSID>
        <t:SmtpAddress>sadie@contoso.com </t:SmtpAddress>
      </t:ConnectingSID>
    </t:ExchangeImpersonation>
  </soap:Header>
  <soap:Body>
    <m:Subscribe>
      <m:StreamingSubscriptionRequest>
        <t:FolderIds>
          <t:DistinguishedFolderId Id="inbox" />
        </t:FolderIds>
        <t:EventTypes>
          <t:EventType>NewMailEvent</t:EventType>
        </t:EventTypes>
      </m:StreamingSubscriptionRequest>
    </m:Subscribe>
  </soap:Body>
</soap:Envelope>

A resposta da assinatura da Sadie é assim. Observe que ele não inclui o X-BackEndOverrideCookie. O cliente é responsável por armazenar esse valor para solicitações futuras.

HTTP/1.1 200 OK
Content-Type: text/xml; charset=utf-8
Set-Cookie: exchangecookie=640ea858f69d47ff8cce8b44c337f6d9; path=/
Set-Cookie: X-BackEndCookie=alfred@contoso.com=Ox8XKzcXLxg==; 
   expires= Wed, 25-Sep-2013 18:53:06 GMT; path=/EWS; secure; HttpOnly
<?xml version="1.0" encoding="utf-8"?>
<s:Envelope xmlns:s="https://schemas.xmlsoap.org/soap/envelope/">
  <s:Header>
    <h:ServerVersionInfo MajorVersion="15"
                         MinorVersion="0"
                         MajorBuildNumber="775"
                         MinorBuildNumber="7"
                         Version="V2_4"
                         xmlns:h="https://schemas.microsoft.com/exchange/services/2006/types"
                         xmlns="https://schemas.microsoft.com/exchange/services/2006/types"
                         xmlns:xsd="http://www.w3.org/2001/XMLSchema"
                         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/>
  </s:Header>
  <s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <m:SubscribeResponse xmlns:m="https://schemas.microsoft.com/exchange/services/2006/messages"
                         xmlns:t="https://schemas.microsoft.com/exchange/services/2006/types">
      <m:ResponseMessages>
        <m:SubscribeResponseMessage ResponseClass="Success">
          <m:ResponseCode>NoError</m:ResponseCode>
          <m:SubscriptionId>JgBjbzFwcjA2bWIyMjIubmFtcHJkMDYucHJvZC5vdXRsb29rLmNvbRAAAAB4EQOy2pfrQJfM3hzs/nZJIZssan6H0Ag=</m:SubscriptionId>
        </m:SubscribeResponseMessage>
      </m:ResponseMessages>
    </m:SubscribeResponse>
  </s:Body>
</s:Envelope>

Usando os valores SubscriptionId das respostas de assinatura, uma solicitação de operação GetStreamingEvents foi criada para todas as assinaturas do grupo. Como há menos de 200 assinaturas nesse grupo, todas elas são enviadas em uma solicitação. O cabeçalho X-PreferServerAffinity é definido como true e o X-BackEndOverrideCookie está incluído.

POST https://outlook.office365.com/EWS/Exchange.asmx HTTP/1.1
Content-Type: text/xml; charset=utf-8
Accept: text/xml
User-Agent: ExchangeServicesClient/15.00.0516.014
X-AnchorMailbox: alfred@contoso.com
X-PreferServerAffinity: true
Host: outlook.office365.com
Cookie: X-BackEndOverrideCookie=CO1PR06MB222.namprd06.prod.outlook.com~1941996295
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:m="https://schemas.microsoft.com/exchange/services/2006/messages" xmlns:t="https://schemas.microsoft.com/exchange/services/2006/types" xmlns:soap="https://schemas.xmlsoap.org/soap/envelope/">
  <soap:Header>
    <t:RequestServerVersion Version="Exchange2013" />
    <t:ExchangeImpersonation>
      <t:ConnectingSID>
        <t:SmtpAddress>sadie@contoso.com</t:SmtpAddress>
      </t:ConnectingSID>
    </t:ExchangeImpersonation>
  </soap:Header>
  <soap:Body>
    <m:GetStreamingEvents>
      <m:SubscriptionIds>
        <t:SubscriptionId>JgBjbzFwcjA2bWIyMjIubmFtcHJkMDYucHJvZC5vdXRsb29rLmNvbRAAAAB4EQOy2pfrQJfM3hzs/nZJIZssan6H0Ag=</t:SubscriptionId>
        <t:SubscriptionId>JgBjbzFwcjA2bWIyMjIubmFtcHJkMDYucHJvZC5vdXRsb29rLmNvbRAAAAAUeGk+7JFdSaFM8/NI/gQQpVdgZX6H0Ag=</t:SubscriptionId>
      </m:SubscriptionIds>
      <m:ConnectionTimeout>10</m:ConnectionTimeout>
    </m:GetStreamingEvents>
  </soap:Body>
</soap:Envelope>

Em seguida, os eventos retornados são passados para um thread separado para processamento.

Como a afinidade mudou?

No Exchange 2010, as assinaturas são mantidas no servidor de Acesso ao Cliente, conforme mostrado na Figura 3. Em versões do Exchange posteriores ao Exchange 2010, as assinaturas são mantidas no servidor caixa de correio, conforme mostrado na Figura 4.

Figura 3. Processo para manter a afinidade no Exchange 2010

An illustration that shows how the table of active subscriptions is maintained on the Client Access server in Exchange 2010.

Figura 4. Processo para manter a afinidade no Exchange Online e no Exchange 2013

An illustration that shows how the load balancer and the Client Access server route requests to the mailbox server that maintains the table of active subscriptions in Exchange Server and Exchange Online.

No Exchange 2010, o cliente só conhece o endereço do balanceador de carga e o exchangecookie retornado pelo servidor garante que a solicitação seja roteada para o servidor de Acesso ao Cliente certo. No entanto, em versões posteriores, o balanceador de carga e as funções do servidor de Acesso ao Cliente precisam rotear as solicitações adequadamente antes de chegarem ao servidor da Caixa de Correio. Para fazer isso, são necessárias informações adicionais, razão pela qual os novos cabeçalhos e cookie foram introduzidos. O artigo Notificação de assinaturas, eventos de caixa de correio e EWS no Exchange explica como as assinaturas são mantidas no Exchange 2013.

Você pode notar que o exchangecookie que o Exchange 2010 usa ainda é retornado por versões posteriores. Não há nenhum dano em incluir esse cookie em solicitações, mas versões posteriores do Exchange ignoram-no.

Confira também