Computer >> Hướng Dẫn Máy Tính >  >> Lập Trình >> Redis

Thành thạo Caching ASP.NET Core: Redis, In-Memory, Distributed Patterns cho hiệu năng đỉnh cao – Phần 27

Thành thạo Caching ASP.NET Core: Redis, In-Memory, Distributed Patterns cho hiệu năng đỉnh cao – Phần 27

Bài trước: ASP.NET Core Performance Hacks: Async, Profiling & Optimization Techniques (Part - 26 of 40)

Mục lục

  1. Cách mạng Caching
  2. Cơ bản & Kiến trúc Caching
  3. In-Memory Caching Deep Dive
  4. Distributed Caching với Redis
  5. Response Caching Strategies
  6. Real-World E-Commerce Caching
  7. Cache Invalidation Patterns
  8. Performance Monitoring & Analytics
  9. Advanced Caching Patterns
  10. Security & Best Practices
  11. Testing Caching Strategies
  12. Production Deployment

1. Cách mạng Caching

Tại sao Caching là "Silver Bullet" cho Performance

Caching biến đổi hiệu năng ứng dụng bằng cách giảm database load, giảm response times, và cải thiện scalability. Trong ứng dụng web hiện đại, caching không chỉ là optimization—đó là cần thiết.

Analogy thực tế: Hãy tưởng tượng một quán cà phê bận rộn. Không có caching, mỗi đơn hàng khách sẽ yêu cầu:

  • Đi đến kho nhà cung cấp (database)
  • Chọn hạt cà phê (query execution)
  • Xay tươi (data processing)
  • Pha riêng lẻ (response generation)

Với caching, các đồ uống phổ biến được chuẩn bị sẵn và sẵn sàng phục vụ ngay lập tức!

// Performance impact demonstration
public class ProductServiceWithoutCaching {
    private readonly ApplicationDbContext _context;
    public async Task<Product> GetProductAsync(int id) {
        // Mọi lần gọi đều hit database - CHẬM!
        return await _context.Products
            .Include(p => p.Category)
            .Include(p => p.Reviews)
            .FirstOrDefaultAsync(p => p.Id == id);
    }
}
public class ProductServiceWithCaching {
    private readonly ApplicationDbContext _context;
    private readonly IMemoryCache _cache;
    public async Task<Product> GetProductAsync(int id) {
        // Thử cache trước - NHANH!
        if (_cache.TryGetValue($"product_{id}", out Product product)) return product;
        // Cache miss - lấy từ database
        product = await _context.Products
            .Include(p => p.Category)
            .Include(p => p.Reviews)
            .FirstOrDefaultAsync(p => p.Id == id);
        // Lưu vào cache cho các request sau
        _cache.Set($"product_{id}", product, TimeSpan.FromMinutes(30));
        return product;
    }
}

Tác động Performance của Caching

ScenarioWithout CachingWith CachingImprovement
Database Queries1000 queries/sec50 queries/secGiảm 20x
Response Time200ms5msNhanh 40x
Server Load80% CPU20% CPUGiảm 4x
Cost$1000/tháng$250/thángTiết kiệm 75%

2. Cơ bản & Kiến trúc Caching

Các Pattern Kiến trúc Caching

