11 KiB
11 KiB
name, description, compatibility, metadata
| name | description | compatibility | metadata | ||||
|---|---|---|---|---|---|---|---|
| redis-caching | Redis caching patterns cho distributed systems. Use for cache-aside, session storage, rate limiting, và distributed locks. | .NET 8+, StackExchange.Redis, Microsoft.Extensions.Caching.StackExchangeRedis |
|
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
/// <summary>
/// EN: Configure Redis distributed cache.
/// VI: Cấu hình Redis distributed cache.
/// </summary>
// 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<IConnectionMultiplexer>(sp =>
ConnectionMultiplexer.Connect(builder.Configuration["Redis:ConnectionString"]!));
Cache Service Implementation
/// <summary>
/// EN: Generic cache service with typed operations.
/// VI: Cache service generic với typed operations.
/// </summary>
public interface ICacheService
{
Task<T?> GetAsync<T>(string key, CancellationToken ct = default);
Task SetAsync<T>(string key, T value, TimeSpan? expiry = null, CancellationToken ct = default);
Task RemoveAsync(string key, CancellationToken ct = default);
Task<T> GetOrSetAsync<T>(string key, Func<Task<T>> factory, TimeSpan? expiry = null, CancellationToken ct = default);
}
public class RedisCacheService : ICacheService
{
private readonly IDistributedCache _cache;
private readonly ILogger<RedisCacheService> _logger;
private static readonly TimeSpan DefaultExpiry = TimeSpan.FromMinutes(30);
public RedisCacheService(
IDistributedCache cache,
ILogger<RedisCacheService> logger)
{
_cache = cache;
_logger = logger;
}
public async Task<T?> GetAsync<T>(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<T>(data);
}
public async Task SetAsync<T>(
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<T> GetOrSetAsync<T>(
string key,
Func<Task<T>> factory,
TimeSpan? expiry = null,
CancellationToken ct = default)
{
var cached = await GetAsync<T>(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
/// <summary>
/// EN: Query handler with cache-aside pattern.
/// VI: Query handler với cache-aside pattern.
/// </summary>
public class GetUserProfileQueryHandler
: IRequestHandler<GetUserProfileQuery, UserProfileDto?>
{
private readonly ICacheService _cache;
private readonly IUserRepository _userRepository;
private static readonly TimeSpan CacheExpiry = TimeSpan.FromMinutes(15);
public async Task<UserProfileDto?> 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
/// <summary>
/// EN: Invalidate cache when data changes.
/// VI: Xóa cache khi dữ liệu thay đổi.
/// </summary>
public class UpdateUserProfileCommandHandler
: IRequestHandler<UpdateUserProfileCommand, Unit>
{
private readonly ICacheService _cache;
private readonly IUserRepository _userRepository;
public async Task<Unit> 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
/// <summary>
/// EN: Shopping cart repository using Redis Hash.
/// VI: Repository giỏ hàng dùng Redis Hash.
/// </summary>
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<Cart?> GetAsync(string cartId)
{
var data = await _redis.StringGetAsync($"cart:{cartId}");
return data.IsNullOrEmpty
? null
: JsonSerializer.Deserialize<Cart>(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
// ❌ 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
// ❌ BAD: Multiple requests hit DB simultaneously on cache miss
var data = await _cache.GetAsync<Data>(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
// ❌ 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<Data> { 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
// 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
# 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 - Full code examples
- Error Handling - Cache failure handling
- Repository Pattern - Data access patterns
- Docker Traefik - Redis container setup