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

ABP - 缓存(Caching)[IDistributedCache、ICacheManager、ICacheKeyNormalizer、[Cache]、[CacheInvalidate]]

(一)缓存(Caching)

核心辅助类

  • IDistributedCache:分布式缓存(基于Redis等)。
  • ICacheManager:缓存管理器(支持多级缓存)。
  • [Cache]:方法缓存特性。
  • ICacheKeyNormalizer:缓存键标准化器,自动添加租户前缀(多租户场景)或应用前缀。
  • [CacheInvalidate]:缓存失效特性(修改数据时自动清除对应缓存)。

缓存(Caching)核心类详解与实战示例

缓存是提升系统性能的关键技术,通过将频繁访问的数据暂时存储在内存(或Redis等中间件)中,减少对数据库的访问次数。ABP框架提供了一套灵活的缓存机制,以下结合IDistributedCacheICacheManager[Cache]等核心类。

一、核心概念:为什么需要缓存?(生活例子)

想象你开了一家奶茶店:

  • 顾客经常问“珍珠奶茶多少钱?”(高频查询),你每次都去查价目表(数据库),效率低;
  • 你把价目表贴在吧台(缓存),顾客再问时直接看吧台,不用反复查价目表,速度快多了。

程序中的缓存也是同理:对“不常变化但经常查询的数据”(如商品分类、字典表),第一次查询后存到缓存,后续直接从缓存取,减少数据库压力。

二、核心类说明

类/特性 核心作用 通俗理解
IDistributedCache 分布式缓存接口(支持Redis、Memcached等) 多服务器共享的“公共缓存”(如集群部署时用)
ICacheManager 缓存管理器(支持内存缓存、多级缓存) 管理不同类型的缓存,方便统一操作
[Cache] 方法缓存特性(自动缓存方法返回值) 给方法加个标签,自动缓存结果,不用写代码
[CacheInvalidate] 缓存失效特性(修改数据时自动删缓存) 数据变了,自动删掉旧缓存,避免返回脏数据
ICacheKeyNormalizer 缓存键标准化(自动加前缀) 给缓存键加“标签”(如租户ID),避免键冲突

三、实战示例:从基础到进阶

1. [Cache]:最简单的方法缓存(一行代码搞定)

给查询方法加[Cache]特性,框架会自动缓存方法的返回值,下次调用时直接返回缓存结果,不执行方法体。

示例:缓存商品分类列表(高频查询,低频修改)

using Volo.Abp.Caching;
using Volo.Abp.Application.Services;public class ProductCategoryAppService : ApplicationService
{private readonly IRepository<ProductCategory, Guid> _categoryRepo;public ProductCategoryAppService(IRepository<ProductCategory, Guid> categoryRepo){_categoryRepo = categoryRepo;}// 加[Cache]特性:自动缓存返回结果,默认缓存10分钟[Cache(SlidingExpiration = 10)] // SlidingExpiration:10分钟内没人访问就失效public async Task<List<ProductCategoryDto>> GetAllCategoriesAsync(){// 第一次调用会执行:查数据库var categories = await _categoryRepo.GetListAsync();return ObjectMapper.Map<List<ProductCategory>, List<ProductCategoryDto>>(categories);}
}

效果:

  • 第一次调用GetAllCategoriesAsync():查数据库,结果存到缓存;
  • 10分钟内再次调用:直接从缓存返回,不查数据库;
  • 10分钟内没人调用:缓存自动失效,下次重新查数据库。

2. [CacheInvalidate]:数据更新时自动删缓存

当数据被修改(新增/更新/删除)时,需要删除旧缓存,否则会返回过时数据。[CacheInvalidate]特性可自动完成这个操作。

示例:修改分类后删除缓存

