Compartilhar via


Cache personalizado no Gerenciamento de API do Azure

APLICA-SE A: todas as camadas do Gerenciamento de API

O serviço de Gerenciamento de API do Azure tem suporte interno para cache de resposta HTTP usando a URL do recurso como a chave. Você pode modificar a chave usando cabeçalhos de solicitação que usam as vary-by propriedades. Essa técnica é útil para armazenar em cache respostas HTTP inteiras (também conhecidas como representações), mas às vezes é útil apenas armazenar em cache uma parte de uma representação. As políticas cache-lookup-value e cache-store-value oferecem a capacidade de armazenar e recuperar partes arbitrárias de dados de dentro de definições de política. Essa capacidade também adiciona valor à política de solicitação de envio porque você pode armazenar em cache respostas de serviços externos.

Architecture

O serviço de Gerenciamento de API usa um cache de dados interno compartilhado entre locatários para que, à medida que você escala para múltiplas unidades, ainda tenha acesso aos mesmos dados em cache. No entanto, ao trabalhar com uma implantação em várias regiões, há caches independentes em cada uma das regiões. É importante não tratar o cache como um armazenamento de dados, onde ele é a única fonte de algumas informações. Se você fez isso e depois decidiu aproveitar a implantação de várias regiões, os clientes com usuários que viajam podem perder o acesso a esses dados armazenados em cache.

Observação

O cache interno não está disponível na camada de consumo do Gerenciamento de API do Azure. Em vez disso, você pode usar um cache externo compatível com Redis . Um cache externo permite maior controle de cache e flexibilidade para instâncias de Gerenciamento de API em todas as camadas.

Cache de fragmento

Há certos casos em que as respostas retornadas contêm parte dos dados que são caros de determinar. No entanto, os dados permanecem frescos por um período razoável de tempo. Por exemplo, considere um serviço criado por uma companhia aérea que fornece informações relacionadas a reservas de voo, status de voo e assim por diante. Se o usuário for membro do programa de pontos de companhias aéreas, ele também terá informações relacionadas ao status atual e à quilometragem acumulada. Essas informações relacionadas ao usuário podem ser armazenadas em um sistema diferente, mas pode ser desejável incluí-la em respostas retornadas sobre o status e as reservas de voo. Você pode incluir esses dados usando um processo chamado cache de fragmentos. A representação primária pode ser retornada do servidor de origem usando algum tipo de token para indicar onde as informações relacionadas ao usuário devem ser inseridas.

Considere a seguinte resposta JSON de uma API de back-end.

{
  "airline" : "Air Canada",
  "flightno" : "871",
  "status" : "ontime",
  "gate" : "B40",
  "terminal" : "2A",
  "userprofile" : "$userprofile$"
}  

E um recurso secundário em /userprofile/{userid} que parece,

{ "username" : "Bob Smith", "Status" : "Gold" }

Para determinar as informações de usuário apropriadas a serem incluídas, o Gerenciamento de API precisa identificar quem é o usuário final. Esse mecanismo depende da implementação. O exemplo a seguir usa a declaração Subject de um JWT token.

<set-variable
  name="enduserid"
  value="@(context.Request.Headers.GetValueOrDefault("Authorization","").Split(' ')[1].AsJwt()?.Subject)" />

O Gerenciamento de API armazena o enduserid valor em uma variável de contexto para uso posterior. A próxima etapa é determinar se uma solicitação anterior já recuperou as informações do usuário e as armazenou no cache. Para isso, o Gerenciamento de API usa a cache-lookup-value política.

<cache-lookup-value
key="@("userprofile-" + context.Variables["enduserid"])"
variable-name="userprofile" />
<rate-limit calls="10" renewal-period="60" />

Observação

Adicione uma política de limite de taxa (ou política de limite de taxa por chave ) após a busca no cache para ajudar a limitar o número de chamadas e evitar sobrecarga no serviço backend caso o cache não esteja disponível.

Se não houver nenhuma entrada no cache que corresponda ao valor da chave, nenhuma userprofile variável de contexto será criada. O Gerenciamento de API verifica o sucesso da pesquisa usando a choose política de fluxo de controle.

<choose>
    <when condition="@(!context.Variables.ContainsKey("userprofile"))">
        <!-- If the userprofile context variable doesn’t exist, make an HTTP request to retrieve it.  -->
    </when>
</choose>

Se a userprofile variável de contexto não existir, o Gerenciamento de API precisará fazer uma solicitação HTTP para recuperá-la.

<send-request
  mode="new"
  response-variable-name="userprofileresponse"
  timeout="10"
  ignore-error="true">

  <!-- Build a URL that points to the profile for the current end-user -->
  <set-url>@(new Uri(new Uri("https://apimairlineapi.azurewebsites.net/UserProfile/"),
      (string)context.Variables["enduserid"]).AbsoluteUri)
  </set-url>
  <set-method>GET</set-method>
