Buforowanie niestandardowe w usłudze Azure API Management

DOTYCZY: Wszystkie warstwy usługi API Management

Usługa Azure API Management ma wbudowaną obsługę buforowania odpowiedzi HTTP przy użyciu adresu URL zasobu jako klucza. Klucz można modyfikować za pomocą nagłówków żądań przy użyciu vary-by właściwości . Jest to przydatne w przypadku buforowania całych odpowiedzi HTTP (nazywanych również reprezentacjami), ale czasami warto po prostu buforować część reprezentacji. Zasady cache-lookup-value i cache-store-value zapewniają możliwość przechowywania i pobierania dowolnych fragmentów danych z definicji zasad. Ta możliwość dodaje również wartość do zasad wysyłania żądań , ponieważ można buforować odpowiedzi z usług zewnętrznych.

Architektura

Usługa API Management używa udostępnionej wewnętrznej pamięci podręcznej danych dla dzierżawy, dzięki czemu podczas skalowania w górę do wielu jednostek nadal uzyskujesz dostęp do tych samych danych w pamięci podręcznej. Jednak podczas pracy z wdrożeniem w wielu regionach istnieją niezależne pamięci podręczne w każdym z regionów. Ważne jest, aby nie traktować pamięci podręcznej jako magazynu danych, gdzie jest jedynym źródłem niektórych informacji. Jeśli tak, a później zdecydujesz się skorzystać z wdrożenia w wielu regionach, klienci z użytkownikami, którzy podróżują, mogą utracić dostęp do tych buforowanych danych.

Uwaga

Wewnętrzna pamięć podręczna nie jest dostępna w warstwie Zużycie usługi Azure API Management. Zamiast tego można użyć zewnętrznej pamięci podręcznej Azure Cache for Redis. Zewnętrzna pamięć podręczna umożliwia większą kontrolę i elastyczność obsługi wystąpień usługi API Management we wszystkich warstwach.

Buforowanie fragmentów

Istnieją pewne przypadki, w których zwracane odpowiedzi zawierają pewną część danych, która jest kosztowna do określenia, a jednak pozostaje świeża przez rozsądny czas. Rozważmy na przykład usługę utworzoną przez linię lotniczą, która dostarcza informacje dotyczące rezerwacji lotów, stanu lotu itd. Jeśli użytkownik jest członkiem programu punktów lotniczych, będzie miał również informacje dotyczące ich bieżącego statusu i skumulowanego przebiegu. Te informacje związane z użytkownikiem mogą być przechowywane w innym systemie, ale może być pożądane, aby uwzględnić je w odpowiedziach zwróconych na temat stanu lotu i rezerwacji. Można to zrobić przy użyciu procesu nazywanego buforowaniem fragmentów. Podstawowa reprezentacja może zostać zwrócona z serwera pochodzenia przy użyciu jakiegoś tokenu, aby wskazać, gdzie mają zostać wstawione informacje dotyczące użytkownika.

Rozważmy następującą odpowiedź JSON z interfejsu API zaplecza.

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

A w tym przypadku zasób /userprofile/{userid} pomocniczy wygląda następująco:

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

Aby określić odpowiednie informacje o użytkowniku do uwzględnienia, usługa API Management musi określić, kim jest użytkownik końcowy. Ten mechanizm jest zależny od implementacji. W poniższym przykładzie użyto Subject oświadczenia tokenu JWT .

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

Usługa API Management przechowuje enduserid wartość w zmiennej kontekstowej do późniejszego użycia. Następnym krokiem jest ustalenie, czy poprzednie żądanie pobrało już informacje o użytkowniku i zapisano je w pamięci podręcznej. W tym celu usługa API Management używa cache-lookup-value zasad.

<cache-lookup-value
key="@("userprofile-" + context.Variables["enduserid"])"
variable-name="userprofile" />

Jeśli w pamięci podręcznej nie ma wpisu odpowiadającego wartości klucza, nie userprofile zostanie utworzona żadna zmienna kontekstowa. Usługa API Management sprawdza powodzenie wyszukiwania przy użyciu choose zasad przepływu sterowania.

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

Jeśli zmienna userprofile kontekstowa nie istnieje, usługa API Management będzie musiała wysłać żądanie HTTP, aby go pobrać.

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

Usługa API Management używa elementu enduserid , aby utworzyć adres URL zasobu profilu użytkownika. Gdy usługa API Management ma odpowiedź, pobiera tekst treści z odpowiedzi i zapisuje go z powrotem w zmiennej kontekstowej.

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

Aby uniknąć ponownego tworzenia tego żądania HTTP przez usługę API Management, gdy ten sam użytkownik wysyła inne żądanie, możesz określić, czy profil użytkownika ma być przechowywany w pamięci podręcznej.

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

Usługa API Management przechowuje wartość w pamięci podręcznej przy użyciu tego samego klucza, za pomocą którego usługa API Management pierwotnie próbowała go pobrać. Czas trwania, przez który usługa API Management decyduje się przechowywać wartość, powinien być oparty na tym, jak często zmieniają się informacje i jak tolerancyjny użytkownicy mają nieaktualne informacje.

