Поделиться через


Пользовательское кэширование в службе "Управление API Azure"

ПРИМЕНЯЕТСЯ КО ВСЕМ уровням управления API

Служба управления API Azure имеет встроенную поддержку кэширования http-ответа с помощью URL-адреса ресурса в качестве ключа. Ключ можно изменить с помощью заголовков запросов, использующих vary-by свойства. Этот метод полезен для кэширования всего HTTP-ответа (также известного как представления), но иногда полезно просто кэшировать часть представления. Политики кэш-lookup и кэш-store позволяют хранить и извлекать произвольные данные в пределах определений политики. Эта возможность также добавляет значение в политику отправки запросов , так как можно кэшировать ответы из внешних служб.

Architecture

Служба управления API использует общий внутренний кэш данных для каждого клиента, чтобы по мере масштабирования до нескольких единиц доступ к тем же кэшируемым данным по-прежнему был предоставлен. Однако при работе с многорегиональной конфигурацией в каждом регионе существуют независимые кэши. Важно не рассматривать кэш как хранилище данных, где это единственный источник информации. Если вы это сделали и позже решили воспользоваться преимуществами развертывания в нескольких регионах, клиенты с пользователями, которые путешествуют, могут потерять доступ к этим закэшированным данным.

Замечание

Внутренний кэш недоступен на уровне потребления службы "Управление API Azure". Вместо этого можно использовать внешний кэш, совместимый с Redis . Внешний кэш обеспечивает более широкий контроль над кэшем и гибкость для экземпляров API Management на всех уровнях.

Кэширование фрагментов

Существуют некоторые случаи, когда возвращаемые ответы содержат некоторые части данных, которые требуется определить. Тем не менее, данные остаются свежими в течение разумного периода времени. Например, рассмотрим службу, созданную авиакомпанией, которая предоставляет информацию о резервированиях рейсов, состоянии полета и т. д. Если пользователь является членом программы точек авиакомпаний, он также будет иметь информацию, связанную с их текущим состоянием и накопленным пробегом. Эти сведения, связанные с пользователем, могут храниться в другой системе, но может быть желательно включить его в ответы, возвращаемые о состоянии полета и резервированиях. Эти данные можно включить с помощью процесса, называемого кэшированием фрагментов. Основное представление можно вернуть с исходного сервера с помощью какого-либо маркера, чтобы указать, где должна быть вставлена информация, связанная с пользователем.

Рассмотрим следующий ответ JSON из серверного API.

{
  "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.azurewebsites.net/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>())" />

Чтобы избежать повторного выполнения этого HTTP-запроса, если тот же пользователь выполняет другой запрос, можно указать, что профиль пользователя хранится в кэше.

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

Управление API сохраняет значение в кэше, используя тот же ключ, с которым управление 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.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>

Этот подход кэширования в основном используется на веб-сайтах, где HTML состоит на стороне сервера, чтобы его можно было отобразить как одну страницу. Это также может быть полезно в API, где клиенты не могут осуществлять кэширование HTTP на стороне клиента, или когда желательно не возлагать эту ответственность на клиента.

Этот же вид кэширования фрагментов также можно выполнить на внутренних веб-серверах с помощью сервера кэширования Redis. Однако использование службы управления API для выполнения этой работы полезно, если кэшированные фрагменты приходят из разных серверных частей, отличных от основных ответов.

Прозрачное управление версиями

Поддержка нескольких разных версий API одновременно — это распространенная практика. Например, для поддержки разных сред (разработки, тестирования, рабочей среды и т. д.) или для поддержки более старых версий API, чтобы предоставить пользователям API время для миграции на более новые версии.

Один из подходов к обработке нескольких версий, вместо необходимости для клиентских разработчиков изменять URL-адреса с /v1/customers на /v2/customers, заключается в хранении в данных профиля потребителя, какую версию API они в настоящее время хотят использовать, и вызове соответствующего URL-адреса бэкенда. Чтобы определить правильный внутренний URL-адрес для вызова определенного клиента, необходимо запросить некоторые данные конфигурации. При кэшировании этих данных конфигурации API Management может свести к минимуму потери производительности при выполнении этого поиска.

Первым шагом является определение идентификатора, используемого для настройки требуемой версии. В этом примере мы свяжите версию с ключом подписки продукта.

<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. Эту прозрачность можно достичь, используя технику, аналогичную прозрачному управлению версиями, манипулируя URL-адресом серверной части через состояние конфигурации для каждого ключа API.

Вместо возврата предпочтительной версии API для каждого ключа подписки вы вернете идентификатор, связанный с клиентом с назначенной группой оборудования. Этот идентификатор можно использовать для создания соответствующего внутреннего URL-адреса.

Сводка

Свобода использования кэша управления API Azure для хранения любых данных обеспечивает эффективный доступ к данным конфигурации, которые могут повлиять на способ обработки входящего запроса. Его также можно использовать для хранения фрагментов данных, которые могут расширять ответы, возвращаемые из серверного API.