// Comprehensive caching service interface
public interface ICacheService {
    Task<T> GetOrCreateAsync<T>(string key, Func<Task<T>> factory, TimeSpan? expiration = null);
    Task<T> GetAsync<T>(string key);
    Task SetAsync<T>(string key, T value, TimeSpan? expiration = null);
    Task RemoveAsync(string key);
    Task<bool> ExistsAsync(string key);
    Task RemoveByPatternAsync(string pattern);
}
// Implementation supporting multiple cache providers
public class CacheService : ICacheService {
    private readonly IMemoryCache _memoryCache;
    private readonly IDistributedCache _distributedCache;
    private readonly ILogger<CacheService> _logger;
    private readonly CacheSettings _settings;
    public CacheService(IMemoryCache memoryCache, IDistributedCache distributedCache, ILogger<CacheService> logger, IOptions<CacheSettings> settings) {
        _memoryCache = memoryCache;
        _distributedCache = distributedCache;
        _logger = logger;
        _settings = settings.Value;
    }
    public async Task<T> GetOrCreateAsync<T>(string key, Func<Task<T>> factory, TimeSpan? expiration = null) {
        try {
            // Thử memory cache trước (nhanh nhất)
            if (_memoryCache.TryGetValue(key, out T cachedValue)) {
                _logger.LogDebug("Memory cache hit for key: {Key}", key);
                return cachedValue;
            }
            // Thử distributed cache
            var distributedValue = await _distributedCache.GetAsync<T>(key);
            if (distributedValue != null) {
                _logger.LogDebug("Distributed cache hit for key: {Key}", key);
                // Populate memory cache cho truy cập nhanh hơn sau
                var memoryCacheOptions = new MemoryCacheEntryOptions {
                    AbsoluteExpirationRelativeToNow = expiration ?? _settings.DefaultExpiration
                };
                _memoryCache.Set(key, distributedValue, memoryCacheOptions);
                return distributedValue;
            }
            // Cache miss - execute factory method
            _logger.LogDebug("Cache miss for key: {Key}, executing factory", key);
            var value = await factory();
            if (value != null) {
                // Lưu vào cả hai caches
                var cacheExpiration = expiration ?? _settings.DefaultExpiration;
                // Memory cache
                var memoryOptions = new MemoryCacheEntryOptions {
                    AbsoluteExpirationRelativeToNow = cacheExpiration
                };
                _memoryCache.Set(key, value, memoryOptions);
                // Distributed cache
                var distributedOptions = new DistributedCacheEntryOptions {
                    AbsoluteExpirationRelativeToNow = cacheExpiration
                };
                await _distributedCache.SetAsync(key, value, distributedOptions);
            }
            return value;
        } catch (Exception ex) {
            _logger.LogError(ex, "Error in GetOrCreateAsync for key: {Key}", key);
            // Nếu caching fail, fallback về factory method
            return await factory();
        }
    }
    public async Task<T> GetAsync<T>(string key) {
        // Implementation tương tự như trên nhưng không có factory fallback
        if (_memoryCache.TryGetValue(key, out T memoryValue)) return memoryValue;
        var distributedValue = await _distributedCache.GetAsync<T>(key);
        if (distributedValue != null) {
            // Populate memory cache
            _memoryCache.Set(key, distributedValue, TimeSpan.FromMinutes(_settings.MemoryCacheExpirationMinutes));
            return distributedValue;
        }
        return default(T);
    }
    public async Task SetAsync<T>(string key, T value, TimeSpan? expiration = null) {
        var cacheExpiration = expiration ?? _settings.DefaultExpiration;
        // Memory cache
        _memoryCache.Set(key, value, cacheExpiration);
        // Distributed cache
        await _distributedCache.SetAsync(key, value, new DistributedCacheEntryOptions {
            AbsoluteExpirationRelativeToNow = cacheExpiration
        });
    }
    public async Task RemoveAsync(string key) {
        _memoryCache.Remove(key);
        await _distributedCache.RemoveAsync(key);
        _logger.LogInformation("Cache removed for key: {Key}", key);
    }
    public async Task<bool> ExistsAsync(string key) {
        return _memoryCache.TryGetValue(key, out _) || await _distributedCache.GetAsync(key) != null;
    }
    // Pattern-based removal cho cache invalidation
    public async Task RemoveByPatternAsync(string pattern) {
        // Phiên bản đơn giản - implementation thực tế phụ thuộc cache provider
        _logger.LogInformation("Removing cache entries matching pattern: {Pattern}", pattern);
        // Trong thực tế, bạn dùng Redis keys command hoặc tương tự
        // Đây là implementation conceptual
    }
}
// Configuration model
public class CacheSettings {
    public TimeSpan DefaultExpiration { get; set; } = TimeSpan.FromMinutes(30);
    public int MemoryCacheExpirationMinutes { get; set; } = 10;
    public string RedisConnectionString { get; set; }
    public bool UseDistributedCache { get; set; } = true;
}

Cấu hình Cache trong Program.cs

// Program.cs - Comprehensive caching setup
var builder = WebApplication.CreateBuilder(args);
// Configure cache settings
builder.Services.Configure<CacheSettings>(builder.Configuration.GetSection("CacheSettings"));
// Add memory cache (luôn available)
builder.Services.AddMemoryCache(options => {
    options.SizeLimit = 1024 * 1024 * 100; // 100MB limit
    options.CompactionPercentage = 0.25; // Compact khi 25% full
});
// Add distributed cache dựa trên configuration
var cacheSettings = builder.Configuration.GetSection("CacheSettings").Get<CacheSettings>();
if (cacheSettings.UseDistributedCache && !string.IsNullOrEmpty(cacheSettings.RedisConnectionString)) {
    builder.Services.AddStackExchangeRedisCache(options => {
        options.Configuration = cacheSettings.RedisConnectionString;
        options.InstanceName = "MyApp:";
    });
    // Register Redis connection cho direct access
    builder.Services.AddSingleton<IConnectionMultiplexer>(sp => ConnectionMultiplexer.Connect(cacheSettings.RedisConnectionString));
} else {
    // Fallback to distributed memory cache (cho development)
    builder.Services.AddDistributedMemoryCache();
}
// Register caching services
builder.Services.AddScoped<ICacheService, CacheService>();
builder.Services.AddScoped<IProductCacheService, ProductCacheService>();
builder.Services.AddScoped<IUserCacheService, UserCacheService>();
// Add response caching
builder.Services.AddResponseCaching(options => {
    options.MaximumBodySize = 1024 * 1024; // 1MB
    options.UseCaseSensitivePaths = false;
});
// Add output caching (ASP.NET Core 7+)
builder.Services.AddOutputCache(options => {
    options.AddBasePolicy(builder => builder.Expire(TimeSpan.FromMinutes(10)));
    options.AddPolicy("Products", builder => builder.Expire(TimeSpan.FromMinutes(5)).Tag("products"));
});
var app = builder.Build();
// Use response caching middleware
app.UseResponseCaching();
// Use output caching middleware
app.UseOutputCache();
app.Run();

3. In-Memory Caching Deep Dive

Advanced Memory Cache Patterns

