Freigeben über


Benutzerdefinierte Zwischenspeicherung in Azure API Management

GILT FÜR: Alle API Management-Ebenen

Der Azure API-Verwaltungsdienst verfügt über integrierte Unterstützung für die HTTP-Antwortzwischenspeicherung mithilfe der Ressourcen-URL als Schlüssel. Sie können den Schlüssel mithilfe von Anforderungsheadern ändern, die die vary-by Eigenschaften verwenden. Diese Technik ist nützlich, um ganze HTTP-Antworten (auch als Darstellungen bezeichnet) zwischenzuspeichern, aber manchmal ist es nützlich, nur einen Teil einer Darstellung zwischenzuspeichern. Die Richtlinien für cache-lookup-value und cache-store-value bieten Ihnen die Möglichkeit, beliebige Datenelemente aus Richtliniendefinitionen zu speichern und abzurufen. Durch diese Möglichkeit wird auch der Send-Request-Richtlinie ein Mehrwert hinzugefügt, da Sie Antworten von externen Diensten zwischenspeichern können.

Architektur

Der API-Verwaltungsdienst verwendet einen freigegebenen internen Datencache pro Mandant, sodass Sie beim Skalieren auf mehrere Einheiten weiterhin Zugriff auf dieselben zwischengespeicherten Daten erhalten. Wenn Sie jedoch mit einer Bereitstellung arbeiten, die mehrere Regionen umfasst, gibt es in allen Regionen unabhängige Caches. Es ist wichtig, den Cache nicht als Datenspeicher zu behandeln, bei dem es sich um die einzige Informationsquelle handelt. Wenn Sie dies getan haben und später beschlossen haben, die Bereitstellung mit mehreren Regionen zu nutzen, verlieren Kunden mit Benutzern, die reisen, möglicherweise den Zugriff auf diese zwischengespeicherten Daten.

Hinweis

Der interne Cache ist in der Nutzungsebene von Azure API Management nicht verfügbar. Sie können stattdessen einen externen Redis-kompatiblen Cache verwenden . Ein externer Cache ermöglicht eine größere Cachesteuerung und Flexibilität für API-Verwaltungsinstanzen in allen Ebenen.

Fragmentzwischenspeicherung

Es gibt bestimmte Fälle, in denen die zurückgegebenen Antworten einige Daten enthalten, die teuer zu bestimmen sind. Dennoch bleiben die Daten für eine angemessene Zeit frisch. Ziehen Sie beispielsweise einen Von einer Fluggesellschaft erstellten Dienst in Betracht, der Informationen zu Flugreservierungen, Flugstatus usw. bereitstellt. Wenn der Benutzer Mitglied des Fluglinienpunkteprogramms ist, hätte er auch Informationen über seinen aktuellen Status und die akkumulierte Kilometerzahl. Diese benutzerbezogenen Informationen können in einem anderen System gespeichert werden, aber es könnte wünschenswert sein, sie in Antworten, die über den Flugstatus und Reservierungen zurückgegeben werden, einzuschließen. Sie können diese Daten mithilfe eines Prozesses einschließen, der als Fragmentzwischenspeicherung bezeichnet wird. Die primäre Darstellung kann vom Ursprungsserver mithilfe einer Art von Token zurückgegeben werden, um anzugeben, wo die benutzerbezogenen Informationen eingefügt werden sollen.

Berücksichtigen Sie die folgende JSON-Antwort von einer Back-End-API.

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

Und eine sekundäre Ressource an der Stelle /userprofile/{userid}, die folgendermaßen aussieht:

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

Um die erforderlichen Benutzerinformationen zu ermitteln, muss die API-Verwaltung ermitteln, wer der Endbenutzer ist. Dieser Mechanismus ist implementierungsabhängig. Das folgende Beispiel verwendet den Subject-Anspruch eines JWT-Tokens.

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

