共用方式為

策略片段的中央元数据缓存

适用于:所有 API 管理层级

当多个策略片段需要访问共享元数据(如通用配置数据)时,请使用跨请求缓存方法来优化性能。 与在每个片段中重复分析元数据相比,分析一次,随处缓存方法可显著提高性能,同时确保数据一致性。 使用此方法,当缓存为空时,在第一个请求上 分析一次 元数据,然后在缓存过期或缓存版本更改之前 从缓存中检索 所有后续请求。

此方法需要两个片段:一个用于存储共享元数据,另一个用于分析和缓存元数据。

1. 元数据片段

元数据片段充当管道中其他片段访问的共享元数据的单一事实来源:

  • 集中式 JSON 存储:将所有元数据存储为 JSON。
  • 缓存设置:包括具有版本控制和持续时间的缓存设置(生存时间或 TTL)。

2. 分析和缓存片段

分析和缓存片段实现以下行为:

  • 单次解析操作:如果缓存为空,将在每个管道请求开始时使用 JObject.Parse() 对存储在元数据片段中的 JSON 进行一次解析。
  • 跨请求缓存:在多个请求中,使用 内置的 JObjectcache-lookup-value 策略,作为 存储和检索解析的元数据部分。
  • 缓存优先访问:后续请求直接从缓存检索分析 JObject ,从而提供对所有片段的即时访问,而无需重新分析。
  • 缓存失效:当元数据版本更改或缓存持续时间 (TTL) 过期时,缓存会刷新。

实施细节

若要实现此模式,请将这两个片段插入到入站阶段开始时的产品或 API 策略定义中。 必须先插入元数据片段,然后插入分析和缓存片段。 例如:

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

元数据片段示例

片段metadata-fragment.xml将共享的 JSON 元数据存储在名为上下文变量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>

分析和缓存片段示例

片段 parse-cache-fragment.xml 分析存储在上下文变量中的 metadata-config JSON 一次,并提供对生成的 JObject访问。 变量 metadata-config 必须已由以下项 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>

在其他片段中使用元数据

其他片段现在可以直接访问分析的元数据部分。 例如:

<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>

缓存设置和失效

parse-cache-fragment.xml 片段使用片段中存储的 metadata-fragment.xml 缓存设置来确定缓存行为和失效。 例如,可按如下所示更改设置:

<!-- 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'
  }
}

缓存失效的工作原理: 片段 parse-cache-fragment.xml 使用 config-version 值(例如, metadata-config-v1.0.1)构造缓存键。 当版本更改为 1.0.2时,将创建一个新的缓存密钥(metadata-config-v1.0.2)。 由于新键不存在缓存的数据,片段将分析新的元数据 JSON。

强制刷新缓存:config-version片段中更新metadata-fragment.xml。 由于缓存设置在缓存查找发生之前对每个请求进行分析,因此对缓存配置的更改会立即生效。

测试和调试

缓存结果跟踪

片段 parse-cache-fragment.xml 设置变量 config-cache-result 。 此变量可用于日志记录并用于响应标头中的调试。

<!-- 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>

缓存绕过

若要禁用缓存,请使用缓存绕过标头:

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

片段 parse-cache-fragment.xml 在分析缓存设置后检查绕过标头:

<!-- 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";
}" />

然后,在缓存决策逻辑中使用绕过检查:

<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>

最佳做法

使用错误日志记录和默认值处理 JSON 分析失败

为 JSON 解析实现错误处理,以预防解析过程中的故障,并提供回退机制。 在 try-catch 块中使用有意义的默认值来包装 JObject.Parse() 操作。

<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>