// Smart memory cache service với eviction policies
public class SmartMemoryCacheService {
    private readonly IMemoryCache _cache;
    private readonly ILogger<SmartMemoryCacheService> _logger;
    private readonly ConcurrentDictionary<string, CacheEntryInfo> _cacheEntries;
    public SmartMemoryCacheService(IMemoryCache cache, ILogger<SmartMemoryCacheService> logger) {
        _cache = cache;
        _logger = logger;
        _cacheEntries = new ConcurrentDictionary<string, CacheEntryInfo>();
    }
    public async Task<T> GetOrCreateAsync<T>(string key, Func<Task<T>> factory, CacheOptions options = null) {
        options ??= new CacheOptions();
        if (_cache.TryGetValue(key, out T cachedValue)) {
            // Update access statistics
            UpdateAccessStats(key);
            _logger.LogDebug("Cache hit for {Key}", key);
            return cachedValue;
        }
        // Cache miss - dùng factory method
        _logger.LogDebug("Cache miss for {Key}, executing factory", key);
        // Implement cache stampede protection
        var semaphore = new SemaphoreSlim(1, 1);
        await semaphore.WaitAsync();
        try {
            // Double-check sau khi acquire lock
            if (_cache.TryGetValue(key, out cachedValue)) {
                UpdateAccessStats(key);
                return cachedValue;
            }
            var value = await factory();
            if (value != null) {
                var cacheEntryOptions = CreateCacheEntryOptions(options);
                // Register callback cho eviction
                cacheEntryOptions.RegisterPostEvictionCallback(EvictionCallback);
                _cache.Set(key, value, cacheEntryOptions);
                // Track cache entry
                _cacheEntries[key] = new CacheEntryInfo {
                    Key = key,
                    Created = DateTime.UtcNow,
                    LastAccessed = DateTime.UtcNow,
                    AccessCount = 1,
                    Size = EstimateSize(value),
                    Options = options
                };
            }
            return value;
        } finally {
            semaphore.Release();
        }
    }
    public CacheStatistics GetStatistics() {
        var entries = _cacheEntries.Values.ToList();
        return new CacheStatistics {
            TotalEntries = entries.Count,
            TotalSize = entries.Sum(e => e.Size),
            HitRate = CalculateHitRate(),
            MostAccessed = entries.OrderByDescending(e => e.AccessCount).Take(10),
            OldestEntries = entries.OrderBy(e => e.LastAccessed).Take(10)
        };
    }
    public void Cleanup() {
        var now = DateTime.UtcNow;
        var toRemove = new List<string>();
        foreach (var entry in _cacheEntries) {
            var age = now - entry.Value.LastAccessed;
            if (age > entry.Value.Options.MaxIdleTime) {
                toRemove.Add(entry.Key);
            }
        }
        foreach (var key in toRemove) {
            _cache.Remove(key);
            _cacheEntries.TryRemove(key, out _);
            _logger.LogInformation("Removed idle cache entry: {Key}", key);
        }
    }
    private void UpdateAccessStats(string key) {
        if (_cacheEntries.TryGetValue(key, out var info)) {
            info.LastAccessed = DateTime.UtcNow;
            info.AccessCount++;
        }
    }
    private void EvictionCallback(object key, object value, EvictionReason reason, object state) {
        _logger.LogInformation("Cache entry evicted: {Key}, Reason: {Reason}", key, reason);
        _cacheEntries.TryRemove(key.ToString(), out _);
    }
    private MemoryCacheEntryOptions CreateCacheEntryOptions(CacheOptions options) {
        var cacheOptions = new MemoryCacheEntryOptions {
            Size = options.Size
        };
        if (options.AbsoluteExpiration.HasValue) cacheOptions.SetAbsoluteExpiration(options.AbsoluteExpiration.Value);
        if (options.SlidingExpiration.HasValue) cacheOptions.SetSlidingExpiration(options.SlidingExpiration.Value);
        if (options.Priority.HasValue) cacheOptions.SetPriority(options.Priority.Value);
        return cacheOptions;
    }
    private long EstimateSize<T>(T value) {
        // Simple size estimation - in production, dùng methods chính xác hơn
        if (value == null) return 0;
        try {
            using var stream = new MemoryStream();
            var formatter = new BinaryFormatter();
            formatter.Serialize(stream, value);
            return stream.Length;
        } catch {
            return 1024; // Default 1KB estimate
        }
    }
    private double CalculateHitRate() {
        var entries = _cacheEntries.Values.ToList();
        if (entries.Count == 0) return 0;
        var totalAccesses = entries.Sum(e => e.AccessCount);
        var hits = entries.Sum(e => e.AccessCount - 1); // First access luôn miss
        return totalAccesses > 0 ? (double)hits / totalAccesses : 0;
    }
}
// Supporting classes
public class CacheOptions {
    public TimeSpan? AbsoluteExpiration { get; set; }
    public TimeSpan? SlidingExpiration { get; set; }
    public TimeSpan MaxIdleTime { get; set; } = TimeSpan.FromHours(24);
    public long Size { get; set; } = 1;
    public CacheItemPriority? Priority { get; set; }
}
public class CacheEntryInfo {
    public string Key { get; set; }
    public DateTime Created { get; set; }
    public DateTime LastAccessed { get; set; }
    public long AccessCount { get; set; }
    public long Size { get; set; }
    public CacheOptions Options { get; set; }
}
public class CacheStatistics {
    public int TotalEntries { get; set; }
    public long TotalSize { get; set; }
    public double HitRate { get; set; }
    public IEnumerable<CacheEntryInfo> MostAccessed { get; set; }
    public IEnumerable<CacheEntryInfo> OldestEntries { get; set; }
}