Die API-Verwaltung speichert den enduserid Wert in einer Kontextvariable für die spätere Verwendung. Der nächste Schritt besteht darin, zu ermitteln, ob eine vorherige Anforderung die Benutzerinformationen bereits abgerufen und im Cache gespeichert hat. Hierfür verwendet die API-Verwaltung die cache-lookup-value Richtlinie.

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

Hinweis

Fügen Sie nach der Cache-Suche eine Rate-Limit-Policy (oder Rate-Limit-by-Key-Policy ) hinzu, um die Anzahl der Anrufe zu begrenzen und Überlastung des Backend-Services zu vermeiden, falls der Cache nicht verfügbar ist.

Wenn kein Eintrag im Cache vorhanden ist, der dem Schlüsselwert entspricht, wird keine userprofile Kontextvariable erstellt. Die API-Verwaltung überprüft den Erfolg des Nachschlagevorgangs mithilfe der choose Steuerungsflussrichtlinie.

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

Wenn die userprofile Kontextvariable nicht vorhanden ist, muss die API-Verwaltung eine HTTP-Anforderung zum Abrufen vornehmen.

<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-Verwaltung verwendet das enduserid, um die URL zur Benutzerprofilressource zu konstruieren. Sobald die API-Verwaltung über die Antwort verfügt, ruft sie den Textkörper aus der Antwort ab und speichert ihn wieder in einer Kontextvariable.

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

Um zu vermeiden, dass die API-Verwaltung diese HTTP-Anforderung erneut durchführt, wenn derselbe Benutzer eine weitere Anforderung vornimmt, können Sie angeben, dass das Benutzerprofil im Cache gespeichert ist.

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

Die API-Verwaltung speichert den Wert im Cache unter Verwendung desselben Schlüssels, mit dem die API-Verwaltung ursprünglich versucht hat, ihn abzurufen. Die Dauer, die das API-Management zum Speichern des Werts auswählt, sollte darauf basieren, wie oft sich die Informationen ändern und wie tolerant Benutzer gegenüber veralteten Informationen sind.

Es ist wichtig zu erkennen, dass das Abrufen von Informationen aus dem Cache immer noch eine out-of-process-Netzwerkanforderung ist und der Anforderung potenziell Mehrere Millisekunden hinzufügen kann. Die Vorteile zeigen sich, wenn die Ermittlung der Benutzerprofilinformationen länger dauert als das Abrufen von Informationen aus dem Cache, aufgrund der Notwendigkeit von Datenbankabfragen oder dem Aggregieren von Informationen aus mehreren Back-Ends.

Der letzte Schritt im Prozess besteht darin, die zurückgegebene Antwort mit den Benutzerprofilinformationen zu aktualisieren.

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

Sie können die Anführungszeichen als Teil des Tokens einschließen, damit die Antwort selbst dann noch gültiges JSON ist, wenn die Ersetzung nicht erfolgt.

Nachdem Sie diese Schritte kombiniert haben, ist das Endergebnis eine Richtlinie, die wie folgt aussieht.

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

Dieser Zwischenspeicherungsansatz wird in erster Linie auf Websites verwendet, auf denen HTML auf der Serverseite verfasst wird, sodass er als einzelne Seite gerendert werden kann. Sie kann auch bei APIs hilfreich sein, bei denen Clients keine clientseitige HTTP-Zwischenspeicherung durchführen können oder es wünschenswert ist, diese Verantwortung nicht auf den Client zu setzen.

Diese Art von Fragmentzwischenspeicherung kann auch auf den Back-End-Webservern mithilfe eines Redis-Cacheservers erfolgen. Die Verwendung des API-Verwaltungsdiensts zum Ausführen dieser Arbeit ist jedoch nützlich, wenn die zwischengespeicherten Fragmente aus unterschiedlichen Back-Ends stammen als die primären Antworten.

Transparente Versionsverwaltung