</send-request>

O Gerenciamento de API usa a enduserid para construir a URL para o recurso de perfil de usuário. Depois que o Gerenciamento de API tiver a resposta, ele extrairá o texto do corpo da resposta e o armazenará novamente em uma variável de contexto.

<set-variable
    name="userprofile"
    value="@(((IResponse)context.Variables["userprofileresponse"]).Body.As<string>())" />

Para evitar que o Gerenciamento de API faça essa solicitação HTTP novamente quando o mesmo usuário fizer outra solicitação, você pode especificar que o perfil de usuário está armazenado no cache.

<cache-store-value
    key="@("userprofile-" + context.Variables["enduserid"])"
    value="@((string)context.Variables["userprofile"])" duration="100000" />

O Gerenciamento de API armazena o valor no cache usando a mesma chave com a qual o Gerenciamento de API tentou recuperá-lo originalmente. A duração que o Gerenciamento de API escolhe para armazenar o valor deve ser baseada na frequência com que as informações são alteradas e o quão tolerantes os usuários são para obter informações desatualizadas.

É importante perceber que recuperar informações do cache ainda é uma solicitação de rede fora do processo e pode potencialmente adicionar dezenas de milissegundos à solicitação. Os benefícios surgem quando determinar as informações de perfil do usuário leva mais tempo do que recuperar informações do cache, devido à necessidade de consultas ao banco de dados ou à agregação de informações de vários back-ends.

A etapa final no processo é atualizar a resposta retornada com as informações do perfil do usuário.

<!-- Update response body with user profile-->
<find-and-replace
    from='"$userprofile$"'
    to="@((string)context.Variables["userprofile"])" />

Você pode optar por incluir as aspas como parte do token para que, mesmo quando a substituição não ocorrer, a resposta ainda seja JSON válida.

Depois de combinar essas etapas, o resultado final é uma política semelhante à seguinte.

<policies>
    <inbound>
        <!-- How you determine user identity is application dependent -->
        <set-variable
          name="enduserid"
          value="@(context.Request.Headers.GetValueOrDefault("Authorization","").Split(' ')[1].AsJwt()?.Subject)" />

        <!--Look for userprofile for this user in the cache -->
        <cache-lookup-value
          key="@("userprofile-" + context.Variables["enduserid"])"
          variable-name="userprofile" />
        <rate-limit calls="10" renewal-period="60" />

        <!-- If API Management doesn’t find it in the cache, make a request for it and store it -->
        <choose>
            <when condition="@(!context.Variables.ContainsKey("userprofile"))">
                <!-- Make HTTP request to get user profile -->
                <send-request
                  mode="new"
                  response-variable-name="userprofileresponse"
                  timeout="10"
                  ignore-error="true">

                   <!-- Build a URL that points to the profile for the current end-user -->
                    <set-url>@(new Uri(new Uri("https://apimairlineapi.azurewebsites.net/UserProfile/"),(string)context.Variables["enduserid"]).AbsoluteUri)</set-url>
                    <set-method>GET</set-method>
                </send-request>

                <!-- Store response body in context variable -->
                <set-variable
                  name="userprofile"
                  value="@(((IResponse)context.Variables["userprofileresponse"]).Body.As<string>())" />

                <!-- Store result in cache -->
                <cache-store-value
                  key="@("userprofile-" + context.Variables["enduserid"])"
                  value="@((string)context.Variables["userprofile"])"
                  duration="100000" />
            </when>
        </choose>
        <base />
    </inbound>
    <outbound>
        <!-- Update response body with user profile-->
        <find-and-replace
              from='"$userprofile$"'
              to="@((string)context.Variables["userprofile"])" />
        <base />
    </outbound>
</policies>

Essa abordagem de cache é usada principalmente em sites em que HTML é composto no lado do servidor para que possa ser renderizado como uma única página. Também pode ser útil em APIs em que os clientes não podem fazer cache HTTP do lado do cliente ou é desejável não colocar essa responsabilidade no cliente.

Esse mesmo tipo de cache de fragmento também pode ser feito nos servidores Web de back-end usando um servidor de cache Redis. No entanto, usar o serviço de Gerenciamento de API para executar esse trabalho é útil quando os fragmentos armazenados em cache são provenientes de back-ends diferentes das respostas primárias.

Versionamento transparente

É uma prática comum que várias versões de implementação diferentes de uma API sejam compatíveis a qualquer momento. Por exemplo, para dar suporte a ambientes diferentes (desenvolvimento, teste, produção etc.) ou para dar suporte a versões mais antigas da API para dar tempo para os consumidores de API migrarem para versões mais recentes.

Uma abordagem para lidar com várias versões, em vez de exigir que os desenvolvedores cliente alterem as URLs de /v1/customers para /v2/customers, é armazenar nos dados de perfil do consumidor qual versão da API eles atualmente desejam usar e chamar a URL de back-end apropriada. Para determinar a URL de back-end correta para chamar um cliente específico, é necessário consultar alguns dados de configuração. Quando você armazena em cache esses dados de configuração, o gerenciamento de API pode minimizar o impacto no desempenho da realização dessa consulta.

