当前位置: 首页 > java >正文

ABP VNext + EF Core 二级缓存:提升查询性能

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 CLIVolo.Abp.Cli v6.x

  • 前提:项目已集成 EF Core 与 ABP 基础模块,已配置连接字符串与常规 DbContext

  • 注意:如需在 .NET 7/8 下使用,请升级到 ABP 7.x 或 ABP 8.x 🔄


二、集成步骤 ⚙️

项目启动
ConfigureServices
注册缓存服务
配置全局策略
UseMemoryCacheProvider/Redis
CacheAllQueries(...)
Configure
AddInterceptors(SecondLevelCacheInterceptor)
DbContext 工作流程

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

三、缓存依赖与失效 🔄

Cacheable
未命中
命中
触发拦截
Query
二级缓存
数据库
返回结果
SaveChanges()/SaveChangesAsync()
自动清理相关缓存
  • 自动失效:拦截所有 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;});
});

这些配置取自官方高级示例,可按需组合使用。


http://www.xdnf.cn/news/15551.html

相关文章:

  • FAISS深度学习指南:构建高效向量检索系统的完整方法论
  • Dify工作流节点-Agent
  • 二分查找算法(一)
  • 玩转Docker | 使用Docker部署vnStat网络流量监控服务
  • Python编程基础(六)| 用户输入和while循环
  • 算法精讲--正则表达式(二):分组、引用与高级匹配技术
  • GENERALIST REWARD MODELS: FOUND INSIDE LARGELANGUAGE MODELS
  • 7.17 滑动窗口 |assign |memo |pii bfs
  • 【Linux】如何使用nano创建并编辑一个文件
  • 使用token调用Spring OAuth2 Resource Server接口错误 insufficient_scope
  • Redis1:高并发与微服务中的键值存储利器
  • 第四章 OB SQL调优
  • OJ题目里面的复杂图形的输出类型的汇总展示(巧妙地利用对称性offset偏移量)
  • 轻松将文件从 iPhone 传输到 Mac
  • 牛客:HJ26 字符串排序[华为机考][map]
  • 暑期算法训练.2
  • ArcGISPro应用指南:使用ArcGIS Pro创建与优化H3六边形网格
  • PHP 社区正在讨论变更许可证,预计 PHP 9.0 版本将完全生效
  • 基于MATLAB的决策树DT的数据分类预测方法应用
  • 【Unity】Mono相关理论知识学习
  • SQL中对字符串字段模糊查询(LIKE)的索引命中情况
  • 第3章 Excel表格格式设置技巧
  • Win11专业工作站版安装配置要求
  • [NOIP][C++] 树的重心
  • Word 文档合并利器:基于 org.docx4j 的 Java 实现全解析
  • Java线程创建与运行全解析
  • GraphQL与REST在微服务接口设计中的对比分析与实践
  • Windows 启动后桌面黑屏,其他程序正常运行
  • Java接口:小白如何初步认识Java接口?
  • 一点点dd