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
- Strings: Simple key-value pairs.
- Lists: Ordered collections of strings (perfect for queues).
- Sets: Unordered collections of unique strings.
- Hashes: Maps between string fields and string values.
- Sorted Sets: Sets where every member is associated with a score (great for leaderboards).
- 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
endExample 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.