DEV Community

Abelardo Ruben Irarrázabal Díaz
Abelardo Ruben Irarrázabal Díaz

Posted on

Mejorar el rendimiento de mis aplicaciones .Net 6 con REDIS

En este post nos centraremos en el caso de uso de caching para la optimización de la respuesta de algún servicio en especifico.

Para ello explicare el siguiente codigo que esta en mi repositorio de github CacheServicesApp donde esta implementado en una API .Net 6 con redis

Pre requisitos

  • Lo primero es tener instalado redis, para ello tengo un post en el cual explico como montar redis en docker Redis en Docker Container
  • Tener instalado Visual studio 2022

El problema

El siguiente problema es hipotético el cual puede pasar en un ambiente productivo real:

Tenemos una api que esta montada en la nube en EE.UU la cual debe comunicarse con otro servicio externo el cual esta en Chile. El problema es que tiene un latencia muy alta debido a la diferencia geográfica entre ambos servicios y la alta demanda de solicitudes lo que afecta aumentando el tiempo de respuesta.

Solución

Una posible solución es montar un servidor de cache distribuida (en este caso Redis) que este en la misma localización que este nuestro servicio.

Por que cache distribuida y no en la cache de la memoria: Esto dependerá si los datos que necesitamos extraer también lo consume otro cliente en ese caso seria una buena opción utilizar cache distribuida.

Construyendo el servicio

En esta ocasión consumiremos un endpoint desde nuestro nuevo servicio construido Net Core 6, el cual en una primera petición obtendrá los datos, los almacenara en Redis y los devuelve al front. En una segunda petición traera los datos directo de Redis optimizando los tiempos de respuesta.

Crear la solución

  • En Visual Studio 2022 creamos un proyecto con la plantilla ASP NET. Core Web API.

  • Luego nos solicitará el nombre del proyecto el cual sera CacheServiceApp

  • En el siguiente paso seleccionamos

    • Framework: .NET 6.0
    • Marcar check de Configurar para HTTPS
    • Marcar check de Usar Controladores
    • Marcar check de Habilitar compatibilidad con OpenAPI (para implementar swagger)
  • Con esto pasos ya tenemos nuestro proyecto y solución lista

Image description

Crear los proyectos

ahora debemos agregar dos capas al proyecto, las cuales serán del tipo librería de clases y quedaría según la siguiente imagen:

Image description

Ahora tenemos: la capa Model de nuestras entidades, la capa Services para conectarnos a fuentes externas y nuestra capa de presentación que para esta ocasión es de tipo API.

Instalar los paquetes nugget

Para conectarnos a Redis debemos instalar los siguientes paquetes

En nuestra capa CacheServiceApp.Services instalamos el paquete

Microsoft.Extensions.Caching.Abstractions

Este paquete es el que a través de la interfaz IDistributedCache nos permitirá interactuar con Redis que lo veremos mas adelante.

En la capa donde exponemos la api CacheServiceApp instalamos el paquete:

Microsoft.Extensions.Caching.StackExchange

Esto nos permitirá en el archivo Program.cs inyectar la cadena de conexión a Redis.

Conectar al servicio

CacheServiceApp

Lo primero como mencionaba en el punto anterior debemos generar la conexión en la capa de presentación CacheServiceApp (API) en el archivo Program.cs y dejaremos el string de conexión en el archivo appsettings.json.

appsettings.json:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "Caching": {
    "RedisConnection": "localhost:6379"
  },
  "Endpoint": {
    "UrlBirds": "https://aves.ninjas.cl/api/birds"
  }
}

Enter fullscreen mode Exit fullscreen mode
  • RedisConnection es nuestra cadena de conexión a redis. Recuerden que en este ejemplo utilizamos redis en docker, que el tutorial lo deje en el link mas arriba.

  • La propiedad Endpoint es la url del servicio que consumiremos para simular la latencia entre nuestra api y el servicio y luego almacenaremos esos datos en Redis.

Program.cs:

using CacheServiceApp.Services;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
var configuration = builder.Configuration;
builder.Services.AddControllers();

builder.Services.AddHttpClient<IBirdsService, BirdsService>(client =>
{
    client.BaseAddress = new Uri(configuration.GetValue<string>("Endpoint:UrlBirds"));
});

builder.Services.AddStackExchangeRedisCache(options =>
{
    options.Configuration = configuration.GetValue<string>("Caching:RedisConnection");
});

// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseAuthorization();

app.MapControllers();

app.Run();

Enter fullscreen mode Exit fullscreen mode
  • Aca son importantes tres puntos en la clase:
  1. obtengo la configuración para traer el string de conexión del appsettings.json
