---
name: redis-caching
description: Redis caching patterns cho distributed systems. Use for cache-aside, session storage, rate limiting, và distributed locks.
compatibility: ".NET 8+, StackExchange.Redis, Microsoft.Extensions.Caching.StackExchangeRedis"
metadata:
author: Velik Ho
version: "1.0"
---
# Redis Caching Patterns / Mẫu Caching Redis
Redis caching và distributed data patterns cho GoodGo microservices.
## When to Use This Skill / Khi Nào Sử Dụng
Use this skill when:
- Implementing distributed cache / Triển khai distributed cache
- Caching API responses / Caching responses API
- Managing user sessions / Quản lý user sessions
- Implementing rate limiting / Triển khai rate limiting
- Using Redis as primary store (e.g., shopping cart) / Dùng Redis làm store chính
## Core Concepts / Khái Niệm Cốt Lõi
### Caching Strategies / Các Chiến Lược Caching
```
┌─────────────────────────────────────────────────────────────┐
│ CACHING PATTERNS │
├─────────────────────┬─────────────────────┬─────────────────┤
│ CACHE-ASIDE │ WRITE-THROUGH │ WRITE-BEHIND │
│ (Read pattern) │ (Write pattern) │ (Async write) │
├─────────────────────┼─────────────────────┼─────────────────┤
│ 1. Check cache │ 1. Write to cache │ 1. Write cache │
│ 2. If miss, get DB │ 2. Write to DB sync │ 2. Queue DB │
│ 3. Populate cache │ │ 3. Async flush │
└─────────────────────┴─────────────────────┴─────────────────┘
```
### Redis Data Types / Các Kiểu Dữ Liệu Redis
| Type | Use Case | Example |
|------|----------|---------|
| **String** | Simple cache | User profile, JWT token |
| **Hash** | Object cache | Shopping cart |
| **List** | Queues | Task queue |
| **Set** | Unique items | Active users |
| **Sorted Set** | Rankings | Leaderboard |
### Cache Invalidation / Xóa Cache
| Strategy | Description | Use When |
|----------|-------------|----------|
| **TTL** | Auto-expire after time | Data changes rarely |
| **Event-based** | Invalidate on update | Data changes often |
| **Version key** | Cache with version | Complex objects |
## Key Patterns / Mẫu Chính
### Redis Configuration
```csharp
///
/// EN: Configure Redis distributed cache.
/// VI: Cấu hình Redis distributed cache.
///
// Program.cs
builder.Services.AddStackExchangeRedisCache(options =>
{
options.Configuration = builder.Configuration["Redis:ConnectionString"];
options.InstanceName = "GoodGo:";
});
// EN: Register Redis connection multiplexer for advanced scenarios
// VI: Đăng ký Redis connection multiplexer cho các trường hợp nâng cao
builder.Services.AddSingleton(sp =>
ConnectionMultiplexer.Connect(builder.Configuration["Redis:ConnectionString"]!));
```
### Cache Service Implementation
```csharp
///
/// EN: Generic cache service with typed operations.
/// VI: Cache service generic với typed operations.
///
public interface ICacheService
{
Task GetAsync(string key, CancellationToken ct = default);
Task SetAsync(string key, T value, TimeSpan? expiry = null, CancellationToken ct = default);
Task RemoveAsync(string key, CancellationToken ct = default);
Task GetOrSetAsync(string key, Func> factory, TimeSpan? expiry = null, CancellationToken ct = default);
}
public class RedisCacheService : ICacheService
{
private readonly IDistributedCache _cache;
private readonly ILogger _logger;
private static readonly TimeSpan DefaultExpiry = TimeSpan.FromMinutes(30);
public RedisCacheService(
IDistributedCache cache,
ILogger logger)
{
_cache = cache;
_logger = logger;
}
public async Task GetAsync(string key, CancellationToken ct = default)
{
var data = await _cache.GetStringAsync(key, ct);
if (data == null)
{
_logger.LogDebug("Cache miss for key: {Key}", key);
return default;
}
_logger.LogDebug("Cache hit for key: {Key}", key);
return JsonSerializer.Deserialize(data);
}
public async Task SetAsync(
string key,
T value,
TimeSpan? expiry = null,
CancellationToken ct = default)
{
var options = new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = expiry ?? DefaultExpiry
};
var data = JsonSerializer.Serialize(value);
await _cache.SetStringAsync(key, data, options, ct);
_logger.LogDebug("Cached key: {Key} with expiry: {Expiry}", key, expiry ?? DefaultExpiry);
}
public async Task RemoveAsync(string key, CancellationToken ct = default)
{
await _cache.RemoveAsync(key, ct);
_logger.LogDebug("Removed cache key: {Key}", key);
}
public async Task GetOrSetAsync(
string key,
Func> factory,
TimeSpan? expiry = null,
CancellationToken ct = default)
{
var cached = await GetAsync(key, ct);
if (cached != null)
return cached;
var value = await factory();
await SetAsync(key, value, expiry, ct);
return value;
}
}
```
### Cache-Aside Pattern in Query Handler
```csharp
///
/// EN: Query handler with cache-aside pattern.
/// VI: Query handler với cache-aside pattern.
///
public class GetUserProfileQueryHandler
: IRequestHandler
{
private readonly ICacheService _cache;
private readonly IUserRepository _userRepository;
private static readonly TimeSpan CacheExpiry = TimeSpan.FromMinutes(15);
public async Task Handle(
GetUserProfileQuery request,
CancellationToken ct)
{
var cacheKey = $"user:profile:{request.UserId}";
// EN: Try get from cache first
// VI: Thử lấy từ cache trước
return await _cache.GetOrSetAsync(
cacheKey,
async () =>
{
var user = await _userRepository.GetByIdAsync(request.UserId, ct);
return user?.ToProfileDto();
},
CacheExpiry,
ct);
}
}
```
### Cache Invalidation on Write
```csharp
///
/// EN: Invalidate cache when data changes.
/// VI: Xóa cache khi dữ liệu thay đổi.
///
public class UpdateUserProfileCommandHandler
: IRequestHandler
{
private readonly ICacheService _cache;
private readonly IUserRepository _userRepository;
public async Task Handle(
UpdateUserProfileCommand request,
CancellationToken ct)
{
var user = await _userRepository.GetByIdAsync(request.UserId, ct)
?? throw new NotFoundException("User", request.UserId);
user.UpdateProfile(request.DisplayName, request.Bio);
await _userRepository.UnitOfWork.SaveChangesAsync(ct);
// EN: Invalidate cache after update
// VI: Xóa cache sau khi cập nhật
await _cache.RemoveAsync($"user:profile:{request.UserId}", ct);
return Unit.Value;
}
}
```
### Shopping Cart with Redis Hash
```csharp
///
/// EN: Shopping cart repository using Redis Hash.
/// VI: Repository giỏ hàng dùng Redis Hash.
///
public class RedisCartRepository : ICartRepository
{
private readonly IDatabase _redis;
private static readonly TimeSpan CartExpiry = TimeSpan.FromDays(7);
public RedisCartRepository(IConnectionMultiplexer redis)
{
_redis = redis.GetDatabase();
}
public async Task GetAsync(string cartId)
{
var data = await _redis.StringGetAsync($"cart:{cartId}");
return data.IsNullOrEmpty
? null
: JsonSerializer.Deserialize(data!);
}
public async Task SaveAsync(Cart cart)
{
var key = $"cart:{cart.Id}";
var data = JsonSerializer.Serialize(cart);
await _redis.StringSetAsync(key, data, CartExpiry);
}
public async Task DeleteAsync(string cartId)
{
await _redis.KeyDeleteAsync($"cart:{cartId}");
}
}
```
## Common Mistakes / Lỗi Thường Gặp
### 1. No Cache Expiry
```csharp
// ❌ BAD: No expiry leads to stale data
await _cache.SetStringAsync(key, data);
// ✅ GOOD: Always set expiry
await _cache.SetStringAsync(key, data, new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(30)
});
```
### 2. Cache Stampede
```csharp
// ❌ BAD: Multiple requests hit DB simultaneously on cache miss
var data = await _cache.GetAsync(key);
if (data == null)
{
data = await _db.GetDataAsync();
await _cache.SetAsync(key, data);
}
// ✅ GOOD: Use locking to prevent stampede
var data = await _cache.GetOrSetWithLockAsync(key, async () =>
{
return await _db.GetDataAsync();
});
```
### 3. Caching Null Values
```csharp
// ❌ BAD: Not caching null causes repeated DB calls
if (data != null)
await _cache.SetAsync(key, data);
// ✅ GOOD: Cache null with short TTL
var cacheValue = new CacheWrapper { Value = data, IsNull = data == null };
await _cache.SetAsync(key, cacheValue, data == null ? TimeSpan.FromMinutes(1) : TimeSpan.FromHours(1));
```
## Quick Reference / Tham Chiếu Nhanh
### Cache Key Naming
```csharp
// EN: Pattern: {entity}:{id}:{optional-subkey}
// VI: Pattern: {entity}:{id}:{optional-subkey}
"user:profile:123"
"order:items:456"
"product:details:789"
"user:orders:123:page:1"
```
### Common TTL Values
| Data Type | TTL | Reason |
|-----------|-----|--------|
| User profile | 15-30 min | Changes moderately |
| Product catalog | 1-4 hours | Batch updates |
| Shopping cart | 7 days | User convenience |
| Session | 30 min sliding | Security |
| Rate limit | 1 min | Short window |
### Redis Commands via CLI
```bash
# EN: View all keys with pattern
redis-cli KEYS "user:*"
# EN: Get TTL of key
redis-cli TTL "user:profile:123"
# EN: Delete keys by pattern
redis-cli KEYS "cart:*" | xargs redis-cli DEL
```
## Resources / Tài Nguyên
- [Detailed Examples](./references/REFERENCE.md) - Full code examples
- [Error Handling](../error-handling-patterns/SKILL.md) - Cache failure handling
- [Repository Pattern](../repository-pattern/SKILL.md) - Data access patterns
- [Docker Traefik](../docker-traefik/SKILL.md) - Redis container setup