Partilhar via


Cache central de metadados para fragmentos de políticas

APLICA-SE A: Todas as camadas de gerenciamento de API

Quando múltiplos fragmentos de política precisam de acesso a metadados partilhados, como dados de configuração comuns, utilize-se uma abordagem de cache cross-request para otimizar o desempenho. Em vez de analisar metadados repetidamente em cada fragmento, uma abordagem de análise uma vez e cache em todo o lado melhora drasticamente o desempenho, garantindo a consistência dos dados. Com esta abordagem, os metadados são analisados uma vez no primeiro pedido quando a cache está vazia, e depois recuperados da cache para todos os pedidos subsequentes até que a cache expire ou a versão da cache mude.

Esta abordagem requer dois fragmentos: um para armazenar metadados partilhados e outro para analisar e armazenar em cache os metadados.

1. Fragmento de metadados

O fragmento de metadados serve como a única fonte confiável para metadados partilhados que são acedidos por outros fragmentos no pipeline.

  • Armazenamento JSON centralizado: Armazena todos os metadados como JSON.
  • Definições de cache: Inclui definições de cache com versionamento e duração (Time to Live, ou TTL).

** 2. Análise e cache de fragmentos

O fragmento de análise e cache implementa os seguintes comportamentos:

  • Operação única de análise sintética: Utiliza JObject.Parse() para analisar o JSON armazenado no fragmento de metadados uma vez no início de cada pedido de pipeline, caso a cache esteja vazia.
  • Cache entre pedidos: Armazena e recupera secções de metadados analisadas sob a forma de JObject utilizando as políticas integradas cache-store-value e cache-lookup-value em vários pedidos.
  • Acesso com prioridade para cache: Pedidos subsequentes recuperam um analisado JObject diretamente do cache, proporcionando acesso imediato a todos os fragmentos sem necessidade de nova análise.
  • Invalidação da cache: A cache atualiza-se quando a versão dos metadados muda ou quando a duração da cache (TTL) expira.

Detalhes da implementação

Para implementar este padrão, insira ambos os fragmentos numa definição de política de produto ou API no início da fase de entrada. O fragmento de metadados deve ser inserido primeiro, seguido pelo fragmento de análise e cache. Por exemplo:

<policies>
    <inbound>
        <base />
        <include-fragment fragment-id="metadata-fragment" />
        <include-fragment fragment-id="parse-cache-fragment" />
    </inbound>
</policies>

Exemplo de fragmento de metadados

O metadata-fragment.xml fragmento armazena metadados JSON partilhados numa variável de contexto chamada metadata-config:

<!-- Single source of truth for all shared metadata -->
<fragment fragment-id="metadata-fragment">
  <set-variable name="metadata-config" value="@{return @"{
    'cache-settings': {
      'config-version': '1.0',
      'ttl-seconds': 3600,
      'feature-flags': {
        'enable-cross-request-cache': true,
        'cache-bypass-header': 'X-Config-Cache-Bypass'
      }
    },
    'logging': {
      'level': 'INFO',
      'enabled': true
    },
    'rate-limits': {
      'premium': { 'requests-per-minute': 1000 },
      'standard': { 'requests-per-minute': 100 },
      'basic': { 'requests-per-minute': 20 }
    }
  }";}" />
</fragment>

Exemplo de fragmento de análise sintática e cache

O parse-cache-fragment.xml fragmento analisa o JSON armazenado na metadata-config variável de contexto uma vez e fornece acesso ao resultado JObject. A metadata-config variável já deve estar definida por metadata-fragment.xml:

