ASP.NET Core에서 분산 캐시 사용하기

등록일시: 2018-05-21 08:00,  수정일시: 2018-05-31 12:23
조회수: 3,834
이 문서는 ASP.NET Core 기술을 널리 알리고자 하는 개인적인 취지로 제공되는 번역문서입니다. 이 문서에 대한 모든 저작권은 마이크로소프트에 있으며 요청이 있을 경우 언제라도 게시가 중단될 수 있습니다. 번역 내용에 오역이 존재할 수 있고 주석은 번역자 개인의 의견일 뿐이며 마이크로소프트는 이에 관한 어떠한 보장도 하지 않습니다. 번역이 완료된 이후에도 대상 제품 및 기술이 개선되거나 변경됨에 따라 원문의 내용도 변경되거나 보완되었을 수 있으므로 주의하시기 바랍니다.

분산 캐시는 ASP.NET Core 응용 프로그램이 특히 클라우드나 서버 팜 환경에서 호스팅 될 때 성능 및 확장성을 개선할 수 있습니다. 본문에서는 ASP.NET Core가 기본으로 제공하는 분산 캐시 추상화 및 구현의 사용 방법을 살펴봅니다.

예제 코드 살펴보기 및 다운로드 (다운로드 방법)

분산 캐시란

분산 캐시는 복수의 응용 프로그램 서버에서 공유됩니다 (캐시 기본 사항 참조). 캐시에 저장된 정보는 개별 웹 서버의 메모리에 저장되지 않으며, 모든 응용 프로그램 서버에서 캐시된 데이터를 사용할 수 있습니다. 이는 몇 가지 이점을 제공합니다:

  1. 캐시된 데이터는 모든 웹 서버에서 일관적입니다. 어떤 서버가 사용자의 요청을 처리하는지에 따라 다른 결과를 표시하거나 하지 않습니다.

  2. 캐시된 데이터는 웹 서버가 재시작되거나 배포된 후에도 유지됩니다. 캐시에 영향을 주지 않고 개별 웹 서버를 제거하거나 추가할 수 있습니다.

  3. 원본 데이터 저장소에 대한 요청 횟수가 줄어듭니다 (다수의 메모리 내 캐시를 사용하거나 캐시를 전혀 사용하지 않는 경우 보다).

노트

SQL Server 분산 캐시를 사용할 경우, 이런 장점 중 일부는 응용 프로그램의 원본 데이터와 다른 캐시 전용 데이터베이스 인스턴스를 사용하는 경우에만 적용됩니다.

다른 모든 캐시와 마찬가지로, 대부분 관계형 데이터베이스(또는 웹 서비스)에서 조회하는 것보다 캐시에서 데이터를 훨씬 빠르게 조회할 수 있기 때문에 분산 캐시도 응용 프로그램의 응답성을 획기적으로 향상시킵니다.

캐시의 구성은 구현에 따라 달라집니다. 본문에서는 Redis 및 SQL Server 분산 캐시를 구성하는 두 가지 방법을 모두 설명합니다. 어떤 구현을 선택하던지 응용 프로그램은 공통 IDistributedCache 인터페이스를 사용해서 캐시와 상호 작용합니다.

IDistributedCache 인터페이스

IDistributedCache 인터페이스는 동기 및 비동기 메서드를 포함하고 있습니다. 이 인터페이스를 사용해서 분산 캐시 구현에 항목을 추가하고, 검색하고, 제거할 수 있습니다. IDistributedCache 인터페이스는 다음과 같은 메서드를 포함하고 있습니다:

Get, GetAsync

문자열 키를 전달받아서 캐시에 항목이 존재할 경우 byte[]로 캐시 항목을 조회합니다.

Set, SetAsync

문자열 키를 사용해서 (byte[]로) 캐시에 항목을 추가합니다.

Refresh, RefreshAsync

키를 기반으로 캐시 항목을 새로 고치고, 캐시 항목의 슬라이딩 만료 시간 제한을 재설정합니다 (필요한 경우).

Remove, RemoveAsync

키를 기반으로 캐시 항목을 제거합니다.

