ABP VNext + EF Core 二級緩存:提升查詢性能 🚀
📚 目錄
- ABP VNext + EF Core 二級緩存:提升查詢性能 🚀
- 引言 🚀
- 一、環境與依賴 🛠?
- 二、集成步驟 ??
- 2.1 安裝 NuGet 包
- 2.2 注冊緩存服務與攔截器
- 2.3 對特定查詢啟用緩存 🎯
- 三、緩存依賴與失效 🔄
- 四、性能對比測試 📈
- 4.1 測試環境 🖥?
- 4.2 對比指標 🔥
- 五、最佳實踐與注意事項 ??
- 六、高級配置 🧩
引言 🚀
TL;DR ?
- 集成
EFCoreSecondLevelCacheInterceptor
v5.3.1,為 ABP VNext 應用添加跨DbContext
、跨請求的二級緩存,顯著降低重復查詢開銷 - 幾行配置即可啟用內存或 Redis 緩存,并支持自動失效與手動失效策略 🔄
- 支持按實體類型或表名緩存,無需手動管理復雜緩存鍵 🛡?
- 實測:平均響應時間由 ~120 ms 降至 ~15 ms,QPS 從 ~500 提升至 ~3 500,數據庫訪問次數減少至 1 次/秒 📊
關系型數據庫在高并發場景下常見瓶頸包括 CPU、IO 與連接數。EF Core 默認僅在單個 DbContext
生命周期內緩存實體,請求結束后即釋放。引入二級緩存(跨 DbContext
、跨請求)可顯著減少重復查詢開銷,緩解數據庫壓力。
一、環境與依賴 🛠?
-
運行平臺:.NET 6.0 LTS + ABP VNext 6.x
-
EF Core 版本:6.x
-
EFCoreSecondLevelCacheInterceptor:5.3.1
-
緩存提供者:
- 內存:
EFCoreSecondLevelCacheInterceptor.MemoryCache
- Redis:
EFCoreSecondLevelCacheInterceptor.StackExchange.Redis
- 內存:
-
其他依賴:
Volo.Abp.EntityFrameworkCore
-
ABP CLI:
Volo.Abp.Cli
v6.x -
前提:項目已集成 EF Core 與 ABP 基礎模塊,已配置連接字符串與常規
DbContext
-
注意:如需在 .NET 7/8 下使用,請升級到 ABP 7.x 或 ABP 8.x 🔄
二、集成步驟 ??
2.1 安裝 NuGet 包
dotnet add package EFCoreSecondLevelCacheInterceptor --version 5.3.1
dotnet add package EFCoreSecondLevelCacheInterceptor.MemoryCache # 內存緩存
# 或
dotnet add package EFCoreSecondLevelCacheInterceptor.StackExchange.Redis # Redis 緩存
2.2 注冊緩存服務與攔截器
在 ABP 模塊(如 MyProjectEntityFrameworkCoreModule
)的 ConfigureServices
方法中:
public override void ConfigureServices(ServiceConfigurationContext context)
{// 1. 添加二級緩存服務context.Services.AddEFSecondLevelCache(options =>options.UseMemoryCacheProvider().ConfigureLogging(false) // 生產環境關閉日志.UseCacheKeyPrefix("EF_") // 統一前綴,便于分區管理.UseDbCallsIfCachingProviderIsDown(TimeSpan.FromMinutes(1)) // 緩存不可用時回退數據庫.CacheAllQueries(CacheExpirationMode.Absolute, TimeSpan.FromMinutes(5)) // 全局緩存所有查詢.AllowCachingWithExplicitTransactions(true) // 顯式事務中也可緩存); // 2. 注冊 DbContext 并注入攔截器(僅針對 MyDbContext)context.Services.AddAbpDbContext<MyDbContext>(options =>{options.AddDefaultRepositories();});context.Services.Configure<AbpDbContextOptions>(opts =>{opts.Configure<MyDbContext>(config =>{config.DbContextOptions.UseSqlServer(context.Services.GetConfiguration().GetConnectionString("Default")).AddInterceptors(context.Services.GetRequiredService<SecondLevelCacheInterceptor>());});});
}
2.3 對特定查詢啟用緩存 🎯
// 使用全局策略(5 分鐘絕對過期)
var products = await _productRepository.WithDetails().Cacheable().ToListAsync();// 自定義滑動過期 1 分鐘
var recentOrders = await _orderRepository.Where(o => o.CreatedDate > since).Cacheable(CacheExpirationMode.Sliding, TimeSpan.FromMinutes(1)).ToListAsync();
三、緩存依賴與失效 🔄
-
自動失效:攔截所有
SaveChanges()
/SaveChangesAsync()
,根據受影響表自動清除相關緩存,無需額外配置 -
批量操作限制:EF Core 的
ExecuteUpdate()
與ExecuteDelete()
繞過 ChangeTracker,不會觸發緩存失效,需手動清理:await context.Blogs.Where(b => b.IsObsolete).ExecuteUpdateAsync(s => s.SetProperty(b => b.IsActive, false)); _cacheServiceProvider.ClearAllCachedEntries();
-
按類型或表名緩存:
services.AddEFSecondLevelCache(options => {options.UseMemoryCacheProvider().CacheQueriesContainingTypes(CacheExpirationMode.Absolute, TimeSpan.FromMinutes(30),typeof(Product), typeof(Order)).CacheQueriesContainingTableNames(CacheExpirationMode.Absolute, TimeSpan.FromMinutes(30),TableNameComparison.ContainsOnly, "Products", "Orders"); });
-
手動清理示例:在服務中注入并使用
IEFCacheServiceProvider
public class ProductAppService : ApplicationService {private readonly IEFCacheServiceProvider _cacheServiceProvider;public ProductAppService(IEFCacheServiceProvider cacheServiceProvider){_cacheServiceProvider = cacheServiceProvider;}public void RefreshProductCache(){_cacheServiceProvider.ClearAllCachedEntries(); // 清除所有緩存_cacheServiceProvider.ClearCacheByPrefix("EF_Products"); // 按前綴清理} }
四、性能對比測試 📈
4.1 測試環境 🖥?
- 機房環境:Windows Server 2019,Intel Xeon Gold 6248(8 核/16 線程),32 GB RAM
- 數據庫:SQL Server 2019
- 數據量:100 萬條訂單記錄
- 測試工具:自編腳本 +
Stopwatch
// 預熱
await WarmUpDbAsync();// 測試 1,000 次請求
var sw = Stopwatch.StartNew();
for (int i = 0; i < 1000; i++)
{await _orderRepository.WithDetails().Cacheable().FirstOrDefaultAsync();
}
sw.Stop();
Console.WriteLine($"Elapsed: {sw.ElapsedMilliseconds} ms");
控制臺輸出示例
Warm-up completed.
Testing 1000 requests...
Elapsed: 15000 ms
聲明:以上測試基于串行腳本,僅對比緩存前后性能變化,實際生產環境下并發吞吐量會更高,讀者可使用 BenchmarkDotNet 進行多線程基準測試,并查看腳本和日志以復現。
4.2 對比指標 🔥
指標 | 無緩存模式 | 啟用二級緩存 |
---|---|---|
平均響應時間 | ~120 ms | ~15 ms |
QPS | ~500/sec | ~3 500/sec |
DB 訪問次數 | ~10 次/秒 | ~1 次/秒 |
五、最佳實踐與注意事項 ??
- 讀多寫少:Cache-Aside 模式僅適合讀多寫少場景,高寫場景慎用
- 緩存粒度:對超大結果集拆分分頁或按關鍵字段緩存,避免一次性加載過多數據
- 容量管理:根據業務規模調優 MemoryCache 或 Redis 參數(如內存上限、Eviction 策略),防止 OOM
- 雪崩/穿透:結合互斥鎖、預熱與空值緩存策略,保障系統穩定性
- 事務內緩存:顯式事務內查詢默認不緩存,啟用需調用
.AllowCachingWithExplicitTransactions(true)
六、高級配置 🧩
services.AddEFSecondLevelCache(options =>
{options.UseMemoryCacheProvider()// 跳過包含特定 SQL 的查詢緩存.SkipCachingCommands(cmd => cmd.Contains("NEWID()"))// 跳過空結果集的緩存.SkipCachingResults(result =>result.Value == null ||(result.Value is EFTableRows rows && rows.RowsCount == 0))// 避免某些更新命令觸發失效.SkipCacheInvalidationCommands(cmd =>cmd.Contains("UPDATE [Posts] SET [Views]"))// 動態覆蓋某些查詢的緩存策略.OverrideCachePolicy(context =>{if (context.IsCrudCommand) return null; // CRUD 不緩存if (context.CommandTableNames.Contains("posts"))return new EFCachePolicy().ExpirationMode(CacheExpirationMode.NeverRemove);return null;});
});
這些配置取自官方高級示例,可按需組合使用。