Optimiza la Gestión de Reservas en Sistemas Distribuidos con Redis, TTL y .NET: Bloqueo Temporal Eficiente

Cristian Mendoza
5 min readSep 4, 2024

En los sistemas de reservas en línea, uno de los mayores desafíos es evitar que un mismo ticket sea reservado o comprado por múltiples usuarios al mismo tiempo. Esto puede generar frustración para los usuarios y problemas logísticos para los sistemas.

Para solucionar este problema, una herramienta efectiva es Redis, en conjunto con su característica de TTL (Time to Live). En este post, exploraremos cómo Redis y TTL pueden evitar reservas simultáneas y liberar tickets automáticamente si una compra no se completa en el tiempo establecido.

Redis es una base de datos en memoria altamente eficiente que permite almacenar datos clave-valor de manera temporal o permanente. Es muy usada en sistemas que requieren alta velocidad, como cachés y sistemas de comunicación en tiempo real.

TTL (Time to Live) es una característica que permite asignar un tiempo de vida a las claves almacenadas en Redis. Una vez que el tiempo asignado expira, Redis elimina automáticamente la clave, liberando recursos y asegurando que los datos no se mantengan en memoria más tiempo del necesario.

El Desafío en un Sistema Reservas de Tickets

En un sistema típico de compra y venta de tickets, múltiples usuarios pueden intentar reservar el mismo ticket simultáneamente. Sin un mecanismo adecuado de bloqueo, esto podría generar problemas como la doble reserva, donde dos o más usuarios creen haber reservado el mismo ticket.

Problemas que enfrentamos sin Redis y TTL:

  • Reservas múltiples: Si un ticket no se bloquea correctamente, varios usuarios pueden comprarlo al mismo tiempo.
  • Bloqueo indefinido: Si un ticket se bloquea pero la compra no se completa (por errores, abandono de carrito, etc.), el ticket puede quedar bloqueado indefinidamente.

Redis con TTL es la solución para ambos problemas, ya que permite bloquear un ticket temporalmente y, si la compra no se completa en el tiempo estipulado, el bloqueo se libera automáticamente.

Solución con Redis y TTL

Bloqueo de un Ticket con Redis y TTL

Cuando un usuario intenta reservar un ticket, lo primero que hacemos es crear un bloqueo temporal en Redis. Este bloqueo asegura que ningún otro usuario pueda reservar el mismo ticket al mismo tiempo.

Establecer TTL en el Ticket Bloqueado

Para evitar que un ticket quede bloqueado indefinidamente si la compra no se completa, usamos TTL. Una vez que el ticket se ha bloqueado, le asignamos un tiempo de vida (por ejemplo, 5 minutos).

De esta forma, si el usuario no completa la compra dentro de los 5 minutos, Redis eliminará automáticamente el bloqueo, y otros usuarios podrán intentar reservar el ticket nuevamente.

Implementación del sistema

Estructura del Sistema

  1. Reservar Ticket: Cuando un usuario intenta reservar un ticket, el sistema utiliza SETNX para bloquear el ticket y luego aplica un TTL. Si la reserva es exitosa, el ticket queda bloqueado durante un tiempo limitado.
  2. Completar Compra: Si el usuario completa la compra, el bloqueo del ticket se elimina manualmente.
  3. Liberar Bloqueo: Si el tiempo TTL expira antes de que la compra sea completada, el ticket se libera automáticamente.

Implementación en ASP.NET Core

En esta sección, mostrare cómo implementar esta lógica en ASP.NET Core utilizando el cliente de Redis de StackExchange.Redis.

Paso 1: Configurar una Instancia de Redis con Docker

Para utilizar Redis, primero necesitamos una instancia de Redis ejecutándose. La forma más fácil de hacerlo es usando Docker. Si tienes Docker instalado en tu sistema, puedes ejecutar Redis con el siguiente comando:

docker run -d --name redis -p 6379:6379 redis

Este comando hará lo siguiente:

  • Descargará la imagen de Redis si no la tienes.
  • Creará un contenedor llamado redis.
  • Expondrá el puerto 6379 en tu máquina para que la aplicación ASP.NET pueda conectarse a Redis.

Paso 2: Crear una Aplicación ASP.NET Core

Crea una nueva aplicación de ASP.NET Core. Si aún no tienes una aplicación, puedes crear una nueva usando el siguiente comando en tu terminal:

