Skip to content

Redis: The In-Memory Data Store

Redis: The In-Memory Data Store

Redis (Remote Dictionary Server) is an open-source, in-memory data structure store, used as a database, cache, and message broker.

πŸš€ Key Data Structures

  1. Strings: Simple key-value pairs.
  2. Lists: Ordered collections of strings (perfect for queues).
  3. Sets: Unordered collections of unique strings.
  4. Hashes: Maps between string fields and string values.
  5. Sorted Sets: Sets where every member is associated with a score (great for leaderboards).
  6. Streams: Append-only log data structures (Kafka-like features).

🌩️ Persistence Options

Redis is in-memory but offers two main persistence mechanisms:

  • RDB (Redis Database): Performs point-in-time snapshots of your dataset at specified intervals.
  • AOF (Append Only File): Logs every write operation received by the server. It’s more durable but slower and results in larger files.

πŸ‘· Messaging Patterns

Pub/Sub

A fire-and-forget messaging system where publishers send messages to channels, and subscribers receive them in real-time. Caveat: Messages are not stored; if a subscriber is down, they miss the message.

Streams

Introduced in Redis 5.0, Streams allow for persistent message storage, consumer groups, and acknowledgment, making it a lightweight alternative to Kafka.

🌐 Scalability & High Availability

Redis Sentinel

Provides high availability for Redis without Cluster. It monitors masters and slaves, and performs automatic failover if a master isn’t working as expected.

Redis Cluster

Automatically shards your data across multiple Redis nodes. It provides a way to run a Redis installation where data is automatically split across multiple nodes.

πŸ› οΈ Advanced Operations: Lua Scripting

Redis allows you to run Lua scripts directly on the server. These scripts are atomicβ€”no other script or Redis command will run while a script is executing.

-- Simple script to increment a key if it exists
if redis.call("EXISTS", KEYS[1]) == 1 then
    return redis.call("INCR", KEYS[1])
else
    return nil
end

Example for .NET

var redisConnectionString = builder.Configuration.GetConnectionString("Redis");

// 1. Build advanced configuration options
var redisOptions = ConfigurationOptions.Parse(redisConnectionString);

// --- ADVANCED TIMEOUTS & RESILIENCE ---
redisOptions.AbortOnConnectFail = false; // Crucial for production reconnect loops
redisOptions.ConnectRetry = 5;          // Retry connecting 5 times before failing
redisOptions.ConnectTimeout = 5000;     // 5 seconds connect timeout
redisOptions.SyncTimeout = 3000;        // 3 seconds synchronous command timeout
redisOptions.AsyncTimeout = 3000;       // 3 seconds asynchronous command timeout

// --- PERFORMANCE & DIAGNOSTICS ---
redisOptions.ClientName = "MyAspNetCoreApp"; 
redisOptions.KeepAlive = 60;            // Send a keep-alive ping every 60 seconds

// 2. Initialize the Multiplexer 
// (It is safe and recommended to register this as a singleton if other services need direct Redis access)
IConnectionMultiplexer multiplexer = ConnectionMultiplexer.Connect(redisOptions);
multiplexer.ConnectionFailed += (sender, e) => 
{
    Console.WriteLine($"[REDIS ERROR] Connection failed to {e.EndPoint}: {e.FailureType}");
};

multiplexer.ConnectionRestored += (sender, e) => 
{
    Console.WriteLine($"[REDIS INFO] Connection recovered to {e.EndPoint}");
};
builder.Services.AddSingleton(multiplexer);

// 3. Attach it via ConnectionMultiplexerFactory
builder.Services.AddStackExchangeRedisCache(options =>
{
    options.InstanceName = builder.Configuration["Redis:InstanceName"] ?? "MyApp:";
    
    // The factory expects a Task<IConnectionMultiplexer>
    options.ConnectionMultiplexerFactory = () => Task.FromResult(multiplexer);
});

resilient

using Polly;
using Polly.CircuitBreaker;

public static class CacheCircuitBreaker
{
    // Circuit breaker shared across the application context
    public static readonly AsyncCircuitBreakerPolicy BreakerPolicy = Policy
        .Handle<Exception>() // Catch Redis connection/socket errors
        .CircuitBreakerAsync(
            exceptionsAllowedBeforeBreaking: 5,         // Trip after 5 failures
            durationOfBreak: TimeSpan.FromSeconds(30)   // Stop trying Redis for 30 seconds
        );
}


public static async Task<T?> GetRecordWithBreakerAsync<T>(this IDistributedCache cache, string recordId, ILogger logger)
{
    // Check if the circuit breaker is currently open (tripped)
    if (CacheCircuitBreaker.BreakerPolicy.CircuitState == CircuitState.Open)
    {
        // Redis is known to be dead; skip completely and go to DB instantly
        return default; 
    }

    try
    {
        return await CacheCircuitBreaker.BreakerPolicy.ExecuteAsync(async () =>
        {
            var jsonData = await cache.GetStringAsync(recordId);
            return jsonData is null ? default : JsonSerializer.Deserialize<T>(jsonData);
        });
    }
    catch (Exception ex)
    {
        logger.LogError(ex, "Redis execution failed. Circuit breaker evaluating state.");
        return default;
    }
}

πŸ’‘ Best Practice: Redlock

When using Redis as a distributed lock, use the Redlock algorithm to ensure safety across multiple independent Redis instances.