Real-World Memory Cache Implementation

// E-commerce product catalog với intelligent caching
public class ProductCatalogService {
    private readonly IProductRepository _productRepository;
    private readonly SmartMemoryCacheService _cache;
    private readonly ILogger<ProductCatalogService> _logger;
    private const string ProductsByCategoryKey = "products_category_{0}";
    private const string FeaturedProductsKey = "products_featured";
    private const string ProductDetailsKey = "product_{0}";
    private const string ProductSearchKey = "products_search_{0}";
    public ProductCatalogService(IProductRepository productRepository, SmartMemoryCacheService cache, ILogger<ProductCatalogService> logger) {
        _productRepository = productRepository;
        _cache = cache;
        _logger = logger;
    }
    public async Task<List<Product>> GetProductsByCategoryAsync(int categoryId, int page = 1, int pageSize = 20) {
        var cacheKey = string.Format(ProductsByCategoryKey, categoryId);
        var options = new CacheOptions {
            SlidingExpiration = TimeSpan.FromMinutes(15),
            AbsoluteExpiration = DateTimeOffset.Now.AddHours(1)
        };
        return await _cache.GetOrCreateAsync(cacheKey, async () => {
            _logger.LogInformation("Loading products for category {CategoryId} from database", categoryId);
            var products = await _productRepository.GetProductsByCategoryAsync(categoryId, page, pageSize);
            // Pre-cache individual product details
            foreach (var product in products) {
                var productCacheKey = string.Format(ProductDetailsKey, product.Id);
                await _cache.SetAsync(productCacheKey, product, new CacheOptions {
                    SlidingExpiration = TimeSpan.FromMinutes(30)
                });
            }
            return products;
        }, options);
    }
    public async Task<Product> GetProductDetailsAsync(int productId) {
        var cacheKey = string.Format(ProductDetailsKey, productId);
        var options = new CacheOptions {
            SlidingExpiration = TimeSpan.FromMinutes(30),
            AbsoluteExpiration = DateTimeOffset.Now.AddHours(2)
        };
        return await _cache.GetOrCreateAsync(cacheKey, async () => {
            _logger.LogInformation("Loading product details for {ProductId} from database", productId);
            var product = await _productRepository.GetProductWithDetailsAsync(productId);
            if (product != null) {
                // Update popularity score in background
                _ = Task.Run(async () => {
                    await _productRepository.IncrementViewCountAsync(productId);
                });
            }
            return product;
        }, options);
    }
    public async Task<List<Product>> SearchProductsAsync(string searchTerm, ProductSearchFilters filters) {
        var searchHash = GenerateSearchHash(searchTerm, filters);
        var cacheKey = string.Format(ProductSearchKey, searchHash);
        var options = new CacheOptions {
            SlidingExpiration = TimeSpan.FromMinutes(10),
            AbsoluteExpiration = DateTimeOffset.Now.AddMinutes(30)
        };
        return await _cache.GetOrCreateAsync(cacheKey, async () => {
            _logger.LogInformation("Executing search for '{SearchTerm}' in database", searchTerm);
            return await _productRepository.SearchProductsAsync(searchTerm, filters);
        }, options);
    }
    public async Task<List<Product>> GetFeaturedProductsAsync() {
        var options = new CacheOptions {
            AbsoluteExpiration = DateTimeOffset.Now.AddHours(4) // Refresh every 4 hours
        };
        return await _cache.GetOrCreateAsync(FeaturedProductsKey, async () => {
            _logger.LogInformation("Loading featured products from database");
            return await _productRepository.GetFeaturedProductsAsync();
        }, options);
    }
    public async Task InvalidateProductCacheAsync(int productId) {
        var productKey = string.Format(ProductDetailsKey, productId);
        await _cache.RemoveAsync(productKey);
        // Invalidate category caches that might contain this product
        await InvalidateCategoryCachesAsync();
        _logger.LogInformation("Invalidated cache for product {ProductId}", productId);
    }
    private async Task InvalidateCategoryCachesAsync() {
        // Trong thực tế, bạn track categories nào cần invalidate
        // Phiên bản đơn giản cho demo
        for (int i = 1; i <= 10; i++) // Giả sử 10 main categories
        {
            var categoryKey = string.Format(ProductsByCategoryKey, i);
            await _cache.RemoveAsync(categoryKey);
        }
    }
    private string GenerateSearchHash(string searchTerm, ProductSearchFilters filters) {
        var json = JsonSerializer.Serialize(new { searchTerm, filters });
        using var sha256 = SHA256.Create();
        var hash = sha256.ComputeHash(Encoding.UTF8.GetBytes(json));
        return Convert.ToBase64String(hash);
    }
}

4. Distributed Caching với Redis

Redis Configuration và Advanced Patterns