<fragment fragment-id="parse-cache-fragment">
  <!-- Extract cache settings from metadata-config to determine cache version and TTL -->
  <set-variable name="cache-config-temp" value="@{
    try {
      var configStr = context.Variables.GetValueOrDefault<string>("metadata-config", "{}");
      if (string.IsNullOrEmpty(configStr) || configStr == "{}") {
        return "{\"version\":\"1.0\",\"enabled\":true,\"ttl\":3600}";
      }
      
      var tempConfig = JObject.Parse(configStr);
      var cacheSettings = tempConfig["cache-settings"] as JObject;
      
      var result = new JObject();
      result["version"] = cacheSettings?["config-version"]?.ToString() ?? "1.0";
      result["enabled"] = cacheSettings?["feature-flags"]?["enable-cross-request-cache"]?.Value<bool>() ?? true;
      result["ttl"] = cacheSettings?["ttl-seconds"]?.Value<int>() ?? 3600;
      
      return result.ToString(Newtonsoft.Json.Formatting.None);
    } catch {
      return "{\"version\":\"1.0\",\"enabled\":true,\"ttl\":3600}";
    }
  }" />
  
  <!-- Parse cache configuration -->
  <set-variable name="cache-settings-parsed" value="@{
    return JObject.Parse(context.Variables.GetValueOrDefault<string>("cache-config-temp", "{}"));
  }" />
  
  <!-- Build cache key with version from cache settings -->
  <set-variable name="cache-key" value="@{
    var settings = context.Variables.GetValueOrDefault<JObject>("cache-settings-parsed");
    var version = settings?["version"]?.ToString() ?? "1.0";
    return "metadata-config-parsed-v" + version;
  }" />
  
  <!-- Try to get from APIM cache -->
  <cache-lookup-value key="@(context.Variables.GetValueOrDefault<string>("cache-key"))" variable-name="cached-config" />
  
  <choose>
    <when condition="@(context.Variables.ContainsKey("cached-config"))">
      <!-- Cache found - Use cached configuration -->
      <set-variable name="config-cache-result" value="@(true)" />
      
      <!-- Restore cached config-parsed -->
      <set-variable name="config-parsed" value="@(context.Variables.GetValueOrDefault<JObject>("cached-config"))" />
      
      <!-- Extract sections from cached metadata JObject -->
      <set-variable name="config-logging" value="@{
        var config = context.Variables.GetValueOrDefault<JObject>("config-parsed");
        return config["logging"] as JObject ?? new JObject();
      }" />
      
      <set-variable name="config-rate-limits" value="@{
        var config = context.Variables.GetValueOrDefault<JObject>("config-parsed");
        return config["rate-limits"] as JObject ?? new JObject();
      }" />
    </when>
    <otherwise>
      <!-- Cache miss - Parse and store in cache -->
      <set-variable name="config-cache-result" value="@(false)" />
      
      <!-- Parse metadata-config JSON -->
      <set-variable name="config-parsed" value="@{
        var configStr = context.Variables.GetValueOrDefault<string>("metadata-config", "{}");
        return JObject.Parse(configStr);
      }" />
      
      <!-- Extract commonly used sections for direct access -->
      <set-variable name="config-logging" value="@{
        var config = context.Variables.GetValueOrDefault<JObject>("config-parsed");
        return config["logging"] as JObject ?? new JObject();
      }" />
      
      <set-variable name="config-rate-limits" value="@{
        var config = context.Variables.GetValueOrDefault<JObject>("config-parsed");
        return config["rate-limits"] as JObject ?? new JObject();
      }" />
      
      <!-- Store parsed metadata JObject in cache -->
      <cache-store-value key="@(context.Variables.GetValueOrDefault<string>("cache-key"))" 
                         value="@(context.Variables.GetValueOrDefault<JObject>("config-parsed"))" 
                         duration="@(context.Variables.GetValueOrDefault<JObject>("cache-settings-parsed")?["ttl"]?.Value<int>() ?? 3600)" />
    </otherwise>
  </choose>
</fragment>

Utilização de metadados noutros fragmentos

Outros fragmentos podem agora aceder diretamente a secções de metadados analisadas. Por exemplo:

<fragment fragment-id="request-logging-fragment">
  <!-- Access logging metadata JObject without reparsing -->
  <set-variable name="config-logging" value="@{
    return context.Variables.GetValueOrDefault<JObject>("config-logging", new JObject()); 
  }" />
