BoundedLruCache 缓存工具
BoundedLruCache<TKey, TValue> 是一个线程安全的 LRU(Least Recently Used)缓存实现,具有容量限制和可选的分段策略。
using MiCake.Util.Cache;public BoundedLruCache( int maxSize = 1000, // 最大缓存条目数 int? segments = null, // 分段数(用于提高并发性能) bool useLockFreeApproximation = false // 是否使用无锁近似算法)参数说明:
maxSize: 最大缓存条目数,超过此数量时最久未使用的项会被移除segments: 分段数,用于提高并发性能。小缓存(< 16)自动使用单段useLockFreeApproximation: 是否使用无锁近似算法,适合高并发场景
GetOrAdd - 获取或添加缓存项
Section titled “GetOrAdd - 获取或添加缓存项”var cache = new BoundedLruCache<string, Product>(maxSize: 500);
var product = cache.GetOrAdd("product-1", key =>{ // 缓存未命中时执行 return LoadProductFromDatabase(key);});
// 异步版本var product = await cache.GetOrAdd("product-1", async key =>{ return await LoadProductFromDatabaseAsync(key);});TryGetValue - 尝试获取缓存项
Section titled “TryGetValue - 尝试获取缓存项”if (cache.TryGetValue("product-1", out var product)){ Console.WriteLine($"缓存命中: {product.Name}");}else{ Console.WriteLine("缓存未命中");}AddOrUpdate - 添加或更新缓存项
Section titled “AddOrUpdate - 添加或更新缓存项”// 添加新项或更新已存在的项cache.AddOrUpdate("product-1", newProduct);Remove - 移除缓存项
Section titled “Remove - 移除缓存项”bool removed = cache.Remove("product-1");if (removed){ Console.WriteLine("缓存项已移除");}Clear - 清空缓存
Section titled “Clear - 清空缓存”cache.Clear();| 属性 | 说明 |
|---|---|
Count | 当前缓存项数量 |
MaxSize | 最大容量 |
// 创建缓存实例var cache = new BoundedLruCache<int, Product>(maxSize: 1000);
// 获取或添加var product = cache.GetOrAdd(productId, id => _repository.FindAsync(id).Result);
// 检查是否存在if (cache.TryGetValue(productId, out var cachedProduct)){ return cachedProduct;}
// 使用完毕后释放cache.Dispose();// 使用分段和无锁近似算法提高性能var cache = new BoundedLruCache<string, Product>( maxSize: 10000, segments: 4, // 4 个分段 useLockFreeApproximation: true);
// 并发安全的缓存操作Parallel.For(0, 1000, i =>{ var product = cache.GetOrAdd($"product-{i}", key => { return new Product { Id = i, Name = $"Product {i}" }; });});在服务中使用
Section titled “在服务中使用”public class ProductService : IScopedService{ private readonly BoundedLruCache<int, Product> _cache; private readonly IRepository<Product, int> _repository;
public ProductService(IRepository<Product, int> repository) { _repository = repository; _cache = new BoundedLruCache<int, Product>(maxSize: 500); }
public async Task<Product> GetProduct(int id) { return await _cache.GetOrAdd(id, async productId => { var product = await _repository.FindAsync(productId); if (product == null) throw new NotFoundException("Product", productId); return product; }); }
public void InvalidateCache(int productId) { _cache.Remove(productId); }
public void ClearCache() { _cache.Clear(); }}public class MyModule : MiCakeModule{ public override void ConfigureServices(ModuleConfigServiceContext context) { // 注册为单例 context.Services.AddSingleton(sp => new BoundedLruCache<string, CachedData>(maxSize: 1000));
base.ConfigureServices(context); }}缓存失效策略
Section titled “缓存失效策略”public class CacheService{ private readonly BoundedLruCache<string, CachedItem> _cache;
public CacheService() { _cache = new BoundedLruCache<string, CachedItem>(maxSize: 1000); }
public CachedItem GetOrCreate(string key, TimeSpan expiration) { return _cache.GetOrAdd(key, k => { var item = new CachedItem { Data = LoadData(k), ExpiresAt = DateTime.UtcNow.Add(expiration) }; return item; }); }
public void RemoveExpired() { // 定期清理过期项 // 注意:BoundedLruCache 不支持内置过期,需要手动实现 }}LRU 工作原理
Section titled “LRU 工作原理”LRU(Least Recently Used)算法会自动移除最久未访问的缓存项:
缓存容量:3
1. 添加 A → [A]2. 添加 B → [B, A]3. 添加 C → [C, B, A]4. 访问 A → [A, C, B] // A 被移到最前面5. 添加 D → [D, A, C] // B 被移除(最久未使用)当缓存容量较大时,使用分段可以减少锁竞争:
// 不分段(适合小缓存)var smallCache = new BoundedLruCache<string, int>(maxSize: 100);
// 4 段(适合中等缓存)var mediumCache = new BoundedLruCache<string, int>( maxSize: 1000, segments: 4);
// 8 段(适合大缓存)var largeCache = new BoundedLruCache<string, int>( maxSize: 10000, segments: 8);1. 合理设置容量
Section titled “1. 合理设置容量”// ✅ 正确:根据实际需求设置容量var cache = new BoundedLruCache<int, Product>( maxSize: EstimateRequiredCapacity());
// ❌ 错误:容量过小导致频繁换出var cache = new BoundedLruCache<int, Product>(maxSize: 10);
// ❌ 错误:容量过大占用过多内存var cache = new BoundedLruCache<int, Product>(maxSize: 1000000);2. 注册为单例
Section titled “2. 注册为单例”// ✅ 正确:在 DI 容器中注册为单例services.AddSingleton<BoundedLruCache<string, CachedData>>(sp => new BoundedLruCache<string, CachedData>(maxSize: 1000));
// ❌ 错误:每次创建新实例services.AddScoped<BoundedLruCache<string, CachedData>>(sp => new BoundedLruCache<string, CachedData>(maxSize: 1000));3. 及时释放资源
Section titled “3. 及时释放资源”// ✅ 正确:使用 using 或手动 Disposeusing (var cache = new BoundedLruCache<int, Data>(maxSize: 100)){ // 使用缓存}
// 或var cache = new BoundedLruCache<int, Data>(maxSize: 100);try{ // 使用缓存}finally{ cache.Dispose();}4. 适合缓存不经常变化的数据
Section titled “4. 适合缓存不经常变化的数据”// ✅ 适合缓存- 配置数据- 字典数据- 产品信息- 用户基本信息
// ❌ 不适合缓存- 实时数据- 频繁更新的数据- 大对象(> 1MB)5. 无锁模式下的幂等工厂
Section titled “5. 无锁模式下的幂等工厂”// ⚠️ 无锁模式下,工厂方法可能被多次调用var cache = new BoundedLruCache<int, Product>( maxSize: 1000, useLockFreeApproximation: true);
// ✅ 正确:使用幂等工厂cache.GetOrAdd(id, k => _repository.Find(k)); // 多次调用结果相同
// ❌ 错误:非幂等工厂cache.GetOrAdd(id, k =>{ var product = new Product(); product.Id = GenerateNewId(); // 每次调用生成不同 ID return product;});| 场景 | 配置建议 |
|---|---|
| 小缓存(< 100) | 默认配置即可 |
| 中等缓存(100-1000) | segments: 2-4 |
| 大缓存(> 1000) | segments: 4-8 |
| 极高并发 | useLockFreeApproximation: true |
- 容量限制:当缓存满时,最久未访问的项会被自动移除
- 分段策略:小缓存(maxSize < 16)使用单段以保证确定性 LRU 语义
- 无锁模式:工厂方法可能被多次调用,建议使用幂等工厂
- 线程安全:所有操作都是线程安全的
- 内存管理:及时释放不再使用的缓存实例