A primeira etapa é determinar o identificador usado para configurar a versão desejada. Neste exemplo, associamos a versão à chave de assinatura do produto.

<set-variable name="clientid" value="@(context.Subscription.Key)" />

Em seguida, o Gerenciamento de API faz uma pesquisa de cache para ver se ele já recuperou a versão do cliente desejada.

<cache-lookup-value
key="@("clientversion-" + context.Variables["clientid"])"
variable-name="clientversion" />
<rate-limit calls="10" renewal-period="60" />

Observação

Adicione uma política de limite de taxa (ou política de limite de taxa por chave ) após a busca no cache para ajudar a limitar o número de chamadas e evitar sobrecarga no serviço backend caso o cache não esteja disponível.

Em seguida, o Gerenciamento de API verifica se ele não o encontrou no cache.

<choose>
    <when condition="@(!context.Variables.ContainsKey("clientversion"))">

Se o Gerenciamento de API não o encontrou, o Gerenciamento de API o recuperará.

<send-request
    mode="new"
    response-variable-name="clientconfiguresponse"
    timeout="10"
    ignore-error="true">
            <set-url>@(new Uri(new Uri(context.Api.ServiceUrl.ToString() + "api/ClientConfig/"),(string)context.Variables["clientid"]).AbsoluteUri)</set-url>
            <set-method>GET</set-method>
</send-request>

Extraia o texto do corpo de resposta da resposta.

<set-variable
      name="clientversion"
      value="@(((IResponse)context.Variables["clientconfiguresponse"]).Body.As<string>())" />

Armazene-o de volta no cache para uso futuro.

<cache-store-value
      key="@("clientversion-" + context.Variables["clientid"])"
      value="@((string)context.Variables["clientversion"])"
      duration="100000" />

Por fim, atualize a URL de back-end para selecionar a versão do serviço desejado pelo cliente.

<set-backend-service
      base-url="@(context.Api.ServiceUrl.ToString() + "api/" + (string)context.Variables["clientversion"] + "/")" />

A política completa é a seguinte:

<inbound>
    <base />
    <set-variable name="clientid" value="@(context.Subscription.Key)" />
    <cache-lookup-value key="@("clientversion-" + context.Variables["clientid"])" variable-name="clientversion" />
    <rate-limit calls="10" renewal-period="60" />

    <!-- If API Management doesn’t find it in the cache, make a request for it and store it -->
    <choose>
        <when condition="@(!context.Variables.ContainsKey("clientversion"))">
            <send-request mode="new" response-variable-name="clientconfiguresponse" timeout="10" ignore-error="true">
                <set-url>@(new Uri(new Uri(context.Api.ServiceUrl.ToString() + "api/ClientConfig/"),(string)context.Variables["clientid"]).AbsoluteUri)</set-url>
                <set-method>GET</set-method>
            </send-request>
            <!-- Store response body in context variable -->
            <set-variable name="clientversion" value="@(((IResponse)context.Variables["clientconfiguresponse"]).Body.As<string>())" />
            <!-- Store result in cache -->
            <cache-store-value key="@("clientversion-" + context.Variables["clientid"])" value="@((string)context.Variables["clientversion"])" duration="100000" />
        </when>
    </choose>
    <set-backend-service base-url="@(context.Api.ServiceUrl.ToString() + "api/" + (string)context.Variables["clientversion"] + "/")" />
</inbound>

Essa solução elegante aborda muitas preocupações de controle de versão de API, permitindo que os consumidores de API controlem de forma transparente qual versão de back-end seus clientes estão acessando sem precisar atualizar e reimplantar seus clientes.

Isolamento de locatário

Em implantações maiores e multilocatários, algumas empresas criam grupos separados de locatários em implantações distintas de hardware de back-end. Essa estrutura minimiza o número de clientes afetados quando há um problema de hardware no back-end. Ele também permite que novas versões de software sejam distribuídas em estágios. O ideal é que essa arquitetura de back-end seja transparente para os consumidores de API. Você pode obter essa transparência usando uma técnica semelhante ao controle de versão transparente, manipulando a URL do back-end usando o estado de configuração por chave de API.

Em vez de retornar uma versão preferencial da API para cada chave de assinatura, você retornaria um identificador que relaciona um locatário ao grupo de hardware atribuído. Esse identificador pode ser usado para construir a URL de back-end apropriada.

Resumo

A liberdade de usar o cache de gerenciamento de API do Azure para armazenar qualquer tipo de dados permite acesso eficiente aos dados de configuração que podem afetar a maneira como uma solicitação de entrada é processada. Ele também pode ser usado para armazenar fragmentos de dados que podem aumentar as respostas, retornados de uma API de back-end.