IDistributedCache 인터페이스를 사용하려면:

  1. 프로젝트 파일에 필요한 NuGet 패키지를 추가합니다.

  2. Startup 클래스의 ConfigureServices 메서드에서 IDistributedCache의 특정 구현을 구성하고 컨테이너에 추가합니다.

  3. 응용 프로그램의 미들웨어나 MVC 컨트롤러 클래스의 생성자에서 IDistributedCache의 인스턴스를 요청합니다. 인스턴스는 종속성 주입(DI)을 통해서 제공됩니다.

노트

IDistributedCache 인스턴스를 Singleton이나 Scoped 수명으로 사용해야만 할 필요는 없습니다 (적어도 기본 구현에 대해서는). 필요할 때마다 인스턴스를 생성할 수도 있지만 (종속성 주입을 사용하는 대신), 그럴 경우 코드를 테스트하기 어려워지고 명시적 종속성 원칙을 위반하게 됩니다.

다음 예제는 간단한 미들웨어 구성 요소에서 IDistributedCache의 인스턴스를 사용하는 방법을 보여줍니다:

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Caching.Distributed;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace DistCacheSample
{
    public class StartTimeHeader
    {
        private readonly RequestDelegate _next;
        private readonly IDistributedCache _cache;

        public StartTimeHeader(RequestDelegate next,
           IDistributedCache cache)
        {
            _next = next;
            _cache = cache;
        }

        public async Task Invoke(HttpContext httpContext)
        {
            string startTimeString = "Not found.";
            var value = await _cache.GetAsync("lastServerStartTime");
            if (value != null)
            {
                startTimeString = Encoding.UTF8.GetString(value);
            }

            httpContext.Response.Headers.Append("Last-Server-Start-Time", startTimeString);

            await _next.Invoke(httpContext);
        }
    }


    // Extension method used to add the middleware to the HTTP request pipeline.
    public static class StartTimeHeaderExtensions
    {
        public static IApplicationBuilder UseStartTimeHeader(this IApplicationBuilder builder)
        {
            return builder.UseMiddleware<StartTimeHeader>();
        }
    }
}

위의 코드는 캐시된 값을 읽기만 하고 작성하지는 않습니다. 이 예제에서 값은 오직 서버가 시작될 때만 설정되고 변경되지 않습니다. 다중 서버 시나리오에서는 가장 최근에 시작된 서버가 다른 서버에 의해 설정된 기존의 모든 값을 덮어쓰게 됩니다. 그리고 GetSet 메서드는 byte[] 형식을 사용합니다. 따라서 문자열 값은 Encoding.UTF8.GetString (Get을 사용할 때) 또는 Encoding.UTF8.GetBytes를 (Set을 사용할 때) 사용해서 변환해야 합니다.

다음 코드는 Startup.cs에서 값이 설정되고 있는 부분을 보여줍니다:

