ASP.NET Boilerplate Per Request Redis Cache将 Redis 查询按 HTTP 请求本地化缓存以消除性能瓶颈【免费下载链接】aspnetboilerplateASP.NET Boilerplate - Web Application Framework项目地址: https://gitcode.com/gh_mirrors/as/aspnetboilerplate导读本文围绕 ASP.NET BoilerplateABP框架提供的Per Request Redis Cache每次请求 Redis 缓存机制展开它在默认 Redis 缓存ICacheManager Redis之上叠加了一层以HttpContext为作用域的本地缓存层使同一个 HTTP 请求内对同一 Redis 键的重复读取只真正访问 Redis 一次。读完本文你将掌握Abp.AspNetCore.PerRequestRedisCache包的接入方式、IAbpPerRequestRedisCacheManager与ICacheManager的关系、如何通过Configuration.Caching.UseRedis(usePerRequestRedisCache: true)一键替换全局缓存实现以及这套机制在源码层面AbpPerRequestRedisCache.cs究竟是如何工作的。一、问题背景Redis 查询同样存在性能成本ABP 的缓存抽象默认基于进程内MemoryCache实现详见 Caching.md。当你通过默认 Redis 缓存实现ICacheManager搭配 Redis获取缓存对象时每一次缓存读写操作都会真正发送到 Redis 服务器。虽然 Redis 通常比读取数据库、发起 API 调用这类操作快得多但 Redis 查询本身依然有性能开销且该开销随 Redis 服务器与业务服务器之间的网络距离延迟而波动。当一个项目在单个请求内对 Redis 发起大量查询时这些开销会被放大有时会引发性能问题甚至成为瓶颈。典型场景本地化Localization字典的反复读取原文档给出一个非常贴合实际的例子假设你正在做本地化缓存并把本地化键值字典保存在 Redis 中。每次页面加载的本地化流程可能需要几十次读取 Redis 来获取相关本地化键的值。如果不加处理每次页面加载都要向 Redis 发起几十次网络往返。如果使用 Per Request Redis Cache只需第一次从 Redis 拉取一次数据之后的查询全部在本地HttpContext内完成。适用前提瞬间变化不敏感的数据。这类非关键数据非常适合用本机制优化。二、核心概念IAbpPerRequestRedisCacheManagerIAbpPerRequestRedisCacheManager接口定义见 IAbpPerRequestRedisCacheManager.cs在用法上与ICacheManager完全一致返回的也是相同的数据类型。唯一的区别在于当缓存对象由IAbpPerRequestRedisCacheManager创建时Redis 查询会被缓存在HttpContext上。因此单个 HTTP 请求内对同一个键只会向 Redis 发送一次查询后续重复查询直接复用第一次的 Redis 响应结果从而提升性能。接口的源码注释对此有非常明确的界定值得反复阅读从IAbpPerRequestRedisCacheManager请求得到的ICache对象会按 HTTP 上下文存储 Redis 缓存并且在当前 HTTP 上下文存续期间不会再次从 Redis 拉取。不适用于数据在一个项目实例上发生变化、可能引起另一个项目实例出错的场景即跨实例强一致场景。只推荐用于可以确定缓存在当前 HTTP 请求结束前不会变化的值或者本身不重要的值例如某些 UI 视图设置。否则请继续使用ICacheManager。接口本身继承自ICacheManager因此对调用方而言它是透明的替换品。三、接入步骤三步启用 Per Request Redis Cache第 1 步引入 NuGet 包Abp.AspNetCore.PerRequestRedisCache该包在仓库中的对应源码目录为 src/Abp.AspNetCore.PerRequestRedisCache。第 2 步声明模块依赖在你的模块上添加对AbpAspNetCorePerRequestRedisCacheModule的DependsOn依赖[DependsOn(typeof(AbpAspNetCorePerRequestRedisCacheModule))] public class MyModule : AbpModule { //... }从源码看AbpAspNetCorePerRequestRedisCacheModule.cs该模块自身依赖AbpRedisCacheModule并在PreInitialize阶段将IAbpPerRequestRedisCacheManager注册为AbpPerRequestRedisCacheManager实现。也就是说Redis 相关基础能力是这个模块的硬依赖必须同时具备。第 3 步注入并使用IAbpPerRequestRedisCacheManagerpublic class TestAppService : ApplicationService { private readonly IAbpPerRequestRedisCacheManager _cacheManager; public TestAppService(IAbpPerRequestRedisCacheManager cacheManager) { _cacheManager cacheManager; } private ICache GetMyCache() _cacheManager.GetCache(MyCache); // get cache using IAbpPerRequestRedisCacheManager }拿到ICache之后Get / GetOrDefault / Set / Remove / Clear以及各自的 async 版本等所有缓存能力都可以直接使用。ABP 缓存抽象的具体用法ICacheManager、ICache、ITypedCache、过期时间配置、EntityCache等可参考 Caching.md。重要前提必须先启用 Redis要使用IAbpPerRequestRedisCacheManager必须启用 Redis参见 Caching.md 的 Redis 集成章节。因为 Per Request 层只是本地快照底层真正的数据源依然是 Redis——本机制优化的是读 Redis的频次而不是取代 Redis。四、一键替换UseRedis(usePerRequestRedisCache: true)如果你希望把项目当前使用的整个缓存实现替换为 Per Request Redis Cache而不改动任何业务代码只需在模块的PreInitialize中启用 Redis 时传入参数Configuration.Caching.UseRedis(usePerRequestRedisCache: true);这一行代码会将ICacheManager替换为IAbpPerRequestRedisCacheManager你的整个项目将开始以 Per Request 方式工作所有通过ICacheManager注入的代码无需修改。源码层面的替换机制从 AbpPerRequestRedisCacheExtensions.cs 可以看到UseRedis有两个重载public static void UseRedis(this ICachingConfiguration cachingConfiguration, bool usePerRequestRedisCache) public static void UseRedis(this ICachingConfiguration cachingConfiguration, ActionAbpRedisCacheOptions optionsAction, bool usePerRequestRedisCache)当usePerRequestRedisCache为true时扩展方法实际完成三件事注册替换实现IocManager.RegisterIfNotICacheManager, AbpPerRequestRedisCacheManagerForReplacement()。AbpPerRequestRedisCacheManagerForReplacement是 AbpPerRequestRedisCacheManager.cs 中定义的内部类继承自AbpPerRequestRedisCacheManager专门用于替换全局ICacheManager从而让所有依赖ICacheManager的代码自动获得 Per Request 行为。同步注册在线客户端存储IocManager.RegisterIfNotIOnlineClientStore, RedisOnlineClientStore()保证实时通信相关在线客户端数据也走 Redis。应用 Redis 选项通过optionsAction(iocManager.ResolveAbpRedisCacheOptions())应用配置连接字符串、DatabaseId 等。当usePerRequestRedisCache为false时则退化为普通的UseRedis(optionsAction)。注意替换语义是从此刻起项目中所有缓存都按每次请求本地化。这会影响所有被缓存数据的跨请求新鲜度语义务必先评估你的业务数据是否适合见第六节的适用边界。五、实现原理HttpContext.Items上的请求级缓存层Per Request Redis Cache 的真正实现类是AbpPerRequestRedisCacheAbpPerRequestRedisCache.cs它继承自标准的 Redis 缓存实现AbpRedisCacheAbpRedisCache.cs基于 StackExchange.Redis并实现了IAbpPerRequestRedisCache。在继承链上AbpPerRequestRedisCacheManager继承自CacheManagerBaseICacheCacheManagerBase.cs单例生命周期按缓存名管理ICache实例。5.1 本地缓存键的设计private const string AbpPerRequestRedisCachePrefix AbpPerRequestRedisCache:; protected virtual string GetPerRequestRedisCacheKey(string key) { return AbpPerRequestRedisCachePrefix NormalizeKey(key).ToString(); }本地缓存键由固定前缀AbpPerRequestRedisCache:加上经过标准化的 Redis 键组成。NormalizeKey是底层AbpRedisCache的能力会结合缓存名、多租户开关IMultiTenancyConfig对键进行统一规范化见 AbpRedisCache.cs因此多租户场景下不同租户的数据天然隔离不会互相污染。5.2 读取路径命中本地则不再访问 RedisTryGetValue的流程省略异常分支后如下通过注入的IHttpContextAccessor获取当前HttpContext若为 null例如后台任务、无 HTTP 上下文直接回退到base.TryGetValue走真正的 Redis。构造本地键检查httpContext.Items命中取出之前保存的ConditionalValueobject直接返回全程不碰 Redis。未命中调用base.TryGetValue真正访问 Redis然后把结果连同是否存在标记一起写入httpContext.Items供本请求后续使用。这里使用ConditionalValueobject而非普通对象是为了区分缓存了 null与从未缓存过——两者在httpContext.Items中的呈现必须不同否则无法判断是否真的需要回源 Redis。5.3 写入路径双写本地与 RedisSet/SetAsync在写入 Redis 成功后会把同样的值同步写入httpContext.Items键同样经过本地化处理。这意味着本请求内先写后读是自洽的写入后立刻读取不会再访问 Redis。5.4 失效路径Remove 与 Clear 同步清理本地快照Remove/RemoveAsync先删除 Redis 中的键再同步移除httpContext.Items中对应的本地条目。Clear调用base.Clear()清空 Redis 后遍历httpContext.Items删除所有以GetPerRequestRedisCacheKey()为前缀的本地条目。这样保证了失效操作在两个层面的一致性避免出现Redis 已删、本地仍返回旧值的脏读。5.5 批量操作与异常兜底提供批量版本TryGetValues/TryGetValuesAsync对缺失键一次性批量回源 Redis再整体回填HttpContext.Items、Set(pairs)/SetAsync(pairs)、Remove(keys)/RemoveAsync(keys)减少网络往返次数。所有读写方法都捕获ObjectDisposedException一旦出现例如请求结束、HttpContext被销毁后仍有访问会记录 Warn 日志并回退到 Redis 直接操作保证健壮性。5.6 生命周期与缓存名规则AbpPerRequestRedisCacheManager以单例注册继承CacheManagerBaseICache的ISingletonDependency在PreInitialize中通过IocManager.RegisterIfNotAbpPerRequestRedisCache(DependencyLifeStyle.Transient)注册具体缓存对象CreateCacheImplementation通过IocManager.ResolveAbpPerRequestRedisCache(new { name })按名创建。缓存名大小写敏感MyCache与MYCACHE是两个不同的缓存同一个名字在应用生命周期内共享同一缓存对象——只是其内容被本地化到每次请求。六、适用边界什么时候该用、什么时候不该用结合接口注释与实现Per Request Redis Cache 适合与不适合的场景可对比如下场景是否推荐原因本地化字典、UI 视图设置等读多写少、变化不敏感的数据推荐请求内首次读取后全部命中本地吞吐显著提升同一个键在单个请求内被反复读取几十次量级推荐从几十次 Redis 往返降为1 次 Redis 往返 N 次内存读取数据可能被其他实例/其他请求频繁更新且要求读取到最新值不推荐请求内复用旧快照可能导致跨实例不一致后台任务、Worker 等无HttpContext的执行环境自动回退源码中HttpContext null时直接走 Redis本地层不生效数据语义要求写入后其他实例立即可见不推荐必须等到当前请求结束本地快照才会失效一句话总结Per Request Redis Cache 是对每次请求内重复读 Redis的优化代价是牺牲请求内的数据新鲜度。只有当你确认缓存数据在本请求生命周期内不会变化或变化无伤大雅时才应使用它。七、配套 Redis 基础配置由于 Per Request 层构建在标准 Redis 缓存之上你需要先完成 Caching.md 中Redis Cache Integration的基础配置引入Abp.RedisCacheNuGet 包并在模块上声明DependsOn(typeof(AbpRedisCacheModule))。在PreInitialize中调用Configuration.Caching.UseRedis()。配置连接字符串与数据库 IDadd nameAbp.Redis.Cache connectionStringlocalhost/ add keyAbp.Redis.Cache.DatabaseId value2/ASP.NET Core 下可用委托方式覆盖Configuration.Caching.UseRedis(options { options.ConnectionString _appConfiguration[RedisCache:ConnectionString]; options.DatabaseId _appConfiguration.GetValueint(RedisCache:DatabaseId); });不同DatabaseId可用于在同一台服务器上隔离出不同的键空间。默认缓存过期时间为 60 分钟滑动过期可通过Configuration.Caching.ConfigureAll/Configure(MyCache, ...)按全局或按缓存名调整。所有这些配置对 Per Request 模式同样有效——本地快照层不改变缓存项的过期语义过期仍由 Redis 决定。八、总结Per Request Redis Cache 是 ABP 针对Redis 高频读取场景给出的低成本优化方案它不改动缓存抽象与业务代码仅通过一个实现类AbpPerRequestRedisCache在HttpContext.Items上维护请求级快照把单请求内对同一键的 N 次 Redis 查询收敛为1 次 Redis 查询 N-1 次内存命中同时通过键前缀规范化天然兼容多租户通过ObjectDisposedException兜底和HttpContext null回退保证在后台环境下的安全性。接入路径有两种按需选择局部使用引入Abp.AspNetCore.PerRequestRedisCache包 → 声明DependsOn(typeof(AbpAspNetCorePerRequestRedisCacheModule))→ 注入IAbpPerRequestRedisCacheManager获取缓存全局替换在PreInitialize调用Configuration.Caching.UseRedis(usePerRequestRedisCache: true)全项目自动切换到 Per Request 模式。核心取舍始终只有一个用请求内的数据新鲜度换取请求内 Redis 往返次数的大幅下降。把这条原则刻在脑子里就能在 ABP 项目中放心地用它解决 Redis 读密集场景的性能问题。【免费下载链接】aspnetboilerplateASP.NET Boilerplate - Web Application Framework项目地址: https://gitcode.com/gh_mirrors/as/aspnetboilerplate创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
企业数字化 ERP 产品动态
相关推荐
Java物联网环境监测系统源码解析:从串口采集到Spring Boot展示 简介:这份基于Java语言的物联网环境监测系统设计源码,面向具备Java基础、希望实践物联网项目开发的学生与开发者,可用于课程设计、毕业设计或环境监测类应用的原型搭建。资源包共43个文件,约3.51MB,其中15个Java源文件… · 2026/9/23 17:02:21
豆瓣阅读app源码解析与重构避坑速查手册 豆瓣阅读app源码解析与重构避坑速查手册 凌晨两点,对着满屏红色的StackTrace抓狂?别急,这行代码的报错信息往往比问题本身更让人头秃。在拆解【豆瓣阅读app】这类复杂移动端应用时,我们常陷入一个误区:把精力全耗在UI还原上,却忽略了… · 2026/9/23 17:02:14
谐波潮流计算与解耦方法详解:从原理到Python实现 简介:面向电力系统谐波分析场景的MATLAB程序包,专注谐波潮流计算与谐波解耦算法,适合电气工程专业学生、科研人员以及从事电能质量治理的工程师使用。当前电网中开关电源、整流器等非线性负载大量接入,谐波畸变已成为影响电能质量… · 2026/9/23 17:02:14
波士顿房价预测与知识图谱融合实践 简介:本资源是一份面向人工智能初学者与高校实验教学的《深度学习知识图谱实验手册》,聚焦机器学习基础建模能力培养,通过波士顿房价预测(回归任务)与鸢尾花分类(聚类/无监督学习)两大经典实验&… · 2026/9/23 17:42:24
钉钉网页版登录入口与使用全攻略:从登录到功能边界 1. 从"钉钉网页版登陆地址"这个搜索词说起"钉钉网页版登陆地址"——这七个字看起来简单,但如果你在搜索引擎里敲过这个词,大概率是因为遇到了下面几种情况之一:公司电脑不让装客户端,想临时用浏览器处理一下审… · 2026/9/23 17:42:17
U-Net轻量语义分割实现工地裂缝像素级识别 简介:本资源是西南交通大学《智能建造与运维养》课程的实践型作业文档,面向土木工程、智能建造及相关专业本科生,聚焦结构表面裂缝的像素级智能识别问题,系统融合卷积神经网络、图像语义分割与TensorFlow工程实现。文档完整覆盖从… · 2026/9/23 17:42:17
AI芯片选型新标准:内存带宽才是关键指标 1. 这个参数,正在悄悄改写芯片选型的底层逻辑“还在只看主频选芯片?”——这句话我去年在给一家智能硬件初创公司做技术顾问时,当着CTO和三位硬件工程师的面直接抛出来,现场安静了三秒。不是因为大家没听过,而是因为所… · 2026/9/23 17:42:17
5个坑搞定极品飞车5下载源码剖析 5个坑搞定极品飞车5下载源码剖析 别再去翻那几百页的官方文档了,真正能让你在实战项目里站稳脚跟的,往往是那些被忽略的细节。 做后端开发,我们总以为“下载”就是 file.download() 这么简单。但当你接手一个类似 极品飞车5下载… · 2026/9/23 17:42:17
使用 LanceDB JavaScript SDK 构建向量检索应用:安装、连接、建表与向量搜索实战 向量数据库数据库人工智能后端 【免费下载链接】lancedb Developer-friendly OSS embedded retrieval library for multimodal AI. Search More; Manage Less. 项目地址: https://gitcode.com/gh_mirrors/la/lancedb 点击查看 免费下载 本篇文章以 LanceDB 官方 Ja… · 2026/9/23 17:42:17
3招搞定手机怎么下载微信面试难题实战项目解析 3招搞定手机怎么下载微信面试难题实战项目解析 面试被问“手机怎么下载微信”背后的原理,90%的人答不上来。别笑,这看似弱智的问题,实则是考察你对移动应用分发机制、安全校验及网络协议理解的试金石。我带过不少校招新人,他们背了八股文,却连一个A… · 2026/9/23 0:00:03
你有新短消息请注意查收:3个新手避坑指南搞定消息系统选型 你有新短消息请注意查收:3个新手避坑指南搞定消息系统选型 面试被问“高并发下如何保证消息不丢失”,你张口就是“用Redis”,结果面试官追问“如果Redis宕机了怎么办”,你瞬间卡壳。这种场景太常见了,很多新手在背八股文时,只记住了技术名词… · 2026/9/23 0:00:29