缓存

ABP框架扩展了 ASP.NET Core的分布式缓存系统.

安装

默认情况下 启动模板 已经安装了这个包. 所以大部分情况下你不需要手动安装.

Volo.Abp.Caching是缓存系统的核心包. 可以使用 ABP CLI 的add-package命令将其安装到项目中:

  1. abp add-package Volo.Abp.Caching

你需要在包含 csproj 文件的文件夹中的命令行终端上运行此命令(请参阅 其他选项 安装).

使用方式

IDistributedCache 接口

ASP.NET Core 定义了 IDistributedCache 接口用于 get/set 缓存值. 但是会有以下问题:

  • 它适用于 byte 数组 而不是 .NET 对象. 因此你需要对缓存的对象进行序列化/反序列化.
  • 它为所有的缓存项提供了 单个 key 池 , 因此;
    • 你需要注意键区分 不同类型的对象.
    • 你需要注意不同租户(参见多租户)的缓存项.

IDistributedCache 定义在 Microsoft.Extensions.Caching.Abstractions 包中. 这使它不仅适用于ASP.NET Core应用程序, 也可用于任何类型的程序.

IDistributedCache 接口的默认实现是 MemoryDistributedCache 它使用内存工作. 参见 ASP.NET Core文档 了解如何切换到 Redis 或其他缓存提供程序. 此外, 如果要将Redis用作分布式缓存服务器, Redis缓存 文档.

有关更多信息, 参见 ASP.NET Core 分布式缓存文档.

IDistributedCache<TCacheItem> 接口

ABP框架在Volo.Abp.Caching包定义了通用的泛型 IDistributedCache<TCacheItem> 接口. TCacheItem 是存储在缓存中的对象类型.

IDistributedCache<TCacheItem> 接口解决了上述中的问题;

  • 它在内部 序列化/反序列化 缓存对象. 默认使用 JSON 序列化, 但可以替换依赖注入系统中 IDistributedCacheSerializer 服务的实现来覆盖默认的处理.
  • 它根据缓存中对象类型自动向缓存key添加 缓存名称 前缀. 默认缓存名是缓存对象类的全名(如果你的类名以CacheItem 结尾, 那么CacheItem 会被忽略,不应用到缓存名称上). 你也可以在缓存类上使用 CacheName 特性 设置缓存的名称.
  • 它自动将当前的租户id添加到缓存键中, 以区分不同租户的缓存项 (只有在你的应用程序是多租户的情况下生效). 如果要在多租户应用程序中的所有租户之间共享缓存对象, 请在缓存项类上定义IgnoreMultiTenancy特性以禁用此选项.
  • 允许为每个应用程序定义 全局缓存键前缀 , 不同的应用程序可以在共享的分布式缓存中拥有自己的隔离池.
  • 它可以在任何可能绕过缓存的情况下 容忍错误 的发生. 这在缓存服务器出现临时问题时非常有用.
  • 它有 GetManyAsyncSetManyAsync 等方法, 可以显著提高批处理的性能.

示例: 在缓存中存储图书名称和价格

  1. namespace MyProject
  2. {
  3. public class BookCacheItem
  4. {
  5. public string Name { get; set; }
  6. public float Price { get; set; }
  7. }
  8. }

你可以注入 IDistributedCache<BookCacheItem> 服务用于 get/set BookCacheItem 对象.

  1. using System;
  2. using System.Threading.Tasks;
  3. using Microsoft.Extensions.Caching.Distributed;
  4. using Volo.Abp.Caching;
  5. using Volo.Abp.DependencyInjection;
  6. namespace MyProject
  7. {
  8. public class BookService : ITransientDependency
  9. {
  10. private readonly IDistributedCache<BookCacheItem> _cache;
  11. public BookService(IDistributedCache<BookCacheItem> cache)
  12. {
  13. _cache = cache;
  14. }
  15. public async Task<BookCacheItem> GetAsync(Guid bookId)
  16. {
  17. return await _cache.GetOrAddAsync(
  18. bookId.ToString(), //缓存键
  19. async () => await GetBookFromDatabaseAsync(bookId),
  20. () => new DistributedCacheEntryOptions
  21. {
  22. AbsoluteExpiration = DateTimeOffset.Now.AddHours(1)
  23. }
  24. );
  25. }
  26. private Task<BookCacheItem> GetBookFromDatabaseAsync(Guid bookId)
  27. {
  28. //TODO: 从数据库获取数据
  29. }
  30. }
  31. }
  • 示例服务代码中的 GetOrAddAsync() 方法从缓存中获取图书项. GetOrAddAsync是ABP框架在 ASP.NET Core 分布式缓存方法中添增的附加方法.
  • 如果没有在缓存中找到图书,它会调用工厂方法 (本示例中是 GetBookFromDatabaseAsync)从原始数据源中获取图书项.
  • GetOrAddAsync 有一个可选参数 DistributedCacheEntryOptions , 可用于设置缓存的生命周期.