public class ProductCategoryAppService : ApplicationService
{// 新增分类后,删除GetAllCategoriesAsync方法的缓存[CacheInvalidate(MethodName = nameof(GetAllCategoriesAsync))]public async Task CreateCategoryAsync(CreateCategoryInput input){var category = new ProductCategory { Name = input.Name };await _categoryRepo.InsertAsync(category);}// 更新分类后,删除GetAllCategoriesAsync方法的缓存[CacheInvalidate(MethodName = nameof(GetAllCategoriesAsync))]public async Task UpdateCategoryAsync(Guid id, UpdateCategoryInput input){var category = await _categoryRepo.GetAsync(id);category.Name = input.Name;await _categoryRepo.UpdateAsync(category);}// 之前的查询方法(带[Cache])[Cache(SlidingExpiration = 10)]public async Task<List<ProductCategoryDto>> GetAllCategoriesAsync(){// ...}
}

效果:

  • 调用CreateCategoryAsyncUpdateCategoryAsync后,框架会自动删除GetAllCategoriesAsync方法的缓存;
  • 下次查询时会重新从数据库获取最新数据,并更新缓存,保证数据一致性。

3. ICacheManager:手动控制缓存(灵活场景)

当需要更灵活的缓存控制(如缓存部分数据、条件性缓存)时,用ICacheManager手动操作缓存。

示例:手动缓存用户信息

using Volo.Abp.Caching;
using Volo.Abp.DependencyInjection;public class UserCacheService : ITransientDependency
{private readonly ICacheManager _cacheManager;private readonly IRepository<IdentityUser, Guid> _userRepo;// 注入缓存管理器public UserCacheService(ICacheManager cacheManager, IRepository<IdentityUser, Guid> userRepo){_cacheManager = cacheManager;_userRepo = userRepo;}// 获取用户信息(优先从缓存取)public async Task<IdentityUser> GetUserByIdAsync(Guid userId){// 1. 获取一个缓存实例(可理解为“缓存容器”,名称自定义,如"UserCache")var cache = _cacheManager.GetCache("UserCache");// 2. 尝试从缓存获取:key是userId,不存在则执行委托查数据库并缓存return await cache.GetOrAddAsync(key: userId.ToString(), // 缓存键(用userId当唯一标识)factory: async () => await _userRepo.GetAsync(userId), // 缓存不存在时执行的逻辑options: () => new CacheOptions { AbsoluteExpiration = DateTime.Now.AddHours(1) } // 1小时后绝对失效);}// 手动删除用户缓存(如用户信息更新后)public async Task RemoveUserCacheAsync(Guid userId){var cache = _cacheManager.GetCache("UserCache");await cache.RemoveAsync(userId.ToString()); // 删除指定key的缓存}
}

核心方法:

  • GetOrAddAsync:尝试从缓存获取,不存在则执行factory方法查询并缓存;
  • RemoveAsync:删除指定键的缓存;
  • SetAsync:手动设置缓存(GetOrAddAsync已包含此逻辑,一般不用单独调用)。

4. IDistributedCache:分布式缓存(集群部署必备)

当系统部署在多台服务器(集群)时,内存缓存(ICacheManager默认是内存缓存)无法共享,需用IDistributedCache(如Redis)实现缓存共享。

示例:用Redis缓存全局配置

using Microsoft.Extensions.Caching.Distributed;
using System.Text.Json;
using Volo.Abp.DependencyInjection;public class GlobalConfigService : ITransientDependency
{private readonly IDistributedCache _distributedCache;private readonly IRepository<GlobalConfig, Guid> _configRepo;// 注入分布式缓存(默认是Redis,需在配置文件中配置)public GlobalConfigService(IDistributedCache distributedCache, IRepository<GlobalConfig, Guid> configRepo){_distributedCache = distributedCache;_configRepo = configRepo;}// 获取全局配置(从Redis缓存)public async Task<GlobalConfigDto> GetGlobalConfigAsync(){// 1. 从Redis获取缓存(键是"GlobalConfig")var cachedData = await _distributedCache.GetStringAsync("GlobalConfig");if (!string.IsNullOrEmpty(cachedData)){// 缓存存在:反序列化为对象并返回return JsonSerializer.Deserialize<GlobalConfigDto>(cachedData);}// 2. 缓存不存在:查数据库var config = await _configRepo.GetAsync(x => x.IsDefault);var configDto = ObjectMapper.Map<GlobalConfig, GlobalConfigDto>(config);// 3. 存入Redis缓存(设置1小时过期)await _distributedCache.SetStringAsync(key: "GlobalConfig",value: JsonSerializer.Serialize(configDto),options: new DistributedCacheEntryOptions{AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1) // 1小时后失效});return configDto;}
}

