Dela via


Anpassad cachelagring i Azure API Management

GÄLLER FÖR: Alla API Management-nivåer

Azure API Management-tjänsten har inbyggt stöd för CACHElagring av HTTP-svar med resurs-URL:en som nyckel. Du kan ändra nyckeln med hjälp av begärandehuvuden vary-by som använder egenskaperna. Den här tekniken är användbar för att cachelagra hela HTTP-svar (kallas även representationer), men ibland är det bra att bara cachelagra en del av en representation. Principerna cache-lookup-value och cache-store-value ger dig möjlighet att lagra och hämta godtyckliga datadelar från principdefinitioner. Den här möjligheten ger också ett mervärde till policyn skicka-begäran eftersom du kan cachelagra svar från externa tjänster.

Architecture

API Management-tjänsten använder en intern datacache per klientorganisation så att du fortfarande får åtkomst till samma cachelagrade data när du skalar upp till flera enheter. När du däremot arbetar med en distribution över flera regioner finns det oberoende cacheminnen i varje region. Det är viktigt att inte behandla cacheminnet som ett datalager, där det är den enda källan till viss information. Om du gjorde det och senare bestämde dig för att dra nytta av distributionen i flera regioner kan kunder med användare som reser förlora åtkomsten till dessa cachelagrade data.

Anmärkning

Den interna cachen är inte tillgänglig på förbrukningsnivån för Azure API Management. Du kan använda en extern Redis-kompatibel cache i stället. En extern cache möjliggör större cachekontroll och flexibilitet för API Management-instanser på alla nivåer.

Cachelagring av fragment

Det finns vissa fall där svaren som returneras innehåller en del data som är dyra att avgöra. Men data förblir färska under en rimlig tid. Tänk dig till exempel en tjänst som skapats av ett flygbolag som tillhandahåller information om flygreservationer, flygstatus och så vidare. Om användaren är medlem i flygbolagens poängprogram skulle de också ha information om sin aktuella status och ackumulerade körsträcka. Den här användarrelaterade informationen kan lagras i ett annat system, men det kan vara önskvärt att inkludera den i svar som returneras om flygstatus och reservationer. Du kan inkludera dessa data genom att använda en process som kallas fragmentcachning. Den primära representationen kan returneras från ursprungsservern med någon form av token för att ange var den användarrelaterade informationen ska infogas.

Överväg följande JSON-svar från ett serverdels-API.

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

Och en sekundär resurs som /userprofile/{userid} ser ut så här:

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

För att fastställa lämplig användarinformation som ska inkluderas måste API Management identifiera vem slutanvändaren är. Den här mekanismen är implementeringsberoende. I följande exempel används Subject-kravet för ett JWT-token.

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

API Management lagrar enduserid värdet i en kontextvariabel för senare användning. Nästa steg är att avgöra om en tidigare begäran redan har hämtat användarinformationen och lagrat den i cacheminnet. För detta använder API Management cache-lookup-value policyn.

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

Anmärkning

Den hastighetsbegränsningsprincip som lagts till efter cachesökningen hjälper till att begränsa antalet anrop för att förhindra överlagring på serverdelstjänsten om cachen inte är tillgänglig.

Om det inte finns någon post i cacheminnet som motsvarar nyckelvärdet skapas ingen userprofile kontextvariabel. API Management kontrollerar om sökningen lyckades med hjälp av choose kontrollflödesprincipen.

<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 Om kontextvariabeln inte finns måste API Management göra en HTTP-begäran för att hämta den.

<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 Management använder enduserid för att konstruera URL:en till användarprofilresursen. När API Management har svaret hämtar det brödtexten ur svaret och lagrar den tillbaka till en kontextvariabel.

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

Om du vill undvika att API Management gör den här HTTP-begäran igen när samma användare gör en annan begäran kan du ange att användarprofilen lagras i cacheminnet.

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

API Management lagrar värdet i cacheminnet med samma nyckel som API Management ursprungligen försökte hämta det med. Den varaktighet som API Management väljer att lagra värdet ska baseras på hur ofta informationen ändras och hur toleranta användare är för inaktuell information.