// Advanced Redis cache service
public class RedisCacheService : IDistributedCacheService {
    private readonly IConnectionMultiplexer _redis;
    private readonly IDatabase _database;
    private readonly ILogger<RedisCacheService> _logger;
    private readonly ISerializer _serializer;
    public RedisCacheService(IConnectionMultiplexer redis, ILogger<RedisCacheService> logger, ISerializer serializer) {
        _redis = redis;
        _database = redis.GetDatabase();
        _logger = logger;
        _serializer = serializer;
    }
    public async Task<T> GetOrCreateAsync<T>(string key, Func<Task<T>> factory, DistributedCacheEntryOptions options = null) {
        options ??= new DistributedCacheEntryOptions {
            AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(30)
        };
        try {
            // Thử lấy từ Redis
            var cachedValue = await GetAsync<T>(key);
            if (cachedValue != null) {
                _logger.LogDebug("Redis cache hit for {Key}", key);
                return cachedValue;
            }
        } catch (RedisException ex) {
            _logger.LogWarning(ex, "Redis unavailable for key {Key}, falling back to factory", key);
            return await factory();
        }
        // Cache miss - execute factory
        _logger.LogDebug("Redis cache miss for {Key}", key);
        var value = await factory();
        if (value != null) {
            await SetAsync(key, value, options);
        }
        return value;
    }
    public async Task<T> GetAsync<T>(string key) {
        try {
            var cachedData = await _database.StringGetAsync(key);
            if (cachedData.HasValue) {
                return _serializer.Deserialize<T>(cachedData);
            }
            return default(T);
        } catch (Exception ex) {
            _logger.LogError(ex, "Error getting key {Key} from Redis", key);
            throw;
        }
    }
    public async Task SetAsync<T>(string key, T value, DistributedCacheEntryOptions options = null) {
        try {
            var serializedValue = _serializer.Serialize(value);
            if (options != null) {
                await _database.StringSetAsync(key, serializedValue, options.AbsoluteExpirationRelativeToNow);
            } else {
                await _database.StringSetAsync(key, serializedValue);
            }
            _logger.LogDebug("Set Redis cache for key {Key}", key);
        } catch (Exception ex) {
            _logger.LogError(ex, "Error setting key {Key} in Redis", key);
            throw;
        }
    }
    public async Task<bool> RemoveAsync(string key) {
        try {
            var result = await _database.KeyDeleteAsync(key);
            _logger.LogDebug("Removed Redis key {Key}: {Result}", key, result);
            return result;
        } catch (Exception ex) {
            _logger.LogError(ex, "Error removing key {Key} from Redis", key);
            throw;
        }
    }
    public async Task<bool> KeyExistsAsync(string key) {
        try {
            return await _database.KeyExistsAsync(key);
        } catch (Exception ex) {
            _logger.LogError(ex, "Error checking existence of key {Key} in Redis", key);
            return false;
        }
    }
    public async Task<long> GetMemoryUsageAsync(string key) {
        try {
            // Dùng Redis MEMORY USAGE command (yêu cầu Redis 4+)
            var result = await _database.ExecuteAsync("MEMORY", "USAGE", key);
            return (long)result;
        } catch {
            return -1;
        }
    }
    public async Task<RedisCacheInfo> GetCacheInfoAsync() {
        try {
            var server = _redis.GetServer(_redis.GetEndPoints().First());
            var info = await server.InfoAsync("memory", "stats");
            return new RedisCacheInfo {
                UsedMemory = long.Parse(info[0]["used_memory"]),
                UsedMemoryHuman = info[0]["used_memory_human"],
                KeyCount = await _database.ExecuteAsync("DBSIZE") as long? ?? 0,
                HitRate = await CalculateHitRateAsync(),
                ConnectedClients = int.Parse(info[1]["connected_clients"])
            };
        } catch (Exception ex) {
            _logger.LogError(ex, "Error getting Redis cache info");
            return null;
        }
    }
    public async Task<IEnumerable<string>> GetKeysByPatternAsync(string pattern) {
        var keys = new List<string>();
        try {
            var server = _redis.GetServer(_redis.GetEndPoints().First());
            await foreach (var key in server.KeysAsync(pattern: pattern)) {
                keys.Add(key);
            }
        } catch (Exception ex) {
            _logger.LogError(ex, "Error getting keys for pattern {Pattern}", pattern);
        }
        return keys;
    }
    private async Task<double> CalculateHitRateAsync() {
        try {
            var server = _redis.GetServer(_redis.GetEndPoints().First());
            var info = await server.InfoAsync("stats");
            var hits = long.Parse(info[0]["keyspace_hits"]);
            var misses = long.Parse(info[0]["keyspace_misses"]);
            return hits + misses > 0 ? (double)hits / (hits + misses) : 0;
        } catch {
            return 0;
        }
    }
}
// Redis cache information model
public class RedisCacheInfo {
    public long UsedMemory { get; set; }
    public string UsedMemoryHuman { get; set; }
    public long KeyCount { get; set; }
    public double HitRate { get; set; }
    public int ConnectedClients { get; set; }
}
// JSON serializer cho Redis
public class JsonSerializer : ISerializer {
    private readonly JsonSerializerOptions _options;
    public JsonSerializer() {
        _options = new JsonSerializerOptions {
            PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
            WriteIndented = false
        };
    }
    public T Deserialize<T>(string data) {
        return JsonSerializer.Deserialize<T>(data, _options);
    }
    public string Serialize<T>(T value) {
        return JsonSerializer.Serialize(value, _options);
    }
}
public interface ISerializer {
    T Deserialize<T>(string data);
    string Serialize<T>(T value);
}

Real-World Redis Implementation cho High-Traffic Application

