缓存组件
缓存类型
1. 内存缓存(Memory Cache)
- 存储在应用程序内存中
- 访问速度最快
- 不支持跨进程共享
- 适合单体应用
2. 分布式缓存(Distributed Cache)
- 存储在外部缓存服务器(Redis、Memcached)
- 支持多实例共享
- 支持数据持久化
- 适合分布式应用
内存缓存(Memory Cache)
安装依赖
# 内存缓存(通常已包含在 ASP.NET Core 中)
dotnet add package Microsoft.Extensions.Caching.Memory
基本使用
- 配置服务
- 基本操作
- 高级选项
// Program.cs
var builder = WebApplication.CreateBuilder(args);
// 注册内存缓存
builder.Services.AddMemoryCache(options =>
{
options.SizeLimit = 1024; // 设置缓存大小限制
options.CompactionPercentage = 0.25; // 当达到限制时,移除 25% 的缓存
});
var app = builder.Build();
using Microsoft.Extensions.Caching.Memory;
public class ProductService
{
private readonly IMemoryCache _cache;
private readonly ILogger<ProductService> _logger;
public ProductService(IMemoryCache cache, ILogger<ProductService> logger)
{
_cache = cache;
_logger = logger;
}
public async Task<Product?> GetProductByIdAsync(int productId)
{
string cacheKey = $"product:{productId}";
// 尝试从缓存获取
if (_cache.TryGetValue(cacheKey, out Product? product))
{
_logger.LogInformation("从缓存获取产品: {ProductId}", productId);
return product;
}
// 从数据库获取
_logger.LogInformation("从数据库获取产品: {ProductId}", productId);
product = await GetProductFromDatabaseAsync(productId);
if (product != null)
{
// 设置缓存选项
var cacheOptions = new MemoryCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10), // 10 分钟后过期
SlidingExpiration = TimeSpan.FromMinutes(2), // 2 分钟内无访问则过期
Size = 1 // 占用缓存大小
};
// 存入缓存
_cache.Set(cacheKey, product, cacheOptions);
}
return product;
}
// 模拟数据库查询
private async Task<Product?> GetProductFromDatabaseAsync(int productId)
{
await Task.Delay(100); // 模拟延迟
return new Product { Id = productId, Name = $"Product {productId}", Price = 99.99M };
}
}
public class Product
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public decimal Price { get; set; }
}
public class CacheService
{
private readonly IMemoryCache _cache;
public CacheService(IMemoryCache cache)
{
_cache = cache;
}
public T GetOrCreate<T>(string key, Func<T> factory, TimeSpan? expiration = null)
{
return _cache.GetOrCreate(key, entry =>
{
// 设置缓存选项
if (expiration.HasValue)
{
entry.AbsoluteExpirationRelativeToNow = expiration;
}
// 设置缓存优先级
entry.Priority = CacheItemPriority.Normal;
// 注册回调(缓存移除时触发)
entry.RegisterPostEvictionCallback((key, value, reason, state) =>
{
Console.WriteLine($"缓存移除: Key={key}, Reason={reason}");
});
// 执行工厂方法获取数据
return factory();
});
}
public async Task<T> GetOrCreateAsync<T>(
string key,
Func<Task<T>> factory,
MemoryCacheEntryOptions? options = null)
{
if (_cache.TryGetValue(key, out T? value))
{
return value!;
}
value = await factory();
options ??= new MemoryCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10)
};
_cache.Set(key, value, options);
return value;
}
public void Remove(string key)
{
_cache.Remove(key);
}
public void Clear()
{
// Memory Cache 没有内置的 Clear 方法
// 需要手动管理缓存键或使用 Compact
if (_cache is MemoryCache memoryCache)
{
memoryCache.Compact(1.0); // 移除所有缓存
}
}
}
缓存驱逐策略
var options = new MemoryCacheEntryOptions
{
// 绝对过期时间(从现在开始)
AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10),
// 绝对过期时间(指定时间点)
AbsoluteExpiration = DateTimeOffset.Now.AddHours(1),
// 滑动过期时间(在指定时间内无访问则过期)
SlidingExpiration = TimeSpan.FromMinutes(5),
// 缓存优先级
Priority = CacheItemPriority.High, // Low, Normal, High, NeverRemove
// 缓存大小
Size = 1,
// 过期回调
PostEvictionCallbacks =
{
new PostEvictionCallbackRegistration
{
EvictionCallback = (key, value, reason, state) =>
{
Console.WriteLine($"Key: {key}, Reason: {reason}");
}
}
}
};
_cache.Set("key", "value", options);
分布式缓存(Distributed Cache)
安装依赖
# Redis 分布式缓存
dotnet add package Microsoft.Extensions.Caching.StackExchangeRedis
# SQL Server 分布式缓存(可选)
dotnet add package Microsoft.Extensions.Caching.SqlServer
Redis 配置
- appsettings.json
- 配置服务
- 基本使用
{
"Redis": {
"Configuration": "localhost:6379",
"InstanceName": "MyApp:"
}
}
// Program.cs
var builder = WebApplication.CreateBuilder(args);
// 配置 Redis 分布式缓存
builder.Services.AddStackExchangeRedisCache(options =>
{
options.Configuration = builder.Configuration["Redis:Configuration"];
options.InstanceName = builder.Configuration["Redis:InstanceName"];
});
var app = builder.Build();
using Microsoft.Extensions.Caching.Distributed;
using System.Text.Json;
public class UserService
{
private readonly IDistributedCache _cache;
private readonly ILogger<UserService> _logger;
public UserService(IDistributedCache cache, ILogger<UserService> logger)
{
_cache = cache;
_logger = logger;
}
public async Task<User?> GetUserByIdAsync(int userId)
{
string cacheKey = $"user:{userId}";
// 从缓存获取
var cachedData = await _cache.GetStringAsync(cacheKey);
if (!string.IsNullOrEmpty(cachedData))
{
_logger.LogInformation("从缓存获取用户: {UserId}", userId);
return JsonSerializer.Deserialize<User>(cachedData);
}
// 从数据库获取
_logger.LogInformation("从数据库获取用户: {UserId}", userId);
var user = await GetUserFromDatabaseAsync(userId);
if (user != null)
{
// 序列化并存入缓存
var serializedData = JsonSerializer.Serialize(user);
var options = new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(30),
SlidingExpiration = TimeSpan.FromMinutes(5)
};
await _cache.SetStringAsync(cacheKey, serializedData, options);
}
return user;
}
public async Task UpdateUserAsync(User user)
{
// 更新数据库
await UpdateUserInDatabaseAsync(user);
// 更新缓存
string cacheKey = $"user:{user.Id}";
var serializedData = JsonSerializer.Serialize(user);
await _cache.SetStringAsync(cacheKey, serializedData);
}
public async Task DeleteUserAsync(int userId)
{
// 删除数据库记录
await DeleteUserFromDatabaseAsync(userId);
// 删除缓存
string cacheKey = $"user:{userId}";
await _cache.RemoveAsync(cacheKey);
}
// 模拟方法
private Task<User?> GetUserFromDatabaseAsync(int userId) =>
Task.FromResult<User?>(new User { Id = userId, Name = $"User {userId}" });
private Task UpdateUserInDatabaseAsync(User user) => Task.CompletedTask;
private Task DeleteUserFromDatabaseAsync(int userId) => Task.CompletedTask;
}
public class User
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public string Email { get; set; } = string.Empty;
}
分布式缓存扩展
public static class DistributedCacheExtensions
{
public static async Task<T?> GetAsync<T>(this IDistributedCache cache, string key)
{
var data = await cache.GetStringAsync(key);
if (string.IsNullOrEmpty(data))
{
return default;
}
return JsonSerializer.Deserialize<T>(data);
}
public static async Task SetAsync<T>(
this IDistributedCache cache,
string key,
T value,
DistributedCacheEntryOptions? options = null)
{
var serializedData = JsonSerializer.Serialize(value);
await cache.SetStringAsync(key, serializedData, options ?? new DistributedCacheEntryOptions());
}
public static async Task<T> GetOrCreateAsync<T>(
this IDistributedCache cache,
string key,
Func<Task<T>> factory,
DistributedCacheEntryOptions? options = null)
{
var cachedValue = await cache.GetAsync<T>(key);
if (cachedValue != null)
{
return cachedValue;
}
var value = await factory();
await cache.SetAsync(key, value, options);
return value;
}
}
// 使用示例
public class OrderService
{
private readonly IDistributedCache _cache;
public OrderService(IDistributedCache cache)
{
_cache = cache;
}
public async Task<Order?> GetOrderAsync(int orderId)
{
return await _cache.GetOrCreateAsync(
key: $"order:{orderId}",
factory: async () => await GetOrderFromDatabaseAsync(orderId),
options: new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(15)
}
);
}
private Task<Order?> GetOrderFromDatabaseAsync(int orderId) =>
Task.FromResult<Order?>(new Order { Id = orderId, Total = 199.99M });
}
public class Order
{
public int Id { get; set; }
public decimal Total { get; set; }
}
缓存策略
1. Cache-Aside(旁路缓存)
public class CacheAsideService<T>
{
private readonly IDistributedCache _cache;
private readonly ILogger<CacheAsideService<T>> _logger;
public CacheAsideService(IDistributedCache cache, ILogger<CacheAsideService<T>> logger)
{
_cache = cache;
_logger = logger;
}
public async Task<T?> GetAsync(string key, Func<Task<T?>> loadFromSource)
{
// 1. 尝试从缓存获取
var cachedData = await _cache.GetAsync<T>(key);
if (cachedData != null)
{
_logger.LogInformation("Cache hit: {Key}", key);
return cachedData;
}
_logger.LogInformation("Cache miss: {Key}", key);
// 2. 从数据源加载
var data = await loadFromSource();
// 3. 写入缓存
if (data != null)
{
await _cache.SetAsync(key, data, new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10)
});
}
return data;
}
}
2. Write-Through(写穿透)
public class WriteThroughService<T>
{
private readonly IDistributedCache _cache;
private readonly ILogger<WriteThroughService<T>> _logger;
public WriteThroughService(IDistributedCache cache, ILogger<WriteThroughService<T>> logger)
{
_cache = cache;
_logger = logger;
}
public async Task<T?> GetAsync(string key, Func<Task<T?>> loadFromSource)
{
var cachedData = await _cache.GetAsync<T>(key);
if (cachedData != null)
{
return cachedData;
}
var data = await loadFromSource();
if (data != null)
{
await _cache.SetAsync(key, data);
}
return data;
}
public async Task SaveAsync(string key, T value, Func<T, Task> saveToSource)
{
// 1. 先写入数据源
await saveToSource(value);
// 2. 再写入缓存
await _cache.SetAsync(key, value, new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10)
});
_logger.LogInformation("Data saved to both source and cache: {Key}", key);
}
}
3. Write-Behind(写回)
public class WriteBehindService<T>
{
private readonly IDistributedCache _cache;
private readonly ILogger<WriteBehindService<T>> _logger;
private readonly Channel<(string Key, T Value)> _writeQueue;
public WriteBehindService(IDistributedCache cache, ILogger<WriteBehindService<T>> logger)
{
_cache = cache;
_logger = logger;
_writeQueue = Channel.CreateUnbounded<(string, T)>();
// 启动后台任务处理写入队列
_ = ProcessWriteQueueAsync();
}
public async Task SaveAsync(string key, T value, Func<T, Task> saveToSource)
{
// 1. 先写入缓存
await _cache.SetAsync(key, value);
_logger.LogInformation("Data saved to cache: {Key}", key);
// 2. 将写入任务放入队列
await _writeQueue.Writer.WriteAsync((key, value));
}
private async Task ProcessWriteQueueAsync()
{
await foreach (var (key, value) in _writeQueue.Reader.ReadAllAsync())
{
try
{
// 异步写入数据源
// await saveToSource(value);
_logger.LogInformation("Data saved to source: {Key}", key);
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to save data to source: {Key}", key);
}
}
}
}
缓存失效
1. 主动失效
public class ProductCacheService
{
private readonly IDistributedCache _cache;
public ProductCacheService(IDistributedCache cache)
{
_cache = cache;
}
public async Task UpdateProductAsync(Product product)
{
// 更新数据库
await UpdateProductInDatabaseAsync(product);
// 失效缓存
await _cache.RemoveAsync($"product:{product.Id}");
// 失效相关缓存
await _cache.RemoveAsync($"product:list:category:{product.CategoryId}");
}
public async Task InvalidateProductCacheAsync(int productId)
{
await _cache.RemoveAsync($"product:{productId}");
}
// 批量失效
public async Task InvalidateMultipleAsync(IEnumerable<string> keys)
{
var tasks = keys.Select(key => _cache.RemoveAsync(key));
await Task.WhenAll(tasks);
}
private Task UpdateProductInDatabaseAsync(Product product) => Task.CompletedTask;
}
2. 基于标签的失效
public class TaggedCacheService
{
private readonly IDistributedCache _cache;
private const string TagPrefix = "tag:";
public TaggedCacheService(IDistributedCache cache)
{
_cache = cache;
}
public async Task SetWithTagsAsync<T>(
string key,
T value,
IEnumerable<string> tags,
DistributedCacheEntryOptions? options = null)
{
// 存储数据
await _cache.SetAsync(key, value, options);
// 为每个标签添加键引用
foreach (var tag in tags)
{
var tagKey = $"{TagPrefix}{tag}";
var existingKeys = await _cache.GetAsync<HashSet<string>>(tagKey) ?? new HashSet<string>();
existingKeys.Add(key);
await _cache.SetAsync(tagKey, existingKeys);
}
}
public async Task InvalidateByTagAsync(string tag)
{
var tagKey = $"{TagPrefix}{tag}";
var keys = await _cache.GetAsync<HashSet<string>>(tagKey);
if (keys != null)
{
foreach (var key in keys)
{
await _cache.RemoveAsync(key);
}
await _cache.RemoveAsync(tagKey);
}
}
}
// 使用示例
await taggedCacheService.SetWithTagsAsync(
key: "product:123",
value: product,
tags: new[] { "products", "category:electronics", "brand:apple" }
);
// 失效所有包含 "category:electronics" 标签的缓存
await taggedCacheService.InvalidateByTagAsync("category:electronics");
缓存穿透、击穿、雪崩
1. 缓存穿透(查询不存在的数据)
public class AntiPenetrationCacheService
{
private readonly IDistributedCache _cache;
private readonly ILogger<AntiPenetrationCacheService> _logger;
public AntiPenetrationCacheService(
IDistributedCache cache,
ILogger<AntiPenetrationCacheService> logger)
{
_cache = cache;
_logger = logger;
}
public async Task<T?> GetAsync<T>(string key, Func<Task<T?>> loadFromSource) where T : class
{
// 尝试从缓存获取
var cachedData = await _cache.GetAsync<CacheWrapper<T>>(key);
if (cachedData != null)
{
// 如果是空值标记,返回 null
if (cachedData.IsNull)
{
_logger.LogInformation("Cache hit (null marker): {Key}", key);
return null;
}
return cachedData.Value;
}
// 从数据源加载
var data = await loadFromSource();
// 无论是否为 null,都缓存结果
var wrapper = new CacheWrapper<T>
{
Value = data,
IsNull = data == null
};
var options = new DistributedCacheEntryOptions
{
// 空值使用较短的过期时间
AbsoluteExpirationRelativeToNow = data == null
? TimeSpan.FromMinutes(1)
: TimeSpan.FromMinutes(10)
};
await _cache.SetAsync(key, wrapper, options);
return data;
}
}
public class CacheWrapper<T>
{
public T? Value { get; set; }
public bool IsNull { get; set; }
}
2. 缓存击穿(热点数据过期)
public class AntiBreakthroughCacheService
{
private readonly IDistributedCache _cache;
private readonly SemaphoreSlim _semaphore = new(1, 1);
public AntiBreakthroughCacheService(IDistributedCache cache)
{
_cache = cache;
}
public async Task<T?> GetAsync<T>(string key, Func<Task<T?>> loadFromSource)
{
var cachedData = await _cache.GetAsync<T>(key);
if (cachedData != null)
{
return cachedData;
}
// 使用信号量确保只有一个线程加载数据
await _semaphore.WaitAsync();
try
{
// 双重检查
cachedData = await _cache.GetAsync<T>(key);
if (cachedData != null)
{
return cachedData;
}
// 加载数据
var data = await loadFromSource();
if (data != null)
{
await _cache.SetAsync(key, data, new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10)
});
}
return data;
}
finally
{
_semaphore.Release();
}
}
}
3. 缓存雪崩(大量缓存同时过期)
public class AntiAvalancheCacheService
{
private readonly IDistributedCache _cache;
private readonly Random _random = new();
public AntiAvalancheCacheService(IDistributedCache cache)
{
_cache = cache;
}
public async Task<T?> GetAsync<T>(string key, Func<Task<T?>> loadFromSource, int baseExpirationMinutes = 10)
{
var cachedData = await _cache.GetAsync<T>(key);
if (cachedData != null)
{
return cachedData;
}
var data = await loadFromSource();
if (data != null)
{
// 添加随机时间,避免同时过期
var randomOffset = TimeSpan.FromSeconds(_random.Next(0, 300)); // 0-5分钟随机偏移
var expiration = TimeSpan.FromMinutes(baseExpirationMinutes) + randomOffset;
await _cache.SetAsync(key, data, new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = expiration
});
}
return data;
}
}
性能监控
public class CacheMetricsService
{
private long _hits;
private long _misses;
private long _totalRequests;
public void RecordHit()
{
Interlocked.Increment(ref _hits);
Interlocked.Increment(ref _totalRequests);
}
public void RecordMiss()
{
Interlocked.Increment(ref _misses);
Interlocked.Increment(ref _totalRequests);
}
public CacheMetrics GetMetrics()
{
var total = Interlocked.Read(ref _totalRequests);
var hits = Interlocked.Read(ref _hits);
var misses = Interlocked.Read(ref _misses);
return new CacheMetrics
{
TotalRequests = total,
Hits = hits,
Misses = misses,
HitRate = total > 0 ? (double)hits / total : 0
};
}
public void Reset()
{
Interlocked.Exchange(ref _hits, 0);
Interlocked.Exchange(ref _misses, 0);
Interlocked.Exchange(ref _totalRequests, 0);
}
}
public class CacheMetrics
{
public long TotalRequests { get; set; }
public long Hits { get; set; }
public long Misses { get; set; }
public double HitRate { get; set; }
}
// 使用示例
public class MonitoredCacheService
{
private readonly IDistributedCache _cache;
private readonly CacheMetricsService _metrics;
public MonitoredCacheService(IDistributedCache cache, CacheMetricsService metrics)
{
_cache = cache;
_metrics = metrics;
}
public async Task<T?> GetAsync<T>(string key, Func<Task<T?>> loadFromSource)
{
var cachedData = await _cache.GetAsync<T>(key);
if (cachedData != null)
{
_metrics.RecordHit();
return cachedData;
}
_metrics.RecordMiss();
var data = await loadFromSource();
if (data != null)
{
await _cache.SetAsync(key, data);
}
return data;
}
}
最佳实践
最佳实践
缓存设计
- ✅ 明确缓存的目标(降低延迟 vs 减少负载)
- ✅ 选择合适的缓存类型(内存 vs 分布式)
- ✅ 设计合理的缓存键命名规范
- ✅ 为不同类型的数据设置不同的过期时间
- ✅ 使用缓存预热避免冷启动
缓存更新
- ✅ 优先使用 Cache-Aside 模式
- ✅ 更新数据库后主动失效缓存
- ✅ 避免缓存和数据库不一致
- ✅ 使用版本号或时间戳检测过期
性能优化
- ✅ 批量操作减少网络开销
- ✅ 使用管道(Pipeline)发送多个命令
- ✅ 避免缓存大对象
- ✅ 使用压缩减少内存占用
- ✅ 监控缓存命中率
可靠性
- ✅ 处理缓存穿透、击穿、雪崩
- ✅ 设置合理的超时时间
- ✅ 实现降级策略
- ✅ 记录缓存操作日志
- ✅ 监控缓存健康状态
常见问题
1. 序列化问题
// 使用 System.Text.Json
var options = new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true,
WriteIndented = false,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
};
var json = JsonSerializer.Serialize(value, options);
var obj = JsonSerializer.Deserialize<T>(json, options);
2. 缓存大小限制
// Redis 默认最大值大小为 512MB
// 如果需要缓存大对象,考虑:
// 1. 分片存储
// 2. 压缩数据
// 3. 只缓存关键字段
3. 缓存雪崩预防
// 1. 设置随机过期时间
// 2. 使用互斥锁
// 3. 双重缓存(L1: 内存缓存, L2: Redis)