Ważne jest, aby pamiętać, że pobieranie z pamięci podręcznej jest nadal żądaniem sieci poza procesem i potencjalnie może dodać dziesiątki milisekund do żądania. Korzyści wynikające z określania informacji o profilu użytkownika trwa dłużej niż z powodu konieczności wykonywania zapytań bazy danych lub agregowania informacji z wielu zapleczy.

Ostatnim krokiem procesu jest zaktualizowanie zwróconej odpowiedzi przy użyciu informacji o profilu użytkownika.

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

Możesz dołączyć znaki cudzysłowu jako część tokenu, aby nawet wtedy, gdy zamiana nie nastąpi, odpowiedź jest nadal prawidłowym kodem JSON.

Po połączeniu tych kroków wynik końcowy jest zasadą, która wygląda podobnie do poniższej.

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

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

Takie podejście do buforowania jest stosowane głównie w witrynach internetowych, w których kod HTML jest skomponowany po stronie serwera, dzięki czemu może być renderowany jako pojedyncza strona. Może to być również przydatne w interfejsach API, w których klienci nie mogą wykonywać buforowania HTTP po stronie klienta lub pożądane jest, aby nie ponosić tej odpowiedzialności za klienta.

Tego samego rodzaju buforowanie fragmentów można również wykonać na serwerach internetowych zaplecza przy użyciu serwera buforowania Redis, jednak użycie usługi API Management do wykonania tej pracy jest przydatne, gdy buforowane fragmenty pochodzą z różnych zaplecza niż odpowiedzi podstawowe.

Przezroczyste przechowywanie wersji

Częstą praktyką jest obsługa wielu różnych wersji implementacji interfejsu API w dowolnym momencie. Na przykład w celu obsługi różnych środowisk (deweloperskich, testowych, produkcyjnych itp.) lub obsługi starszych wersji interfejsu API w celu zapewnienia użytkownikom interfejsu API czasu na migrację do nowszych wersji.

Jednym z podejść do obsługi tego rozwiązania, zamiast wymagać od deweloperów klientów zmiany adresów URL z /v1/customers na /v2/customers jest przechowywanie w danych profilu użytkownika, których wersji interfejsu API aktualnie chcą używać, i wywoływanie odpowiedniego adresu URL zaplecza. Aby określić prawidłowy adres URL zaplecza w celu wywołania określonego klienta, należy wykonać zapytanie dotyczące niektórych danych konfiguracji. Buforując te dane konfiguracji, usługa API Management może zminimalizować karę wydajności wykonywania tego wyszukiwania.

Pierwszym krokiem jest określenie identyfikatora użytego do skonfigurowania żądanej wersji. W tym przykładzie wybrano skojarzenie wersji z kluczem subskrypcji produktu.

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

Usługa API Management wykonuje wyszukiwanie w pamięci podręcznej, aby sprawdzić, czy pobrała już żądaną wersję klienta.

<cache-lookup-value
key="@("clientversion-" + context.Variables["clientid"])"
variable-name="clientversion" />

Następnie usługa API Management sprawdza, czy nie znalazła go w pamięci podręcznej.

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

Jeśli usługa API Management nie znalazła go, usługa API Management pobierze ją.

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

Wyodrębnij tekst treści odpowiedzi z odpowiedzi.

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

Zapisz go z powrotem w pamięci podręcznej do użytku w przyszłości.

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

Na koniec zaktualizuj adres URL zaplecza, aby wybrać wersję usługi żądanej przez klienta.

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

Pełne zasady są następujące:

<inbound>
    <base />
    <set-variable name="clientid" value="@(context.Subscription.Key)" />
    <cache-lookup-value key="@("clientversion-" + context.Variables["clientid"])" variable-name="clientversion" />

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

Umożliwienie użytkownikom interfejsu API przezroczystego kontrolowania, do której wersji zaplecza uzyskuje dostęp klienci bez konieczności aktualizowania i ponownego wdrażania klientów, jest eleganckim rozwiązaniem, które zajmuje się wieloma problemami dotyczącymi obsługi wersji interfejsu API.

Izolacja dzierżawy

W przypadku większych wdrożeń wielodostępnych niektóre firmy tworzą oddzielne grupy dzierżaw w różnych wdrożeniach sprzętu zaplecza. Minimalizuje to liczbę klientów, którzy mają wpływ na problem ze sprzętem w zapleczu. Umożliwia również wdrażanie nowych wersji oprogramowania na etapach. W idealnym przypadku ta architektura zaplecza powinna być niewidoczna dla użytkowników interfejsu API. Można to osiągnąć w podobny sposób do przezroczystego przechowywania wersji, ponieważ jest on oparty na tej samej technice manipulowania adresem URL zaplecza przy użyciu stanu konfiguracji na klucz interfejsu API.

Zamiast zwracać preferowaną wersję interfejsu API dla każdego klucza subskrypcji, należy zwrócić identyfikator, który wiąże dzierżawę z przypisaną grupą sprzętową. Ten identyfikator może służyć do konstruowania odpowiedniego adresu URL zaplecza.

Podsumowanie

Swoboda używania pamięci podręcznej usługi Azure API Management do przechowywania dowolnego rodzaju danych umożliwia wydajny dostęp do danych konfiguracji, które mogą mieć wpływ na sposób przetwarzania żądania przychodzącego. Może również służyć do przechowywania fragmentów danych, które mogą rozszerzać odpowiedzi, zwracane z interfejsu API zaplecza.