.Net 솔루션에서 Redis를 사용하는 방법

안녕하세요 ! 👋

여기에서 마지막으로 글을 쓰는 것이 오랜만입니다.

음, 지난번 이후로 저는 .Net 및 인프라/DevOps 문제에 대해 많은 작업을 시작했습니다. 내 일상의 첫 번째이자 가장 일반적인 문제 중 하나는 Redis를 사용하여 API 결과를 캐싱하는 방법을 구현하는 것입니다.

교훈적인 목적으로 캐시는 임시 데이터 저장 장소입니다. 이 데이터는 모든 종류의 응용 프로그램에서 사용되며 이를 구현하면 대역폭 절약, 더 빠른 응답 시간, 더 적은 데이터베이스 적중 등과 같은 많은 이점을 얻을 수 있지만 신중하게 구현하지 않으면 많은 피해를 줄 수 있습니다.

이 게시물을 위해 만든 코드는 in this GitHub repository 에서 찾을 수 있습니다. 거대한 포켓몬 데이터베이스인 PokéAPI을 사용하는 API를 찾을 수 있습니다. 공정 사용 정책은 "요청할 때마다 리소스를 로컬로 캐시합니다."라고 말합니다. 이것이 이 프로젝트에 완벽한 API인 이유입니다.

최종 폴더 구조는 다음과 같습니다.

ExemploRedis/
├─ Controllers/
│  ├─ PokemonController.cs
├─ Extensions/
│  ├─ DistributedCacheExtension.cs
├─ Services/
│  ├─ Interfaces/
│  │  ├─ ICacheService.cs
│  │  ├─ IPokemonService.cs
│  ├─ PokemonCacheService.cs
│  ├─ PokemonService.cs
├─ Pokemon.cs


Pokemon.cs에는 PokéApi가 반환하는 속성이 있습니다. 간단하게 3가지 속성만 추가했습니다.

public class Pokemon
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int Weight { get; set; }
}


Redis를 사용하기 위해 Microsoft.Extensions.Caching.Redis NuGet 패키지를 추가했습니다. 이를 통해 Redis 서비스를 추가하기 위해 Extension/DistributedCacheExtension.cs를 만들었습니다.

public static IServiceCollection AddDistributedCache(
    this IServiceCollection services,
    IConfiguration configuration)
{
    services.AddDistributedRedisCache(options =>
    {
        options.Configuration = 
            configuration.GetConnectionString("Redis");
        options.InstanceName = 
            configuration["Redis:InstanceName"];
    });
    return services;
}


구성 옵션은 자명합니다: 연결 문자열 및 인스턴스 이름.
그런 다음 다음과 같이 Startup.cs에 추가했습니다.

services.AddDistributedCache(Configuration);


Redis를 구성한 후 데이터를 가져오고 보내는 데 도움이 되는 서비스를 개발했습니다(코드 반복 방지). 상호 작용:

public interface ICacheService<T>
{
    Task<T> Get(int id);
    Task Set(T content);
}


서비스:

public class PokemonCacheService : ICacheService<Pokemon>
{
    private readonly IDistributedCache _distributedCache;
    private readonly DistributedCacheEntryOptions _options;
    private const string Prefix = "pokemon_";

    public PokemonCacheService(IDistributedCache distributedCache)
    {
        _distributedCache = distributedCache;
        _options = new DistributedCacheEntryOptions
        {
            AbsoluteExpirationRelativeToNow = 
                TimeSpan.FromSeconds(120),
            SlidingExpiration = TimeSpan.FromSeconds(60)
        };
    }

    public async Task<Pokemon> Get(int id)
    {
        var key = Prefix + id;
        var cache = await _distributedCache.GetStringAsync(key);
        if (cache is null)
        {
            return null;
        }
        var pokemon = JsonConvert.DeserializeObject<Pokemon> 
            (cache);
        return pokemon;
    }

    public async Task Set(Pokemon content)
    {
        var key = Prefix + content.Id;
        var pokemonString = JsonConvert.SerializeObject(content);
        await _distributedCache.SetStringAsync(key, pokemonString, 
            _options);
    }
}


단계별 분석:

private readonly IDistributedCache _distributedCache;
private readonly DistributedCacheEntryOptions _options;
private const string Prefix = "pokemon_";