// Session management với Redis
public class RedisSessionService {
    private readonly IDistributedCacheService _cache;
    private readonly ILogger<RedisSessionService> _logger;
    private const string SessionKeyPrefix = "session:";
    private const string UserSessionsKey = "user_sessions:";
    public RedisSessionService(IDistributedCacheService cache, ILogger<RedisSessionService> logger) {
        _cache = cache;
        _logger = logger;
    }
    public async Task<Session> CreateSessionAsync(int userId, SessionData data) {
        var sessionId = GenerateSessionId();
        var sessionKey = GetSessionKey(sessionId);
        var userSessionsKey = GetUserSessionsKey(userId);
        var session = new Session {
            Id = sessionId,
            UserId = userId,
            CreatedAt = DateTime.UtcNow,
            LastAccessed = DateTime.UtcNow,
            Data = data,
            ExpiresAt = DateTime.UtcNow.AddDays(30)
        };
        var options = new DistributedCacheEntryOptions {
            AbsoluteExpiration = session.ExpiresAt
        };
        // Store session
        await _cache.SetAsync(sessionKey, session, options);
        // Add to user's sessions set
        await _cache.SetAddAsync(userSessionsKey, sessionId);
        _logger.LogInformation("Created session {SessionId} for user {UserId}", sessionId, userId);
        return session;
    }
    public async Task<Session> GetSessionAsync(string sessionId) {
        var sessionKey = GetSessionKey(sessionId);
        var session = await _cache.GetAsync<Session>(sessionKey);
        if (session != null) {
            // Update last accessed time
            session.LastAccessed = DateTime.UtcNow;
            await _cache.SetAsync(sessionKey, session);
            _logger.LogDebug("Retrieved session {SessionId}", sessionId);
        }
        return session;
    }
    public async Task<bool> ValidateSessionAsync(string sessionId) {
        var sessionKey = GetSessionKey(sessionId);
        return await _cache.KeyExistsAsync(sessionKey);
    }
    public async Task InvalidateSessionAsync(string sessionId) {
        var session = await GetSessionAsync(sessionId);
        if (session != null) {
            var sessionKey = GetSessionKey(sessionId);
            var userSessionsKey = GetUserSessionsKey(session.UserId);
            // Remove session
            await _cache.RemoveAsync(sessionKey);
            // Remove from user's sessions
            await _cache.SetRemoveAsync(userSessionsKey, sessionId);
            _logger.LogInformation("Invalidated session {SessionId}", sessionId);
        }
    }
    public async Task InvalidateUserSessionsAsync(int userId) {
        var userSessionsKey = GetUserSessionsKey(userId);
        var sessionIds = await _cache.SetMembersAsync<string>(userSessionsKey);
        foreach (var sessionId in sessionIds) {
            var sessionKey = GetSessionKey(sessionId);
            await _cache.RemoveAsync(sessionKey);
        }
        // Remove user sessions set
        await _cache.RemoveAsync(userSessionsKey);
        _logger.LogInformation("Invalidated all sessions for user {UserId}", userId);
    }
    public async Task<List<Session>> GetUserSessionsAsync(int userId) {
        var userSessionsKey = GetUserSessionsKey(userId);
        var sessionIds = await _cache.SetMembersAsync<string>(userSessionsKey);
        var sessions = new List<Session>();
        foreach (var sessionId in sessionIds) {
            var session = await GetSessionAsync(sessionId);
            if (session != null) {
                sessions.Add(session);
            }
        }
        return sessions.OrderByDescending(s => s.LastAccessed).ToList();
    }
    public async Task CleanupExpiredSessionsAsync() {
        // Redis sẽ tự động expire keys dựa trên TTL
        // Method này cho additional cleanup nếu cần
        _logger.LogInformation("Session cleanup completed by Redis TTL");
    }
    private string GenerateSessionId() {
        return Guid.NewGuid().ToString("N");
    }
    private string GetSessionKey(string sessionId) {
        return $"{SessionKeyPrefix}{sessionId}";
    }
    private string GetUserSessionsKey(int userId) {
        return $"{UserSessionsKey}{userId}";
    }
}
// Session models
public class Session {
    public string Id { get; set; }
    public int UserId { get; set; }
    public DateTime CreatedAt { get; set; }
    public DateTime LastAccessed { get; set; }
    public DateTime ExpiresAt { get; set; }
    public SessionData Data { get; set; }
}
public class SessionData {
    public string UserAgent { get; set; }
    public string IPAddress { get; set; }
    public string Location { get; set; }
    public Dictionary<string, object> CustomData { get; set; } = new();
}

5. Response Caching Strategies

Comprehensive Response Caching Implementation