IDistributedCache<BookCacheItem> 与ASP.NET Core的IDistributedCache 接口拥有相同的方法, 你可以参考 ASP.NET Core文档.

IDistributedCache<TCacheItem, TCacheKey> 接口

IDistributedCache<TCacheItem> 接口默认了缓存键string 类型 (如果你的键不是string类型需要进行手动类型转换). 但当缓存键的类型不是string时, 可以使用IDistributedCache<TCacheItem, TCacheKey>.

示例: 在缓存中存储图书名称和价格

示例缓存项

  1. using Volo.Abp.Caching;
  2. namespace MyProject
  3. {
  4. [CacheName("Books")]
  5. public class BookCacheItem
  6. {
  7. public string Name { get; set; }
  8. public float Price { get; set; }
  9. }
  10. }
  • 在本例中使用CacheName特性给BookCacheItem类设置缓存名称.

你可以注入 IDistributedCache<BookCacheItem, Guid> 服务用于 get/set BookCacheItem 对象.

  1. using System;
  2. using System.Threading.Tasks;
  3. using Microsoft.Extensions.Caching.Distributed;
  4. using Volo.Abp.Caching;
  5. using Volo.Abp.DependencyInjection;
  6. namespace MyProject
  7. {
  8. public class BookService : ITransientDependency
  9. {
  10. private readonly IDistributedCache<BookCacheItem, Guid> _cache;
  11. public BookService(IDistributedCache<BookCacheItem, Guid> cache)
  12. {
  13. _cache = cache;
  14. }
  15. public async Task<BookCacheItem> GetAsync(Guid bookId)
  16. {
  17. return await _cache.GetOrAddAsync(
  18. bookId, //Guid类型作为缓存键
  19. async () => await GetBookFromDatabaseAsync(bookId),
  20. () => new DistributedCacheEntryOptions
  21. {
  22. AbsoluteExpiration = DateTimeOffset.Now.AddHours(1)
  23. }
  24. );
  25. }
  26. private Task<BookCacheItem> GetBookFromDatabaseAsync(Guid bookId)
  27. {
  28. //TODO: 从数据库获取数据
  29. }
  30. }
  31. }
  • 示例服务中 GetOrAddAsync() 方法获取缓存的图书项.
  • 我们采用了 Guid 做为键, 在 _cache_GetOrAddAsync() 方法中传入 Guid 类型的bookid.

复杂类型的缓存键

IDistributedCache<TCacheItem, TCacheKey> 在内部使用键对象的 ToString() 方法转换类型为string. 如果你的将复杂对象做为缓存键,那么需要重写类的 ToString 方法.

举例一个作为缓存键的类:

  1. public class UserInOrganizationCacheKey
  2. {
  3. public Guid UserId { get; set; }
  4. public Guid OrganizationId { get; set; }
  5. //构建缓存key
  6. public override string ToString()
  7. {
  8. return $"{UserId}_{OrganizationId}";
  9. }
  10. }

用法示例:

  1. public class BookService : ITransientDependency
  2. {
  3. private readonly IDistributedCache<UserCacheItem, UserInOrganizationCacheKey> _cache;
  4. public BookService(
  5. IDistributedCache<UserCacheItem, UserInOrganizationCacheKey> cache)
  6. {
  7. _cache = cache;
  8. }
  9. ...
  10. }

配置

