Skip to main content

缓存组件

缓存类型

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();

缓存驱逐策略

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 配置

{
"Redis": {
"Configuration": "localhost:6379",
"InstanceName": "MyApp:"
}
}

分布式缓存扩展

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)

相关资源