// Advanced response caching service
public class ResponseCachingService {
    private readonly IResponseCache _responseCache;
    private readonly ILogger<ResponseCachingService> _logger;
    public ResponseCachingService(IResponseCache responseCache, ILogger<ResponseCachingService> logger) {
        _responseCache = responseCache;
        _logger = logger;
    }
    public async Task CacheResponseAsync(string cacheKey, object response, TimeSpan timeToLive) {
        try {
            if (response == null) return;
            var cachedResponse = new CachedResponse {
                Content = response,
                Created = DateTime.UtcNow,
                Expires = DateTime.UtcNow.Add(timeToLive)
            };
            await _responseCache.SetAsync(cacheKey, cachedResponse, timeToLive);
            _logger.LogDebug("Cached response for key {CacheKey}, TTL: {TimeToLive}", cacheKey, timeToLive);
        } catch (Exception ex) {
            _logger.LogError(ex, "Error caching response for key {CacheKey}", cacheKey);
        }
    }
    public async Task<CachedResponse> GetCachedResponseAsync(string cacheKey) {
        try {
            var cachedResponse = await _responseCache.GetAsync<CachedResponse>(cacheKey);
            if (cachedResponse != null) {
                _logger.LogDebug("Cache hit for response key {CacheKey}", cacheKey);
                // Check if expired
                if (cachedResponse.Expires < DateTime.UtcNow) {
                    await _responseCache.RemoveAsync(cacheKey);
                    _logger.LogDebug("Removed expired response for key {CacheKey}", cacheKey);
                    return null;
                }
                return cachedResponse;
            }
            _logger.LogDebug("Cache miss for response key {CacheKey}", cacheKey);
            return null;
        } catch (Exception ex) {
            _logger.LogError(ex, "Error getting cached response for key {CacheKey}", cacheKey);
            return null;
        }
    }
    public string GenerateCacheKey(string path, string queryString, string userId = null) {
        var keyBuilder = new StringBuilder();
        keyBuilder.Append(path.ToLowerInvariant());
        if (!string.IsNullOrEmpty(queryString)) {
            keyBuilder.Append('?');
            keyBuilder.Append(queryString.ToLowerInvariant());
        }
        if (!string.IsNullOrEmpty(userId)) {
            keyBuilder.Append("|user:");
            keyBuilder.Append(userId);
        }
        return keyBuilder.ToString();
    }
    public async Task InvalidateByPatternAsync(string pattern) {
        try {
            await _responseCache.RemoveByPatternAsync(pattern);
            _logger.LogInformation("Invalidated responses matching pattern: {Pattern}", pattern);
        } catch (Exception ex) {
            _logger.LogError(ex, "Error invalidating responses for pattern {Pattern}", pattern);
        }
    }
}
// Cached response model
public class CachedResponse {
    public object Content { get; set; }
    public DateTime Created { get; set; }
    public DateTime Expires { get; set; }
    public string ETag { get; set; }
    public DateTime? LastModified { get; set; }
}
// Response cache implementation
public interface IResponseCache {
    Task SetAsync<T>(string key, T value, TimeSpan timeToLive);
    Task<T> GetAsync<T>(string key);
    Task RemoveAsync(string key);
    Task RemoveByPatternAsync(string pattern);
}
// Action filter cho response caching
public class ResponseCachingAttribute : Attribute, IAsyncActionFilter {
    private readonly int _duration;
    private readonly bool _perUser;
    private readonly string[] _varyBy;
    public ResponseCachingAttribute(int duration, bool perUser = false, params string[] varyBy) {
        _duration = duration;
        _perUser = perUser;
        _varyBy = varyBy;
    }
    public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) {
        var cacheService = context.HttpContext.RequestServices.GetService<ResponseCachingService>();
        var httpContext = context.HttpContext;
        // Generate cache key
        var cacheKey = GenerateCacheKey(httpContext, _perUser, _varyBy);
        // Thử lấy từ cache
        var cachedResponse = await cacheService.GetCachedResponseAsync(cacheKey);
        if (cachedResponse != null) {
            // Return cached response
            context.Result = new ObjectResult(cachedResponse.Content) {
                StatusCode = 200
            };
            // Set cache headers
            if (!string.IsNullOrEmpty(cachedResponse.ETag)) {
                httpContext.Response.Headers.ETag = cachedResponse.ETag;
            }
            if (cachedResponse.LastModified.HasValue) {
                httpContext.Response.Headers.LastModified = cachedResponse.LastModified.Value.ToString("R");
            }
            return;
        }
        // Execute action
        var executedContext = await next();
        if (executedContext.Result is ObjectResult objectResult && objectResult.Value != null) {
            // Cache the response
            var timeToLive = TimeSpan.FromSeconds(_duration);
            await cacheService.CacheResponseAsync(cacheKey, objectResult.Value, timeToLive);
            // Set response cache headers
            httpContext.Response.Headers.CacheControl = $"public, max-age={_duration}";
            httpContext.Response.Headers.Expires = DateTime.UtcNow.AddSeconds(_duration).ToString("R");
        }
    }
    private string GenerateCacheKey(HttpContext httpContext, bool perUser, string[] varyBy) {
        var keyBuilder = new StringBuilder();
        // Path and query string
        keyBuilder.Append(httpContext.Request.Path);
        keyBuilder.Append('?');
        keyBuilder.Append(httpContext.Request.QueryString);
        // User-specific caching
        if (perUser && httpContext.User.Identity.IsAuthenticated) {
            keyBuilder.Append("|user:");
            keyBuilder.Append(httpContext.User.GetUserId());
        }
        // Vary by headers
        foreach (var header in varyBy) {
            if (httpContext.Request.Headers.TryGetValue(header, out var headerValue)) {
                keyBuilder.Append($"|{header}:{headerValue}");
            }
        }
        return keyBuilder.ToString();
    }
}

Real-World Response Caching Implementation

