Partilhar via


Cache personalizado no Gerenciamento de API do Azure

APLICA-SE A: Todas as camadas de 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 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 armazenar em cache apenas 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 agrega 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 por locatário para que, à medida que você aumenta a escala para várias unidades, ainda tenha acesso aos mesmos dados armazenados em cache. No entanto, ao trabalhar com uma implementaçã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 alguma informação. Se você fez isso, e mais tarde decidiu aproveitar a implantação em 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 todos os níveis.

Cache de fragmentos

Há certos casos em que as respostas que são devolvidas contêm uma parte dos dados cujo custo é elevado para determinar. No entanto, os dados permanecem atualizados por um período de tempo razoável. Por exemplo, considere um serviço criado por uma companhia aérea que fornece informações relacionadas a reservas de voos, status de voos e assim por diante. Se o usuário for membro do programa de pontos das companhias aéreas, ele também terá informações relacionadas ao seu status atual e quilometragem acumulada. Estas informações relacionadas com o utilizador podem ser armazenadas num sistema diferente, mas pode ser desejável incluí-las nas respostas devolvidas sobre o estado do voo e as reservas. 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 no estado /userprofile/{userid} que se parece com,

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

Para determinar as informações apropriadas do usuário a serem incluídas, o Gerenciamento de API precisa identificar quem é o usuário final. Este mecanismo depende da implementação. O exemplo a seguir usa a Subject declaração 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 pesquisa de cache para ajudar a limitar o número de chamadas e evitar sobrecarga no serviço backend caso a 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 terá que 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 o enduserid para construir a URL para o recurso de perfil de usuário. Depois que o Gerenciamento de API tiver a resposta, ele retira o corpo do texto da resposta e o armazena de volta 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 seja 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 na tolerância dos usuários a informações desatualizadas.

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

A etapa final do 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 onde o HTML é composto no lado do servidor para que possa ser renderizado como uma única página. Também pode ser útil em APIs onde 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 fragmentos 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.

Controle de versão transparente

É prática comum que várias versões de implementação diferentes de uma API sejam suportadas a qualquer momento. Por exemplo, para oferecer suporte a diferentes ambientes (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 de clientes alterem as URLs de /v1/customers para /v2/customers, é armazenar nos dados de perfil do consumidor qual versão da API desejem usar atualmente 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 esses dados de configuração em cache, o Gerenciamento de API pode minimizar a penalidade de desempenho de fazer essa pesquisa.

O primeiro passo é 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 pesquisa de cache para ajudar a limitar o número de chamadas e evitar sobrecarga no serviço backend caso a cache não esteja disponível.

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

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

Se o Gerenciamento de API não o encontrar, 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 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" />

E, finalmente, atualize a URL de back-end para selecionar a versão do serviço desejada 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 ter que atualizar e reimplantar seus clientes.

Isolamento de inquilinos

Em implantações maiores e multilocatário, 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 que são afetados quando há problemas de hardware no back-end. Ele também permite que novas versões de software sejam lançadas em etapas. Idealmente, essa arquitetura de back-end deve ser transparente para os consumidores de API. Você pode alcançar essa transparência usando uma técnica semelhante ao versionamento transparente, manipulando a URL de back-end usando o estado de configuração para cada 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 da API do Azure para armazenar qualquer tipo de dados permite o 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, retornadas de uma API de back-end.