Es ist üblich, dass mehrere verschiedene Implementierungsversionen einer API jederzeit unterstützt werden. Um z. B. verschiedene Umgebungen (Entwicklungs-, Test-, Produktions- usw.) zu unterstützen oder ältere Versionen der API zu unterstützen, um API-Consumern Zeit für die Migration zu neueren Versionen zu geben.

Ein Ansatz zum Umgang mit mehreren Versionen, anstatt Cliententwicklern zu verlangen, die URLs von /v1/customers nach /v2/customers zu ändern, besteht darin, in den Profildaten des Nutzers zu speichern, welche Version der API sie derzeit verwenden möchten, und die entsprechende Back-End-URL aufzurufen. Um die richtige Back-End-URL zu ermitteln, die für einen bestimmten Client aufgerufen werden soll, ist es erforderlich, einige Konfigurationsdaten abzufragen. Wenn Sie diese Konfigurationsdaten zwischenspeichern, kann die API-Verwaltung die Leistungseinbußen bei diesem Nachschlagevorgang minimieren.

Der erste Schritt besteht darin, den Bezeichner zu bestimmen, der zum Konfigurieren der gewünschten Version verwendet wird. In diesem Beispiel ordnen wir die Version dem Product Subscription Key zu.

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

Die API-Verwaltung führt dann eine Cachesuche durch, um festzustellen, ob sie bereits die gewünschte Clientversion abgerufen hat.

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

Hinweis

Fügen Sie nach der Cache-Suche eine Rate-Limit-Policy (oder Rate-Limit-by-Key-Policy ) hinzu, um die Anzahl der Anrufe zu begrenzen und Überlastung des Backend-Services zu vermeiden, falls der Cache nicht verfügbar ist.

Anschließend überprüft die API-Verwaltung, ob sie im Cache nicht gefunden wurde.

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

Wenn die API-Verwaltung sie nicht gefunden hat, ruft die API-Verwaltung sie ab.

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

Extrahieren Sie den Antworttexttext aus der Antwort.

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

Speichern Sie sie wieder im Cache für die zukünftige Verwendung.

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

Aktualisieren Sie schließlich die Back-End-URL, um die vom Client gewünschte Version des Diensts auszuwählen.

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

Die vollständige Richtlinie lautet wie folgt:

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

Diese elegante Lösung behebt viele Bedenken bei der API-Versionsverwaltung, indem API-Verbraucher transparent steuern können, auf welche Back-End-Version ihre Clients zugreifen, ohne ihre Clients aktualisieren und erneut bereitstellen zu müssen.

Mandantenisolation

In größeren Bereitstellungen mit mehreren Mandanten erstellen einige Unternehmen separate Mandantengruppen auf unterschiedlichen Bereitstellungen von Back-End-Hardware. Diese Struktur minimiert die Anzahl der Kunden, die betroffen sind, wenn ein Hardwareproblem im Back-End aufgetreten ist. Außerdem können neue Softwareversionen in Phasen eingeführt werden. Idealerweise sollte diese Back-End-Architektur für API-Verbraucher transparent sein. Sie können diese Transparenz erreichen, indem Sie eine Technik verwenden, die der transparenten Versionsverwaltung ähnelt und die Back-End-URL mithilfe des Konfigurationsstatus pro API-Schlüssel manipuliert.

Anstatt eine bevorzugte Version der API für jeden Abonnementschlüssel zurückzugeben, würden Sie einen Bezeichner zurückgeben, der einen Mandanten mit der zugewiesenen Hardwaregruppe verknüpft. Dieser Bezeichner kann verwendet werden, um die entsprechende Back-End-URL zu erstellen.

Zusammenfassung

Die Freiheit, den Azure API-Verwaltungscache zum Speichern beliebiger Daten zu verwenden, ermöglicht einen effizienten Zugriff auf Konfigurationsdaten, die sich auf die Verarbeitung einer eingehenden Anforderung auswirken können. Sie kann auch verwendet werden, um Datenfragmente zu speichern, die Antworten erweitern können, die von einer Back-End-API zurückgegeben werden.