</fragment>

Definições de cache e invalidação

O fragmento parse-cache-fragment.xml utiliza as definições de cache armazenadas no fragmento metadata-fragment.xml para determinar o comportamento de cache e a invalidade. Por exemplo, as definições podem ser alteradas da seguinte forma:

<!-- Example: Updated cache settings in the metadata fragment -->
'cache-settings': {
  'config-version': '1.0.1',     <!-- Change version to invalidate cache -->
  'ttl-seconds': 7200,           <!-- Increase TTL to 2 hours -->
  'feature-flags': {
    'enable-cross-request-cache': true,
    'cache-bypass-header': 'X-Config-Cache-Bypass'
  }
}

Como funciona a invalidação de cache: O parse-cache-fragment.xml fragmento constrói chaves de cache usando o config-version valor (por exemplo, metadata-config-v1.0.1). Quando a versão é alterada para 1.0.2, é criada uma nova chave de cache (metadata-config-v1.0.2). Como não existem dados em cache para a nova chave, o fragmento analisa novos metadados em JSON.

Para forçar a atualização da cache: Atualize o config-version no metadata-fragment.xml fragmento. Como as definições de cache são analisadas em cada pedido antes de ocorrer a consulta de cache, as alterações à configuração da cache entram em vigor imediatamente.

Teste e depuração

Rastreamento de resultados de cache

O parse-cache-fragment.xml fragmento define uma config-cache-result variável. Esta variável é útil para registos e cabeçalhos de resposta para depuração:

<!-- Add cache status to response headers for debugging -->
<set-header name="X-Config-Cache-Result" exists-action="override">
  <value>@(context.Variables.GetValueOrDefault<bool>("config-cache-result", false).ToString())</value>
</set-header>

Ignorar cache

Para desativar a cache, use o cabeçalho para contornar a cache.

curl -H "X-Config-Cache-Bypass: true" https://your-gateway.com/api

O parse-cache-fragment.xml fragmento verifica o cabeçalho de bypass depois de analisar as definições da cache:

<!-- Check if cache bypass is requested -->
<set-variable name="cache-bypass-requested" value="@{
  var settings = context.Variables.GetValueOrDefault<JObject>("cache-settings-parsed");
  var bypassHeader = settings?["bypass-header"]?.ToString() ?? "X-Config-Cache-Bypass";
  return context.Request.Headers.GetValueOrDefault(bypassHeader, "").ToLower() == "true";
}" />

A verificação de bypass é então usada na lógica de decisão de cache.

<when condition="@{
  var settings = context.Variables.GetValueOrDefault<JObject>("cache-settings-parsed");
  var enabled = settings?["enabled"]?.Value<bool>() ?? false;
  var bypass = context.Variables.GetValueOrDefault<bool>("cache-bypass-requested", false);
  return enabled && !bypass;
}">
  <!-- Cross-request caching is enabled and not bypassed -->
</when>

Melhores práticas

Gerir falhas no processamento de JSON com registo de erros e valores predefinidos

Implementar o tratamento de erros para operações de análise JSON para evitar falhas de fragmentos e proporcionar comportamento de recuo. Envolver operações JObject.Parse() em blocos try-catch com valores padrão significativos:

<set-variable name="config-parsed" value="@{
  try {
    var configJson = context.Variables.GetValueOrDefault<string>("metadata-config", "{}");
    return JObject.Parse(configJson);
  } catch (Exception ex) {
    // Return default configuration on parse failure
    return JObject.Parse(@"{
      'logging': { 'level': 'ERROR', 'enabled': false },
      'rate-limits': { 'default': { 'requests-per-minute': 10 } }
    }");
  }
}" />

<!-- Log parse error using trace policy -->
<choose>
  <when condition="@(context.Variables.ContainsKey("parse-error"))">
    <trace source="config-parse" severity="error">
      <message>@("JSON parse failed: " + context.Variables.GetValueOrDefault<string>("parse-error"))</message>
    </trace>
  </when>
</choose>