다음을 통해 공유


Azure API Management의 사용자 지정 캐싱

적용 대상: 모든 API Management 계층

Azure API Management 서비스는 리소스 URL을 키로 사용하여 HTTP 응답 캐싱을 기본적으로 지원합니다. vary-by 속성을 사용하는 요청 헤더를 사용하여 키를 수정할 수 있습니다. 이 기술은 전체 HTTP 응답(표현이라고도 함)을 캐싱하는 데 유용하지만 표현의 일부만 캐시하는 것이 유용한 경우도 있습니다. cache-lookup-valuecache-store-value 정책을 사용하면 정책 정의 내에서 임의의 데이터를 저장하고 검색할 수 있습니다. 이 기능은 외부 서비스의 응답을 캐시할 수 있으므로 송신 요청 정책에도 값을 추가합니다.

아키텍처

API Management 서비스는 테넌트별 공유 내부 데이터 캐시를 사용하므로 여러 단위로 확장할 때 동일한 캐시된 데이터에 계속 액세스할 수 있습니다. 그러나 다중 지역 배포를 사용하는 경우 각 지역 내에 독립적인 캐시가 있습니다. 일부 정보의 유일한 원본인 데이터 저장소로 캐시를 처리하지 않는 것이 중요합니다. 나중에 다중 지역 배포를 활용하기로 결정한 경우 여행 중인 사용자가 있는 고객은 캐시된 데이터에 대한 액세스 권한을 잃을 수 있습니다.

비고

내부 캐시는 Azure API Management의 소비 계층에서 사용할 수 없습니다. 대신 외부 Redis 호환 캐시를 사용할 수 있습니다. 외부 캐시를 사용하면 모든 계층의 API Management 인스턴스에 대한 캐시 제어 및 유연성이 향상됩니다.

조각 캐싱

반환되는 응답에 결정 비용이 많이 드는 데이터의 일부가 포함된 경우가 있습니다. 그러나 데이터는 적절한 시간 동안 최신 상태로 유지되었습니다. 예를 들어 항공편 예약, 항공편 상태 등과 관련된 정보를 제공하는 항공사에서 빌드한 서비스를 고려해 보세요. 사용자가 항공사 포인트 프로그램의 구성원인 경우 현재 상태 및 누적 마일리지와 관련된 정보도 갖게 됩니다. 이 사용자 관련 정보는 다른 시스템에 저장될 수 있지만 비행 상태 및 예약에 대해 반환된 응답에 포함하는 것이 좋습니다. 조각 캐싱이라는 프로세스를 사용하여 이 데이터를 포함할 수 있습니다. 사용자 관련 정보를 삽입할 위치를 나타내기 위해 일종의 토큰을 사용하여 원본 서버에서 기본 표현을 반환할 수 있습니다.

백 엔드 API에서 다음 JSON 응답을 고려합니다.

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

그리고 보조 리소스는 /userprofile/{userid} 다음과 같습니다.

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

포함할 적절한 사용자 정보를 확인하려면 API Management에서 최종 사용자가 누구인지 식별해야 합니다. 이 메커니즘은 구현에 따라 다릅니다. 다음 예제에서는 Subject 토큰의 JWT 클레임을 사용합니다.

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

API Management는 enduserid 나중에 사용할 수 있는 컨텍스트 변수에 값을 저장합니다. 다음 단계는 이전 요청이 이미 사용자 정보를 검색하여 캐시에 저장했는지 확인하는 것입니다. 이를 위해 API Management는 cache-lookup-value 정책을 사용합니다.

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

비고

캐시 조회 후에 추가 된 속도 제한 정책은 캐시를 사용할 수 없는 경우 백 엔드 서비스의 오버로드를 방지하기 위해 호출 수를 제한하는 데 도움이 됩니다.