public void Configure(IApplicationBuilder app,
    IDistributedCache cache)
{
    var serverStartTimeString = DateTime.Now.ToString();
    byte[] val = Encoding.UTF8.GetBytes(serverStartTimeString);
    var cacheEntryOptions = new DistributedCacheEntryOptions()
        .SetSlidingExpiration(TimeSpan.FromSeconds(30));
    cache.Set("lastServerStartTime", val, cacheEntryOptions);
노트

ConfigureServices 메서드에서 IDistributedCache가 구성되고 나면 이를 Configure 메서드에 매개 변수로 전달할 수 있습니다. 매개 변수로 추가하면 DI를 통해서 구성된 인스턴스가 제공됩니다.

Redis 분산 캐시 사용하기

Redis는 분산 캐시로 흔히 사용되는 오픈 소스 메모리 내 데이터 저장소입니다. 로컬에서 사용할 수도 있고 Azure에서 호스팅되는 응용 프로그램을 대상으로 Azure Redis Cache를 구성할 수도 있습니다. ASP.NET Core 응용 프로그램은 RedisDistributedCache의 인스턴스를 사용해서 캐시 구현을 구성합니다.

ConfigureServices에서 Redis 구현을 구성한 다음 응용 프로그램 코드에서 IDistributedCache의 인스턴스를 요청하여 이에 접근합니다 (위의 코드 참조).

예제 코드에서는 서버가 Staging 환경으로 구성될 때 RedisCache 구현이 사용됩니다. 따라서 ConfigureStagingServices 메서드에서 RedisCache를 구성합니다:

/// <summary>
/// Use Redis Cache in Staging
/// </summary>
/// <param name="services"></param>
public void ConfigureStagingServices(IServiceCollection services)
{
    services.AddDistributedRedisCache(options =>
    {
        options.Configuration = "localhost";
        options.InstanceName = "SampleInstance";
    });
}
노트

Redis를 로컬 컴퓨터에 설치하려면 chocolatey 패키지(https://chocolatey.org/packages/redis-64/)를 설치하고 명령 프롬프트에서 redis-server를 실행합니다.

SQL Server 분산 캐시 사용하기

SqlServerCache 구현을 사용하면 SQL Server 데이터베이스를 백업 저장소로 이용해서 분산 캐시를 사용할 수 있습니다. 지정한 이름 및 스키마로 테이블을 생성하는 도구인 sql-cache 도구를 이용해서 SQL Server 테이블을 생성할 수 있습니다.

sql-cache 캐시 도구를 사용하려면 .csproj 파일의 <ItemGroup> 요소에 SqlConfig.Tools를 추가하고 dotnet restore를 실행합니다.

<ItemGroup>
  <DotNetCliToolReference Include="Microsoft.Extensions.Caching.SqlConfig.Tools" Version="1.0.0-msbuild3-final" />
</ItemGroup>

다음 명령을 실행하면 SqlConfig.Tools를 테스트 할 수 있습니다.

C:\DistCacheSample\src\DistCacheSample>dotnet sql-cache create --help

그러면 sql-cache 도구가 사용 방법, 옵션 및 명령 도움말을 출력하며, 이제 "sql-cache create" 명령을 실행하여 Sql Server에 테이블을 생성할 수 있습니다:

C:\DistCacheSample\src\DistCacheSample>dotnet sql-cache create "Data Source=(localdb)\v11.0;Initial Catalog=DistCache;Integrated Security=True;" dbo TestCache
   info: Microsoft.Extensions.Caching.SqlConfig.Tools.Program[0]
       Table and index were created successfully.

생성된 테이블은 다음과 같은 스키마를 갖습니다:

SqlServer 캐시 테이블

다른 모든 캐시 구현과 마찬가지로, 응용 프로그램은 SqlServerCache가 아닌 IDistributedCache의 인스턴스를 사용해서 캐시 값을 읽고 설정해야 합니다. 예제에서는 Production 환경에서 SqlServerCache를 구현하고 있습니다 (그래서 ConfigureProductionServices 메서드에서 설정되어 있습니다).

/// Use SQL Server Cache in Production
/// </summary>
/// <param name="services"></param>
public void ConfigureProductionServices(IServiceCollection services)
{
    services.AddDistributedSqlServerCache(options =>
    {
        options.ConnectionString = @"Data Source=(localdb)\v11.0;Initial Catalog=DistCache;Integrated Security=True;";
        options.SchemaName = "dbo";
        options.TableName = "TestCache";
    });
}
노트

일반적으로 ConnectionString은 (그리고 필요한 경우 SchemaNameTableName은) 자격 증명이 포함되어 있을 수도 있기 때문에 소스 제어 외부에 (UserSecrets 같은) 저장되어야 합니다.

권장 사항

응용 프로그램에 적합한 IDistributedCache의 구현을 결정할 때 기존 인프라와 환경, 성능 요구 사항 및 팀의 경험을 감안해서 Redis 및 SQL Server 중 한 가지를 선택해야 합니다. Redis로 작업하는 것이 더 익숙한 팀이라면 Redis를 선택하는 것이 가장 좋습니다. 팀이 SQL Server를 선호한다면 해당 구현에도 역시 자신감을 가질 수 있습니다. 전통적인 캐싱 솔루션이 데이터를 빠르게 조회할 수 있는 이유는 데이터를 메모리에 저장하기 때문이라는 점에 주의하시기 바랍니다. 공통적으로 사용되는 데이터는 캐시에 저장하고 전체 데이터는 SQL Server 또는 Azure Storage 같은 백엔드 영구 저장소에 저장해야 합니다. Redis 캐시는 SQL 캐시와 비교해서 높은 처리량과 낮은 대기 시간을 제공하는 캐싱 솔루션입니다.

추가 자료