var configuration = builder.Configuration;
Enter fullscreen mode Exit fullscreen mode
  1. Genero la inyección del HttpClient del servicio que consumimos
builder.Services.AddHttpClient<IBirdsService, BirdsService>(client =>
{
    client.BaseAddress = new Uri(configuration.GetValue<string>("Endpoint:UrlBirds"));
});
Enter fullscreen mode Exit fullscreen mode
  1. Conexión a Redis
builder.Services.AddStackExchangeRedisCache(options =>
{
    options.Configuration = configuration.GetValue<string>("Caching:RedisConnection");
});
Enter fullscreen mode Exit fullscreen mode

CacheServiceApp.Services

Para conectarnos al servicio se debe crear la clase BirdService y la interfaz IBirdService para luego realizar la inyección de dependencia.

BirdService.cs:

public class BirdsService : IBirdsService
{
    private readonly HttpClient _httpClient;
    private readonly IDistributedCache _cache;

    public BirdsService(HttpClient httpClient, IDistributedCache cache) => (_httpClient, _cache) = (httpClient, cache);

    public async Task<List<Bird>> Get()
    {
        string cacheKey = "listBirds";
        string serializedBirds;

        var redisBirds = await _cache.GetAsync(cacheKey);
        List<Bird> listBirds;

        if (redisBirds != null)
        {
            serializedBirds = Encoding.UTF8.GetString(redisBirds);
            listBirds = JsonConvert.DeserializeObject<List<Bird>>(serializedBirds);
        }
        else
        {
            listBirds = await _httpClient.GetFromJsonAsync<List<Bird>>(_httpClient.BaseAddress);
            serializedBirds = JsonConvert.SerializeObject(listBirds);
            redisBirds = Encoding.UTF8.GetBytes(serializedBirds);
            var options = new DistributedCacheEntryOptions()
                .SetAbsoluteExpiration(TimeSpan.FromSeconds(30))
                .SetSlidingExpiration(TimeSpan.FromSeconds(10));

            await _cache.SetAsync(cacheKey, redisBirds, options);
        }

        return listBirds;
    }
}
Enter fullscreen mode Exit fullscreen mode
  • Aca son importantes 3 puntos en la clase:
  1. Declarar en el contructor la interfaz IDistributedCache para obtener y gurdar datos en Redis
private readonly HttpClient _httpClient;
private readonly IDistributedCache _cache;

public BirdsService(HttpClient httpClient, IDistributedCache cache)
Enter fullscreen mode Exit fullscreen mode
  1. Obtener datos desde redis
var redisBirds = await _cache.GetAsync(cacheKey);
Enter fullscreen mode Exit fullscreen mode
  1. Guardar datos en Redis
var options = new DistributedCacheEntryOptions()
                .SetAbsoluteExpiration(TimeSpan.FromSeconds(30))
                .SetSlidingExpiration(TimeSpan.FromSeconds(10));

await _cache.SetAsync(cacheKey, redisBirds, options);
Enter fullscreen mode Exit fullscreen mode

Ya con la implementación en la clase BirdService.cs estaríamos haciendo lo siguiente:

  1. Verificamos si existen datos en redis con la key listBirds
  2. Si hay datos los deserializa de arregglo de bytes y posteriormente de json a objeto
  3. Si no hay datos en la cache la primera vez consulta el servicio de Birds
  4. Serializa en json y luego los guardamos como arreglo de bytes en redis con un expiración absoluta de 30 segundos y la expiración parcial 10 segundos, lo que quire decir que si nadie consulta en 10 segundos expira con un máximo de 30 segundos.

Probemos la solución

Corremos nuestra aplicación CacheServiceApp y nos mostrara el swagger.

Para probarla utilizare Postman que para mi caso la api utiliza el puerto 5112:

Image description

La primera vez que ejecutamos el servicio demora 9.73 segundos

Image description

La segunda vez que lo ejecutamos lo ira a buscar a Redis por lo que tendrá una mayor velocidad de respuesta bajando a 18 milisegundos.

Image description

Conclusiones

  • Para este ejemplo entre 9 segundos y 18 milisegundos no hay tanta diferencia, pero si lo implementan en un ambiente productivo en servicio que aprox se demora 10 y 30 segundos y tiene cientos de solicitudes por minuto, ahí si se vera la diferencia.

  • Cada vez que necesitamos mejorar el rendimiento debes evaluar si es necesario utilizar la cache distribuida o solo en la memoria RAM en donde esta montando el servicio.

💼 Mi Portafolio

Latest comments (0)