次の方法で共有

Azure API 管理中的自定义缓存

适用于:所有 API 管理层级

Azure API 管理服务内置支持使用资源 URL 作为密钥的 HTTP 响应缓存 。 可以使用使用 vary-by 属性的请求标头修改密钥。 此方法可用于缓存整个 HTTP 响应(也称为表示形式),但有时只缓存部分表示形式很有用。 使用 cache-lookup-valuecache-store-value 策略,可以从策略定义中存储和检索任意数据片段。 此功能还会将值添加到 发送请求 策略,因为你可以缓存来自外部服务的响应。

Architecture

API 管理服务使用共享的每租户内部数据缓存,以便在纵向扩展到多个单元时,仍可以访问相同的缓存数据。 但是,在使用多区域部署时,每个区域中都有独立的缓存。 请务必不要将缓存视为数据存储,而该存储是某些信息块的唯一来源。 如果你这样做了,后来决定利用多区域部署,则具有旅行的用户的客户可能会失去对该缓存数据的访问权限。

注释

内部缓存在 Azure API 管理的 消耗 层中不可用。 可以改 用与外部 Redis 兼容的缓存 。 外部缓存允许对所有层中的 API 管理实例进行更高的缓存控制和灵活性。

片段缓存

在某些情况下,返回的响应包含一些难以确定且资源消耗较高的数据。 然而,数据在合理的时间内保持新鲜。 例如,考虑航空公司构建的服务,该服务提供有关航班预订、航班状态等的信息。 如果用户是航空公司积分计划的成员,他们也会获得与其当前状态和累积里程相关的信息。 此用户相关信息可能存储在不同的系统中,但可能需要将其包含在有关航班状态和预留的响应中。 可以使用称为片段缓存的进程来包含此数据。 主要表示形式可以使用某种令牌从源服务器返回,以指示要插入用户相关信息的位置。

请考虑后端 API 的以下 JSON 响应。

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

辅助资源 /userprofile/{userid} 如下所示:

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

若要确定要包括的相应用户信息,API 管理需要确定最终用户是谁。 此机制依赖于实现。 以下示例使用 Subject 令牌中的 JWT 声明。

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

API 管理将 enduserid 值存储在上下文变量中供以后使用。 下一步是确定以前的请求是否已检索用户信息并将其存储在缓存中。 为此,API 管理使用 cache-lookup-value 策略。

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

注释

缓存查找后添加的 速率限制 策略有助于限制在缓存不可用时阻止后端服务重载的调用数。

如果缓存中没有对应于键值的条目,则不会 userprofile 创建上下文变量。 API 管理使用 choose 控制流策略检查查找是否成功。

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

userprofile如果上下文变量不存在,则 API 管理必须发出 HTTP 请求才能检索它。

<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.chinacloudsites.cn/UserProfile/"),
      (string)context.Variables["enduserid"]).AbsoluteUri)
  </set-url>
  <set-method>GET</set-method>
</send-request>

API 管理使用 enduserid 来构造用户配置文件资源的 URL。 API 管理获得响应后,它将正文文本从响应中提取出来,并将其存储回上下文变量。

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

为了避免API管理服务在同一用户再次发出请求时重新发起此HTTP请求,可以指定将用户配置文件存储在缓存中。

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

API 管理使用最初尝试检索时的相同密钥将值存储在缓存中。 API 管理选择存储值的持续时间应基于信息更改的频率以及用户对过时信息的容忍程度。

请务必认识到,从缓存中检索信息仍然是进程外网络请求,并且可能会向请求添加数十毫秒。 当确定用户配置文件信息所需的时间比从缓存中检索信息更长时,由于需要进行数据库查询或聚合来自多个后端的信息,这种情况下就会体现出优势。

该过程的最后一步是使用用户配置文件信息更新返回的响应。

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

可以选择将引号作为标记的一部分进行包含,这样即使没有发生替换,响应仍然是有效的 JSON。

合并这些步骤后,最终结果是如下所示的策略。

<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.chinacloudsites.cn/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>

此缓存方法主要用于在服务器端撰写 HTML 的网站中,以便将其呈现为单个页面。 在某些 API 中,客户端无法执行客户端 HTTP 缓存,或者不希望将这一责任承担在客户端上,这种情况下,它也会非常有用。

也可以使用 Redis 缓存服务器在后端 Web 服务器上完成此类片段缓存。 但是,当缓存的片段来自与主要响应不同的后端时,使用 API 管理服务来执行这项工作非常有用。

透明版本控制

同时支持多个不同 API 实现版本是常见做法。 例如,若要支持不同的环境(开发、测试、生产等),或支持旧版 API,让 API 使用者有时间迁移到较新版本。

一种处理多个版本的方法是不要求客户端开发人员更改从 /v1/customers/v2/customers 的 URL,而是在使用者的配置文件中存储他们希望当前使用的 API 版本,从而调用相应的后端 URL。 若要确定要为特定客户端调用的正确后端 URL,必须查询某些配置数据。 缓存此配置数据时,API 管理可以最大程度地减少执行此查找的性能损失。

第一步是确定用于配置所需版本的标识符。 在此示例中,我们将版本关联到产品订阅密钥。

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

然后,API 管理会执行缓存查找,查看它是否已检索到所需的客户端版本。

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

注释

缓存查找后添加的 速率限制 策略有助于限制在缓存不可用时阻止后端服务重载的调用数。

然后,API 管理会检查是否未在缓存中找到它。

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

如果 API 管理找不到它,API 管理将检索它。

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

从响应中提取响应正文文本。

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

将其存储回缓存中以供将来使用。

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

最后,更新后端 URL 以选择客户端所需的服务版本。

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

完整策略如下所示:

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

此优雅的解决方案解决了许多 API 版本控制问题,使 API 使用者能够透明地控制客户端访问的后端版本,而无需更新和重新部署其客户端。

租户隔离

在更大的多租户部署中,某些公司会在后端硬件的不同部署上创建单独的租户组。 此结构可最大程度地减少后端存在硬件问题时受影响的客户数。 它还允许分阶段推出新的软件版本。 理想情况下,此后端体系结构应对 API 使用者透明。 可以采用类似透明版本控制的技术,通过每个 API 密钥的配置状态来操控后端 URL,以实现这种透明度。

不会为每个订阅密钥返回首选版本的 API,而是返回一个标识符,该标识符将租户与分配的硬件组相关联。 该标识符可用于构造相应的后端 URL。

概要

使用 Azure API 管理缓存存储任何类型的数据可以有效地访问可能影响入站请求处理方式的配置数据。 它还可用于存储数据片段,这些片段可以扩充从后端 API 返回的响应。