Det är viktigt att inse att hämta information från cacheminnet fortfarande är en nätverksbegäran som sker utanför processen och potentiellt kan fördröja begäran med tiotals millisekunder. Fördelarna kommer när det tar längre tid att fastställa användarprofilinformationen än att hämta information från cacheminnet på grund av behovet av databasfrågor eller aggregeringsinformation från flera serverdelar.

Det sista steget i processen är att uppdatera det returnerade svaret med användarprofilinformationen.

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

Du kan välja att inkludera citattecknen som en del av token så att även om ersättningen inte sker är svaret fortfarande giltigt JSON.

När du har kombinerat de här stegen är slutresultatet en princip som ser ut som följande.

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

Den här cachelagringsmetoden används främst på webbplatser där HTML är sammansatt på serversidan så att den kan återges som en enda sida. Det kan också vara användbart i API:er där klienter inte kan göra HTTP-cachelagring på klientsidan eller det är önskvärt att inte lägga det ansvaret på klienten.

Samma typ av fragmentcachelagring kan också göras på webbservrarna med hjälp av en Redis-cache. Att använda API Management-tjänsten för att utföra det här arbetet är dock användbart när de cachelagrade fragmenten kommer från olika serverdelar än de primära svaren.

Transparent versionshantering

Det är vanligt att flera olika implementeringsversioner av ett API stöds samtidigt. Till exempel för att stödja olika miljöer (utveckling, testning, produktion osv.) eller för att stödja äldre versioner av API:et för att ge API-konsumenter tid att migrera till nyare versioner.

En metod för att hantera flera versioner, i stället för att kräva att klientutvecklare ändrar URL:er från /v1/customers till /v2/customers, är att lagra i konsumentens profildata vilken version av API:et som de för närvarande vill använda och anropa lämplig serverdels-URL. För att fastställa rätt backend-URL för en specifik klient, är det nödvändigt att hämta vissa konfigurationsdata. När du cachelagrar dessa konfigurationsdata kan API Management minimera prestandakostnaden vid denna sökning.

Det första steget är att fastställa den identifierare som används för att konfigurera den önskade versionen. I det här exemplet associerar vi versionen med produktprenumerationsnyckeln.

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

API Management gör sedan en cachesökning för att se om den redan har hämtat den önskade klientversionen.

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

Anmärkning

Den hastighetsbegränsningsprincip som lagts till efter cachesökningen hjälper till att begränsa antalet anrop för att förhindra överlagring på serverdelstjänsten om cachen inte är tillgänglig.

Sedan kontrollerar API Management om den inte hittade den i cacheminnet.

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

Om API Management inte hittade det hämtar API Management det.

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

Extrahera svarstexten från svaret.

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

Lagra den i cacheminnet för framtida användning.

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

Och uppdatera slutligen serverdels-URL:en för att välja den version av tjänsten som klienten vill ha.

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

Den fullständiga principen är följande:

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

Den här eleganta lösningen löser många problem med API-versionshantering genom att göra det möjligt för API-konsumenter att transparent kontrollera vilken serverdelsversion som deras klienter har åtkomst till utan att behöva uppdatera och distribuera om sina klienter.

Hyresgästisolering

I större distributioner med flera klienter skapar vissa företag separata grupper av klienter på distinkta distributioner av serverdelsmaskinvara. Den här strukturen minimerar antalet kunder som påverkas när det finns maskinvaruproblem på serverdelen. Det gör också att nya programvaruversioner kan distribueras i etapper. Den här serverdelsarkitekturen bör helst vara transparent för API-konsumenter. Du kan uppnå den här transparensen genom att använda en teknik som liknar transparent versionshantering och manipulera serverdels-URL:en med hjälp av konfigurationstillstånd per API-nyckel.

I stället för att returnera en önskad version av API:et för varje prenumerationsnyckel returnerar du en identifierare som relaterar en klientorganisation till den tilldelade maskinvarugruppen. Den identifieraren kan användas för att konstruera lämplig serverdels-URL.

Sammanfattning

Friheten att använda Azure API-hanteringscache för att lagra alla typer av data ger effektiv åtkomst till konfigurationsdata som kan påverka hur en inkommande begäran bearbetas. Den kan också användas för att lagra datafragment som kan utöka svar som returneras från ett serverdels-API.