(一)缓存(Caching)
核心辅助类:
IDistributedCache:分布式缓存(基于Redis等)。ICacheManager:缓存管理器(支持多级缓存)。[Cache]:方法缓存特性。ICacheKeyNormalizer:缓存键标准化器,自动添加租户前缀(多租户场景)或应用前缀。[CacheInvalidate]:缓存失效特性(修改数据时自动清除对应缓存)。
缓存(Caching)核心类详解与实战示例
缓存是提升系统性能的关键技术,通过将频繁访问的数据暂时存储在内存(或Redis等中间件)中,减少对数据库的访问次数。ABP框架提供了一套灵活的缓存机制,以下结合IDistributedCache、ICacheManager、[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(){// ...}
}
效果:
- 调用
CreateCategoryAsync或UpdateCategoryAsync后,框架会自动删除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 // 用户信息缓存默认时间(分钟)}
}
五、新手避坑指南
- 缓存数据必须可序列化:存入分布式缓存(如Redis)的数据需要能序列化为字符串(避免存入复杂对象如
DataTable); - 缓存失效策略要合理:
- 高频访问、低频修改的数据:缓存时间可以长(如1小时);
- 低频访问、高频修改的数据:不建议缓存(或缓存时间短,如1分钟);
- 避免缓存“热点数据”:如首页Banner、热门商品,更新时要及时用
[CacheInvalidate]删除缓存; - 分布式缓存配置:集群部署必须用
IDistributedCache(Redis),否则多服务器缓存不一致; - 不要缓存太大的数据:如整个表的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}");}
}
验证步骤:
- 启动项目,访问
https://localhost:端口/set-cache,返回“缓存已存入Redis”; - 打开Redis客户端(如Windows的
redis-cli.exe),输入命令keys *,会看到类似MyApp_MyAppCache_TestKey的键(前缀由配置的InstanceName和KeyPrefix组成); - 输入
get MyApp_MyAppCache_TestKey,会返回Hello Redis,说明缓存成功存入Redis; - 访问
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、常见问题与解决办法
-
无法连接Redis:
- 检查
Configuration中的地址和端口是否正确(默认localhost:6379); - 确保Redis服务器已启动(Windows可在服务中查看“Redis”服务状态);
- 关闭防火墙或开放6379端口。
- 检查
-
缓存键重复:
- 务必配置
InstanceName和KeyPrefix,尤其是多个应用共用一个Redis时; - 多租户系统中,ABP的
ICacheKeyNormalizer会自动添加租户ID前缀,无需额外处理。
- 务必配置
-
缓存数据序列化问题:
-
IDistributedCache默认用JSON序列化,复杂对象(如DateTimeOffset)可能有兼容性问题; -
可自定义序列化器(如用Protobuf),提升性能和兼容性:
Configure<AbpDistributedCacheOptions>(options => {options.Serializer = new MyProtobufCacheSerializer(); // 自定义序列化器 });
-
通过以上步骤,你的ABP项目就能成功集成Redis分布式缓存了。分布式缓存特别适合集群部署(多台服务器共享缓存),或需要持久化缓存(避免应用重启后缓存丢失)的场景。