public PokemonCacheService(IDistributedCache distributedCache)
{
    _distributedCache = distributedCache;
    _options = new DistributedCacheEntryOptions
    {
        AbsoluteExpirationRelativeToNow =   
            TimeSpan.FromSeconds(120),
        SlidingExpiration = TimeSpan.FromSeconds(60)
    };
}


처음 2개의 필드인 _distributedCache 및 _options는 Redis 구성에 연결됩니다. IDistributedCache는 종속성 주입을 통해 Redis에 액세스하는 인터페이스입니다. DistributedCacheEntryOptions는 AbsoluteExpirationRelativeToNow 및 SlidingExpiration을 추가하는 역할을 하는 클래스로, 각각 데이터가 저장되는 총 시간과 액세스되지 않고 저장되는 총 시간(절대 시간보다 크지 않음)을 의미합니다. 접두사는 저장된 포켓몬에 접근하기 위한 키를 만드는 데 사용되는 문자열입니다.
메서드 가져오기:

public async Task<Pokemon> Get(int id)
{
    var key = Prefix + id;
    var cache = await _distributedCache.GetStringAsync(key);
    if (cache is null)
    {
        return null;
    }
    var pokemon = JsonConvert.DeserializeObject<Pokemon>(cache);
    return pokemon;
}


키는 IDistributedCache 인터페이스에서 GetStringAsync(key) 메서드를 사용하여 데이터를 찾는 데 사용됩니다. null인 경우 반환합니다(예외 또는 유효성을 검사하는 다른 방법일 수 있음). 그렇지 않으면 결과 문자열이 역직렬화됩니다.
설정 방법:

public async Task Set(Pokemon content)
{
    var key = Prefix + content.Id;
    var pokemonString = JsonConvert.SerializeObject(content);
    await _distributedCache.SetStringAsync(key, pokemonString, 
        _options);
}


키는 직렬화된 포켓몬 개체로 SetStringAsync() 메서드에 사용됩니다. _options(만료 시간에 대한 것)도 여기에서 사용됩니다.

모든 준비가 완료되면 PokéApi를 사용하는 서비스를 만들었습니다. 상호 작용:

public interface IPokemonService
{
    Task<Pokemon> GetPokemon(int id);
}


서비스:

public class PokemonService : IPokemonService
{
    private readonly HttpClient _httpClient;

    public PokemonService(HttpClient httpClient)
    {
        _httpClient = httpClient;
        _httpClient.BaseAddress = new 
            Uri("https://pokeapi.co/api/v2/");
    }

    public async Task<Pokemon> GetPokemon(int id)
    {
        var response = await 
            _httpClient.GetAsync($"pokemon/{id}");
        var content = await response.Content.ReadAsStringAsync();
        var pokemon = JsonConvert.DeserializeObject<Pokemon> 
            (content);
        return pokemon;
    }
}


매우 간단한 서비스입니다. HttpClient와 PokéApi를 호출하고 포켓몬을 반환하는 GetPokemon(int id) 메서드가 있습니다. 다음과 같이 주입되었습니다.

services.AddHttpClient<IPokemonService, PokemonService();


그리고 마지막 부분에서는 컨트롤러를 만들었습니다.

[ApiController]
[Route("api/[controller]")]
public class PokemonController : ControllerBase
{
    private readonly IPokemonService _pokemonService;
    private readonly ICacheService<Pokemon> _pokemonCacheService;

    public PokemonController(IPokemonService pokemonService, 
        ICacheService<Pokemon> pokemonCacheService)
    {
        _pokemonService = pokemonService;
        _pokemonCacheService = pokemonCacheService;
    }

    [HttpGet("{id}")]
    public async Task<IActionResult> Get(int id)
    {
        Pokemon pokemon = await _pokemonCacheService.Get(id);
        if (pokemon is null)
        {
            pokemon = await _pokemonService.GetPokemon(id);
            await _pokemonCacheService.Set(pokemon);
        }
        return Ok(pokemon);
    }
}


그리고 이것이다. 전체 코드를 받는 것을 잊지 마십시오here. Redis 사용에 도움이 되는 docker-compose.yml 파일이 있습니다.

좋은 웹페이지 즐겨찾기