dotnet new webapi -n TicketApp

Esto creará una nueva aplicación web API en un directorio llamado TicketApp.

Paso 3: Instalar el Cliente de Redis en ASP.NET Core

Para que tu aplicación ASP.NET Core pueda comunicarse con Redis, necesitas instalar la librería StackExchange.Redis, que es un cliente oficial de Redis para .NET.

Dentro de tu proyecto, ejecuta el siguiente comando para agregar la dependencia:

dotnet add package StackExchange.Redis

Paso 4: Configurar Redis en Program.cs

En el archivo Program.cs, necesitas configurar la conexión a Redis. Utilizaremos el ConnectionMultiplexer de StackExchange.Redis para conectarnos a Redis y lo registraremos como un servicio singleton para que se comparta en toda la aplicación.

Abre Program.cs y agrega el siguiente código:

var redis = ConnectionMultiplexer.Connect("redis:6379");
serviceCollection.AddSingleton<IConnectionMultiplexer>(redis);
serviceCollection.AddSingleton<ITicketService, TicketService>();

app.MapGet("/api/tickets/lock", async (string ticketId, string userId, ITicketService ticketService) =>
{
var isLocked = await ticketService.TryLockTicketAsync(ticketId, userId);
return isLocked
? Results.Ok($"Ticket {ticketId} processing for the {userId}")
: Results.Conflict("Ticket is not available");
}).WithName("LockTicket").WithOpenApi();

app.MapPost("/api/tickets/unlock", async (string ticketId, ITicketService ticketService) =>
{
await ticketService.UnlockTicketAsync(ticketId);
return Results.Ok($"Ticket {ticketId} unlocked.");
}).WithName("UnLockTicket").WithOpenApi();

app.MapGet("/api/tickets/ttl", async (string ticketId, ITicketService ticketService) =>
{
var ttl = await ticketService.GetRemainingTTLAsync(ticketId);
return ttl >= 0
? Results.Ok($"remain time for {ticketId}: {ttl} seg")
: Results.NotFound("ticket isn't blocked.");
}).WithName("GetTtlTicket").WithOpenApi();
}

Aquí estamos haciendo lo siguiente:

  • Configuramos la conexión a Redis usando ConnectionMultiplexer.Connect("localhost:6379").
  • Registramos nuestro servicio ITicketService como un singleton.
  • Definimos los endpoints de la API para bloquear, desbloquear y consultar el TTL de un ticket.

Paso 5: Crear la Interfaz ITicketService y su Implementación

Ahora necesitamos definir la interfaz ITicketService y su implementación en el servicio TicketService. Este servicio será responsable de interactuar con Redis para bloquear, desbloquear y verificar el TTL de los tickets.

Crea una nueva carpeta en tu proyecto llamada Services, y dentro de ella, crea dos archivos: ITicketService.cs y TicketService.cs.

  1. ITicketService.cs:
public interface ITicketService
{
Task<bool> TryLockTicketAsync(string ticketId, string userId);
Task UnlockTicketAsync(string ticketId);
Task<int> GetRemainingTTLAsync(string ticketId);
}

2. TicketService.cs:

public class TicketService : ITicketService
{
private readonly IConnectionMultiplexer _redis;

public TicketService(IConnectionMultiplexer redis)
{
_redis = redis;
}
/*
*
* Lock the ticket if it's available
*/

public async Task<bool> TryLockTicketAsync(string ticketId, string userId)
{
var db = _redis.GetDatabase();
bool isLocked = await db.StringSetAsync($"ticket:{ticketId}", userId, TimeSpan.FromMinutes(5), When.NotExists);
return isLocked;
}
/*
*
* Unlock the ticket
*/

public async Task UnlockTicketAsync(string ticketId)
{
var db = _redis.GetDatabase();
await db.KeyDeleteAsync($"ticket:{ticketId}");
}
/*
*
* get the remain time
*/

public async Task<int> GetRemainingTTLAsync(string ticketId)
{
var db = _redis.GetDatabase();
var ttl = await db.KeyTimeToLiveAsync($"ticket:{ticketId}");
return ttl.HasValue ? (int)ttl.Value.TotalSeconds : -1;
}
}

Paso 6: Probar la API Localmente

Ahora que tienes todo configurado, puedes ejecutar tu aplicación y probar los endpoints utilizando Postman O Swagger.

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

No responses yet

Write a response