配置Redis(appsettings.json):

"Redis": {"Configuration": "localhost:6379", // Redis服务器地址"InstanceName": "MyApp_" // 实例名(避免多个应用共用Redis时键冲突)
}

5. ICacheKeyNormalizer:避免缓存键冲突

在多租户系统或模块化应用中,不同租户/模块可能用相同的缓存键(如都用"Config"),导致冲突。ICacheKeyNormalizer会自动给缓存键加前缀(如租户ID、模块名)。

示例:自动添加租户前缀

ABP默认的CacheKeyNormalizer会处理多租户场景:

  • 租户1的缓存键:Tenant_1:UserCache:123
  • 租户2的缓存键:Tenant_2:UserCache:123
  • 避免不同租户的数据互相干扰。

自定义缓存键前缀(可选):

public class MyCacheKeyNormalizer : CacheKeyNormalizerBase
{public MyCacheKeyNormalizer(ICurrentTenant currentTenant) : base(currentTenant){}public override string NormalizeKey(CacheKeyNormalizeContext context){// 自定义前缀:模块名 + 租户ID + 原始键return $"MyModule:{CurrentTenant.Id}:{context.Key}";}
}// 注册自定义缓存键标准化器(在模块中)
public override void ConfigureServices(ServiceConfigurationContext context)
{context.Services.Replace(ServiceDescriptor.Singleton<ICacheKeyNormalizer, MyCacheKeyNormalizer>());
}

四、缓存的核心配置(appsettings.json)

"AbpCaching": {"CacheExpirationDefaults": {"Default": 60, // 默认缓存时间(分钟)"Entity": 360, // 实体缓存默认时间(分钟)"Permission": 360, // 权限缓存默认时间(分钟)"Setting": 360, // 设置缓存默认时间(分钟)"User": 120 // 用户信息缓存默认时间(分钟)}
}

五、新手避坑指南

  1. 缓存数据必须可序列化:存入分布式缓存(如Redis)的数据需要能序列化为字符串(避免存入复杂对象如DataTable);
  2. 缓存失效策略要合理
    • 高频访问、低频修改的数据:缓存时间可以长(如1小时);
    • 低频访问、高频修改的数据:不建议缓存(或缓存时间短,如1分钟);
  3. 避免缓存“热点数据”:如首页Banner、热门商品,更新时要及时用[CacheInvalidate]删除缓存;
  4. 分布式缓存配置:集群部署必须用IDistributedCache(Redis),否则多服务器缓存不一致;
  5. 不要缓存太大的数据:如整个表的10万条记录,会占用过多内存,建议分页缓存。

总结

  • 简单场景:用[Cache]+[CacheInvalidate],零代码实现缓存;
  • 灵活场景:用ICacheManager手动控制缓存(如部分数据缓存);
  • 集群场景:用IDistributedCache(Redis)实现缓存共享;
  • 多租户/模块化:依赖ICacheKeyNormalizer避免键冲突。

通过合理使用缓存,能显著减少数据库压力,提升系统响应速度。需要“缓存与数据库一致性保障”的深入讲解可以告诉我。

(二)ABP 配置 Redis

抱歉之前没详细讲Redis配置!在ABP中使用分布式缓存(Redis)需要几个关键步骤,包括安装依赖、配置连接、注册服务,下面手把手你一步一步讲清楚,确保能跑通。

1、准备工作:安装Redis服务器

首先需要在你的服务器(或本地)安装Redis:

  • Windows:推荐用 Redis for Windows(下载.msi文件安装,默认端口6379);
  • Linux:直接用命令安装 sudo apt-get install redis-server
  • 安装后启动Redis,默认不需要密码(生产环境需配置密码)。

2、ABP项目中配置Redis的完整步骤

步骤1:安装Redis依赖包

在你的 应用层项目(如MyApp.Application)或 Web层项目(如MyApp.Web)中,安装ABP的Redis缓存包:

# 通过NuGet安装(Package Manager控制台)
Install-Package Volo.Abp.Caching.StackExchangeRedis# 或通过.NET CLI
dotnet add package Volo.Abp.Caching.StackExchangeRedis

步骤2:在模块中引入Redis模块

在你的 核心模块(如MyAppWebModule)中,通过[DependsOn]引入AbpCachingStackExchangeRedisModule,开启Redis缓存支持:

using Volo.Abp.Caching.StackExchangeRedis;
using Volo.Abp.Modularity;[DependsOn(typeof(AbpAspNetCoreModule),typeof(AbpCachingStackExchangeRedisModule) // 引入Redis缓存模块
)]
public class MyAppWebModule : AbpModule
{// 后续配置写在这里
}

步骤3:配置Redis连接(appsettings.json)

appsettings.json中添加Redis连接配置,告诉ABP如何连接你的Redis服务器:

{"Redis": {"Configuration": "localhost:6379", // Redis服务器地址+端口(默认6379)"InstanceName": "MyApp_" // 实例名(可选,用于区分多个应用的缓存键)},"AbpDistributedCacheOptions": {"KeyPrefix": "MyAppCache_" // 缓存键统一前缀(可选,进一步避免冲突)}
}

配置说明:

  • Configuration:Redis连接字符串,格式为"服务器地址:端口[,password=密码]"
    • 示例(带密码):"192.168.1.100:6379,password=myredis123"
    • 示例(集群):"192.168.1.101:6379,192.168.1.102:6379"
  • InstanceName:多个应用共用一个Redis时,用实例名区分(如"ShopApp_""AdminApp_");
  • KeyPrefix:所有缓存键会自动加上这个前缀(如"MyAppCache_User_123"),避免键重复。

步骤4:验证Redis是否生效

配置完成后,ABP会自动将IDistributedCache的实现替换为Redis缓存,无需额外代码。可以通过以下方式验证:

示例:存入缓存并在Redis中查看

using Microsoft.Extensions.Caching.Distributed;
using Volo.Abp.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc;public class RedisTestController : AbpController
{private readonly IDistributedCache _distributedCache;public RedisTestController(IDistributedCache distributedCache){_distributedCache = distributedCache;}// 访问此接口:将数据存入Redis[HttpGet("set-cache")]public async Task<IActionResult> SetCacheAsync(){// 存入缓存:键为"TestKey",值为"Hello Redis"await _distributedCache.SetStringAsync("TestKey", "Hello Redis",new DistributedCacheEntryOptions{AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10) // 10分钟后过期});return Ok("缓存已存入Redis");}// 访问此接口:从Redis读取缓存[HttpGet("get-cache")]public async Task<IActionResult> GetCacheAsync(){var value = await _distributedCache.GetStringAsync("TestKey");return Ok($"从Redis读取到的值:{value}");}
}

验证步骤:

  1. 启动项目,访问 https://localhost:端口/set-cache,返回“缓存已存入Redis”;
  2. 打开Redis客户端(如Windows的redis-cli.exe),输入命令 keys *,会看到类似 MyApp_MyAppCache_TestKey 的键(前缀由配置的InstanceNameKeyPrefix组成);
  3. 输入 get MyApp_MyAppCache_TestKey,会返回 Hello Redis,说明缓存成功存入Redis;
  4. 访问 https://localhost:端口/get-cache,会返回“从Redis读取到的值:Hello Redis”,验证读取成功。

步骤5:高级配置(可选)

如果需要更精细的Redis配置(如连接池大小、超时时间),可以在模块的ConfigureServices中手动配置:

public override void ConfigureServices(ServiceConfigurationContext context)
{var configuration = context.Services.GetConfiguration();// 手动配置RedisConfigure<AbpRedisCacheOptions>(options =>{// 从配置文件读取连接字符串options.Configuration = configuration["Redis:Configuration"];options.InstanceName = configuration["Redis:InstanceName"];// 高级选项:连接池大小(默认50)options.ConnectionPoolSize = 100;// 连接超时时间(毫秒)options.ConnectTimeout = 5000;// 同步超时时间(毫秒)options.SyncTimeout = 3000;});
}

3、常见问题与解决办法

  1. 无法连接Redis

    • 检查Configuration中的地址和端口是否正确(默认localhost:6379);
    • 确保Redis服务器已启动(Windows可在服务中查看“Redis”服务状态);
    • 关闭防火墙或开放6379端口。
  2. 缓存键重复

    • 务必配置InstanceNameKeyPrefix,尤其是多个应用共用一个Redis时;
    • 多租户系统中,ABP的ICacheKeyNormalizer会自动添加租户ID前缀,无需额外处理。
  3. 缓存数据序列化问题

    • IDistributedCache默认用JSON序列化,复杂对象(如DateTimeOffset)可能有兼容性问题;

    • 可自定义序列化器(如用Protobuf),提升性能和兼容性:

      Configure<AbpDistributedCacheOptions>(options =>
      {options.Serializer = new MyProtobufCacheSerializer(); // 自定义序列化器
      });
      

通过以上步骤,你的ABP项目就能成功集成Redis分布式缓存了。分布式缓存特别适合集群部署(多台服务器共享缓存),或需要持久化缓存(避免应用重启后缓存丢失)的场景。

http://www.hskmm.com/?act=detail&tid=38415

相关文章:

  • 《打造自己的 DeepSeek》第 1 期:为什么要打造自己的 DeepSeek?
  • ret2text
  • ABP - 异常处理(Exception Handling)[AbpExceptionFilter、UserFriendlyException、IExceptionSubscriber]
  • 2025年沸腾干燥机厂家权威推荐榜单:专业直销与高效节能技术深度解析,提供优质沸腾干燥设备及定制方案
  • CF Round 1046(#2135) 总结
  • 重组蛋白表达的几种类型介绍
  • ABP - 接口授权 [Authorize、AllowAnonymous、IPermissionChecker]
  • 日总结 17
  • Luogu P5479 [BJOI2015] 隐身术 题解 [ 紫 ] [ 多维 DP ] [ 交换维度 ] [ 后缀数组 ] [ 哈希 ]
  • 2025年10月23日
  • 杂题选做-3
  • 10.24每日总结
  • 利用Eval Villain挖掘CSPT漏洞的完整指南
  • Button按钮插入图片后仍有白色边框的解决办法
  • Hugo主题的修改和配置
  • 多元生成函数+多项式方程组——[AGC058D] Yet Another ABC String
  • ABP - JWT 鉴权(JWT Authentication)[AbpJwtBearerModule、JwtBearerOptions]
  • 最小生成树 kruskal算法
  • 【Java】Synchronized-你知道Java是如何上锁的吗?
  • Java中的字符串及相关类的介绍
  • ABP - 工作单元(Unit of Work)[UnitOfWorkAttribute、IUnitOfWorkManager、UnitOfWorkOptions]
  • LeetCode刷题笔记
  • [NOIP2023] 双序列拓展 题解
  • 洛谷 P9530 Fish 2
  • 洛谷 P7011 Escape
  • 你可以把它喂给AI让AI猜猜我在干什么
  • 【深入浅出Nodejs】异步非阻塞IO
  • 135. 分发糖果
  • 【Java-JMM】Happens-before原则
  • P6072 『MdOI R1』Path