AbpDistributedCacheOptions

AbpDistributedCacheOptions 是配置缓存的主要Option类.

示例:为应用程序设置缓存键前缀

  1. Configure<AbpDistributedCacheOptions>(options =>
  2. {
  3. options.KeyPrefix = "MyApp1";
  4. });

模块类ConfigureServices方法中添加代码.

可用选项

  • HideErrors (bool, 默认: true): 启用/禁用隐藏从缓存服务器写入/读取值时的错误.
  • KeyPrefix (string, 默认: null): 如果你的缓存服务器由多个应用程序共同使用, 则可以为应用程序的缓存键设置一个前缀. 在这种情况下, 不同的应用程序不能覆盖彼此的缓存内容.
  • GlobalCacheEntryOptions (DistributedCacheEntryOptions): 用于设置保存缓内容却没有指定选项时, 默认的分布式缓存选项 (例如 AbsoluteExpirationSlidingExpiration). SlidingExpiration的默认值设置为20分钟.

错误处理

当为你的对象设计缓存时, 通常会首先尝试从缓存中获取值. 如果在缓存中找不到该值, 则从来源查询对象. 它可能在数据库中, 或者可能需要通过HTTP调用远程服务器.

在大多数情况下, 你希望容忍缓存错误; 如果缓存服务器出现错误, 也不希望取消该操作. 相反, 你可以默默地隐藏(并记录)错误并从来源查询. 这就是ABP框架默认的功能.

ABP的分布式缓存 异常处理, 默认记录并隐藏错误. 有一个全局修改该功能的选项(参见下面的选项内容).

所有的IDistributedCache<TCacheItem> (和 IDistributedCache<TCacheItem, TCacheKey>)方法都有一个可选的参数hideErrors, 默认值为null. 如果此参数设置为null, 则全局生效, 否则你可以选择单个方法调用时隐藏或者抛出异常.

批量操作

ABP的分布式缓存接口定义了以下批量操作方法,当你需要在一个方法中调用多次缓存操作时,这些方法可以提高性能

  • SetManyAsyncSetMany 方法可以用来向缓存中设置多个值.
  • GetManyAsyncGetMany 方法可以用来从缓存中获取多个值.
  • GetOrAddManyAsyncGetOrAddMany 方法可以用来从缓存中获取并添加缺少的值.
  • RefreshManyAsyncRefreshMany 方法可以来用重置多个值的滚动过期时间.
  • RemoveManyAsyncRemoveMany 方法可以用来从缓存中删除多个值.

这些不是标准的ASP.NET Core缓存方法, 所以某些提供程序可能不支持. ABP Redis集成包实现了它们. 如果提供程序不支持,会回退到 SetAsyncGetAsync … 方法(循环调用).

高级主题

工作单元级别的缓存

分布式缓存服务提供了一个有趣的功能. 假设你已经更新了数据库中某本书的价格, 然后将新价格设置到缓存中, 以便以后使用缓存的值. 如果设置缓存后出现异常, 并且更新图书价格的事务被回滚了, 该怎么办?在这种情况下, 缓存值是错误的.

IDistributedCache<..>方法提供一个可选参数, considerUow, 默认为false. 如果将其设置为true, 则你对缓存所做的更改不会应用于真正的缓存存储, 而是与当前的工作单元关联. 你将获得在同一工作单元中设置的缓存值, 但仅当前工作单元成功时更改才会生效.

IDistributedCacheSerializer

IDistributedCacheSerializer服务用于序列化和反序列化缓存内容. 默认实现是Utf8JsonDistributedCacheSerializer类, 它使用IJsonSerializer服务将对象转换为JSON, 反之亦然. 然后, 它使用UTC8编码将JSON字符串转换为分布式缓存接受的字节数组.

如果你想实现自己的序列化逻辑, 可以自己实现并替换 此服务.

IDistributedCacheKeyNormalizer

默认情况下, IDistributedCacheKeyNormalizer是由DistributedCacheKeyNormalizer类实现的. 它将缓存名称、应用程序缓存前缀和当前租户id添加到缓存键中. 如果需要更高级的键规范化, 可以自己实现并替换此服务.

另请参阅