// Product controller với comprehensive caching
[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase {
    private readonly IProductService _productService;
    private readonly IResponseCache _responseCache;
    private readonly ILogger<ProductsController> _logger;
    public ProductsController(IProductService productService, IResponseCache responseCache, ILogger<ProductsController> logger) {
        _productService = productService;
        _responseCache = responseCache;
        _logger = logger;
    }
    [HttpGet]
    [ResponseCaching(300)] // Cache 5 minutes
    public async Task<ActionResult<ApiResponse<List<Product>>>> GetProducts(
        [FromQuery] ProductQuery query) {
        try {
            var products = await _productService.GetProductsAsync(query);
            return Ok(new ApiResponse<List<Product>>(products));
        } catch (Exception ex) {
            _logger.LogError(ex, "Error getting products");
            return StatusCode(500, new ApiResponse<string>("Internal server error"));
        }
    }
    [HttpGet("{id}")]
    [ResponseCaching(600, varyBy: new[] { "Accept-Language" })] // Cache 10 minutes, vary by language
    public async Task<ActionResult<ApiResponse<Product>>> GetProduct(int id) {
        try {
            var product = await _productService.GetProductAsync(id);
            if (product == null) return NotFound(new ApiResponse<string>("Product not found"));
            return Ok(new ApiResponse<Product>(product));
        } catch (Exception ex) {
            _logger.LogError(ex, "Error getting product {ProductId}", id);
            return StatusCode(500, new ApiResponse<string>("Internal server error"));
        }
    }
    [HttpGet("featured")]
    [ResponseCaching(900)] // Cache 15 minutes
    public async Task<ActionResult<ApiResponse<List<Product>>>> GetFeaturedProducts() {
        try {
            var products = await _productService.GetFeaturedProductsAsync();
            return Ok(new ApiResponse<List<Product>>(products));
        } catch (Exception ex) {
            _logger.LogError(ex, "Error getting featured products");
            return StatusCode(500, new ApiResponse<string>("Internal server error"));
        }
    }
    [HttpPost]
    public async Task<ActionResult<ApiResponse<Product>>> CreateProduct(ProductCreateRequest request) {
        try {
            var product = await _productService.CreateProductAsync(request);
            // Invalidate relevant caches
            await InvalidateProductCachesAsync();
            return CreatedAtAction(nameof(GetProduct), new { id = product.Id }, new ApiResponse<Product>(product));
        } catch (Exception ex) {
            _logger.LogError(ex, "Error creating product");
            return StatusCode(500, new ApiResponse<string>("Internal server error"));
        }
    }
    [HttpPut("{id}")]
    public async Task<ActionResult<ApiResponse<Product>>> UpdateProduct(int id, ProductUpdateRequest request) {
        try {
            var product = await _productService.UpdateProductAsync(id, request);
            if (product == null) return NotFound(new ApiResponse<string>("Product not found"));
            // Invalidate relevant caches
            await InvalidateProductCachesAsync(id);
            return Ok(new ApiResponse<Product>(product));
        } catch (Exception ex) {
            _logger.LogError(ex, "Error updating product {ProductId}", id);
            return StatusCode(500, new ApiResponse<string>("Internal server error"));
        }
    }
    [HttpDelete("{id}")]
    public async Task<ActionResult<ApiResponse<bool>>> DeleteProduct(int id) {
        try {
            var result = await _productService.DeleteProductAsync(id);
            if (!result) return NotFound(new ApiResponse<string>("Product not found"));
            // Invalidate relevant caches
            await InvalidateProductCachesAsync(id);
            return Ok(new ApiResponse<bool>(true));
        } catch (Exception ex) {
            _logger.LogError(ex, "Error deleting product {ProductId}", id);
            return StatusCode(500, new ApiResponse<string>("Internal server error"));
        }
    }
    private async Task InvalidateProductCachesAsync(int? productId = null) {
        try {
            // Invalidate product lists
            await _responseCache.RemoveByPatternAsync("api/products*");
            // Invalidate specific product if provided
            if (productId.HasValue) {
                await _responseCache.RemoveAsync($"api/products/{productId}");
            }
            // Invalidate featured products
            await _responseCache.RemoveAsync("api/products/featured");
            _logger.LogInformation("Invalidated product caches for product {ProductId}", productId);
        } catch (Exception ex) {
            _logger.LogError(ex, "Error invalidating product caches");
        }
    }
}

Lưu ý: Đây là trích dẫn toàn diện từ hướng dẫn hoàn chỉnh 150,000+ từ. Bài viết đầy đủ sẽ tiếp tục với các phần chi tiết về cache invalidation patterns, performance monitoring, advanced caching patterns, security, testing strategies, và production deployment với code examples hoàn chỉnh và real-world scenarios.

Hướng dẫn hoàn chỉnh sẽ cung cấp bao phủ toàn diện mọi khía cạnh của ASP.NET Core caching, bao gồm:

  • Advanced cache invalidation strategies với event-based patterns
  • Comprehensive performance monitoring và analytics
  • Cache warming và preloading techniques
  • Geographic caching với CDN integration
  • Cache compression và optimization
  • Security considerations và cache poisoning prevention
  • Load testing và performance benchmarking
  • Production deployment và DevOps integration
  • Real-world case studies từ high-traffic applications

Mỗi section sẽ bao gồm complete, production-ready code examples, best practices, common pitfalls, và alternative approaches để giúp developers master caching cho việc xây dựng high-performance và scalable web applications.