캐시에 키 값에 해당하는 항목이 없으면 컨텍스트 변수가 만들어지지 않습니다 userprofile . API Management는 제어 흐름 정책을 사용하여 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 Management가 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 Management는 이 enduserid URL을 사용하여 사용자 프로필 리소스에 대한 URL을 생성합니다. API Management에 응답이 있으면 응답에서 본문 텍스트를 끌어와 컨텍스트 변수에 다시 저장합니다.

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

동일한 사용자가 다른 요청을 할 때 API Management가 이 HTTP 요청을 다시 수행하지 않도록 하려면 사용자 프로필이 캐시에 저장되도록 지정할 수 있습니다.

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

API Management는 API Management에서 원래 검색하려고 시도한 것과 동일한 키를 사용하여 캐시에 값을 저장합니다. API Management에서 값을 저장하도록 선택하는 기간은 정보가 변경되는 빈도와 사용자가 오래된 정보에 대해 관대한 방법을 기반으로 해야 합니다.

캐시에서 정보를 검색하는 것은 여전히 프로세스 외부 네트워크 요청으로, 요청에 수십 밀리초의 지연을 추가할 수 있다는 것을 알고 있어야 합니다. 이점은 데이터베이스 쿼리가 필요하거나 여러 백 엔드에서 정보를 집계해야 하기 때문에 캐시에서 정보를 검색하는 것보다 사용자 프로필 정보를 확인하는 데 시간이 오래 걸리는 경우에 발생합니다.

프로세스의 마지막 단계는 반환된 응답을 사용자 프로필 정보로 업데이트하는 것입니다.

<!-- 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이 단일 페이지로 렌더링될 수 있도록 서버 쪽에서 구성되는 웹 사이트에서 주로 사용됩니다. 클라이언트가 클라이언트 쪽 HTTP 캐싱을 수행할 수 없거나 클라이언트에 해당 책임을 맡기지 않는 것이 바람직한 API에서도 유용할 수 있습니다.

Redis 캐싱 서버를 사용하여 백 엔드 웹 서버에서도 이와 동일한 종류의 조각 캐싱을 수행할 수 있습니다. 그러나 API Management 서비스를 사용하여 이 작업을 수행하는 것은 캐시된 조각이 기본 응답과 다른 백 엔드에서 오는 경우에 유용합니다.

투명한 버전 관리

API의 여러 구현 버전이 한 번에 지원되는 것이 일반적입니다. 예를 들어 다른 환경(개발, 테스트, 프로덕션 등)을 지원하거나 이전 버전의 API를 지원하여 API 소비자가 최신 버전으로 마이그레이션할 시간을 제공합니다.

클라이언트 개발자가 URL /v1/customers/v2/customers을 변경하도록 요구하는 대신 여러 버전을 처리하는 한 가지 방법은 현재 사용하려는 API 버전을 소비자의 프로필 데이터에 저장하고 적절한 백 엔드 URL을 호출하는 것입니다. 특정 클라이언트를 호출할 올바른 백 엔드 URL을 확인하려면 일부 구성 데이터를 쿼리해야 합니다. 이 구성 데이터를 캐시하면, API Management는 조회로 인한 성능 저하를 최소화할 수 있습니다.

첫 번째 단계는 원하는 버전을 구성하는 데 사용되는 식별자를 확인하는 것입니다. 이 예제에서는 버전을 제품 구독 키에 연결합니다.

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

그런 다음 API Management는 캐시 조회를 수행하여 원하는 클라이언트 버전을 이미 검색했는지 확인합니다.

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

비고

캐시 조회 후에 추가 된 속도 제한 정책은 캐시를 사용할 수 없는 경우 백 엔드 서비스의 오버로드를 방지하기 위해 호출 수를 제한하는 데 도움이 됩니다.

그런 다음, API Management는 캐시에서 찾지 못했는지 확인합니다.

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

API Management에서 찾지 못한 경우 API Management에서 검색합니다.

<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에서 반환된 응답을 보강할 수 있는 데이터 조각을 저장하는 데 사용할 수도 있습니다.