项目

分布式缓存

ABP扩展了 ASP.NET Core分布式缓存 ,提供更便捷易用的缓存服务。

IDistributedCache接口的默认实现是MemoryDistributedCache,它在内存中工作。 内存缓存仅在你构建单体应用且运行单个应用实例时有用。对于其他情况,请考虑使用分布式缓存服务器。更多详情请参见 何时使用分布式缓存服务器 文档。

安装

此包已默认安装在 启动模板 中。因此,大多数情况下,你无需手动安装。

Volo.Abp.Caching是缓存系统的主包。你可以使用 ABP CLI 的 add-package 命令将其作为项目安装:

abp add-package Volo.Abp.Caching

你需要在包含csproj文件的文件夹中的命令行终端运行此命令(查看 其他安装选项 )。

使用

IDistributedCache接口

ASP.NET Core定义了IDistributedCache接口来获取/设置缓存值。但它存在一些难点:

  • 它处理的是字节数组而非.NET对象。因此,你需要序列化/反序列化要缓存的对象。
  • 它为所有缓存项提供单一键池,因此:
    • 你需要关注键以区分不同类型的对象
    • 多租户 系统中,你需要关注不同租户的缓存项。

IDistributedCache定义在Microsoft.Extensions.Caching.Abstractions包中。这意味着它不仅适用于ASP.NET Core应用,也适用于任何类型的应用

更多信息请参见 ASP.NET Core的分布式缓存文档

IDistributedCache<TCacheItem>接口

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

IDistributedCache<TCacheItem>解决了上述难点:

  • 它在内部序列化/反序列化缓存对象。默认使用JSON序列化,但可以通过替换 依赖注入 系统中的IDistributedCacheSerializer服务来重写。
  • 它根据存储在缓存中的对象类型,自动为缓存键添加缓存名称前缀。默认缓存名称是缓存项类的全名(如果缓存项类以 CacheItem 结尾,则去除该后缀)。你可以在缓存项类上使用**CacheName特性**来设置缓存名称。
  • 它自动将当前租户ID添加到缓存键中,以区分不同租户的缓存项(如果你的应用是 多租户 的)。在多租户应用中,如果你想在所有租户间共享缓存对象,可以在缓存项类上定义 IgnoreMultiTenancy 特性来禁用此功能。
  • 允许为每个应用定义全局缓存键前缀,以便不同应用可以在共享的分布式缓存服务器中使用其隔离的键池。
  • 能够容忍错误并在可能的情况下绕过缓存。这在缓存服务器出现临时问题时非常有用。
  • 它提供了GetManyAsyncSetManyAsync等方法,显著提升了批量操作的性能。

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

namespace MyProject
{
    public class BookCacheItem
    {
        public string Name { get; set; }

        public float Price { get; set; }
    }
}

你可以注入并使用IDistributedCache<BookCacheItem>服务来获取/设置BookCacheItem对象:

using System;
using System.Threading.Tasks;
using Microsoft.Extensions.Caching.Distributed;
using Volo.Abp.Caching;
using Volo.Abp.DependencyInjection;

namespace MyProject
{
    public class BookService : ITransientDependency
    {
        private readonly IDistributedCache<BookCacheItem> _cache;

        public BookService(IDistributedCache<BookCacheItem> cache)
        {
            _cache = cache;
        }

        public async Task<BookCacheItem> GetAsync(Guid bookId)
        {
            return await _cache.GetOrAddAsync(
                bookId.ToString(), //缓存键
                async () => await GetBookFromDatabaseAsync(bookId),
                () => new DistributedCacheEntryOptions
                {
                    AbsoluteExpiration = DateTimeOffset.Now.AddHours(1)
                }
            );
        }

        private Task<BookCacheItem> GetBookFromDatabaseAsync(Guid bookId)
        {
            //TODO: 从数据库获取
        }
    }
}
  • 此示例服务使用GetOrAddAsync()方法从缓存中获取书籍项。GetOrAddAsync是ABP添加到标准ASP.NET Core分布式缓存方法中的额外方法。
  • 如果书籍未在缓存中找到,它会调用工厂方法(本例中为GetBookFromDatabaseAsync)从原始源检索书籍项。
  • GetOrAddAsync可选地获取DistributedCacheEntryOptions,可用于设置缓存项的生命周期。

IDistributedCache<BookCacheItem>支持ASP.NET Core标准IDistributedCache接口的相同方法,因此你可以参考 其文档

IDistributedCache<TCacheItem, TCacheKey>接口

IDistributedCache<TCacheItem>接口假设你的缓存键类型是string(因此,如果你需要使用不同类型的缓存键,需要手动将其转换为字符串)。虽然这不是大问题,但当缓存键类型不是string时,可以使用IDistributedCache<TCacheItem, TCacheKey>

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

using Volo.Abp.Caching;

namespace MyProject
{
    [CacheName("Books")]
    public class BookCacheItem
    {
        public string Name { get; set; }

        public float Price { get; set; }
    }
}
  • 此示例对BookCacheItem类使用CacheName特性来设置缓存名称。

你可以注入并使用IDistributedCache<BookCacheItem, Guid>服务来获取/设置BookCacheItem对象:

using System;
using System.Threading.Tasks;
using Microsoft.Extensions.Caching.Distributed;
using Volo.Abp.Caching;
using Volo.Abp.DependencyInjection;

namespace MyProject
{
    public class BookService : ITransientDependency
    {
        private readonly IDistributedCache<BookCacheItem, Guid> _cache;

        public BookService(IDistributedCache<BookCacheItem, Guid> cache)
        {
            _cache = cache;
        }

        public async Task<BookCacheItem> GetAsync(Guid bookId)
        {
            return await _cache.GetOrAddAsync(
                bookId, //Guid类型用作缓存键
                async () => await GetBookFromDatabaseAsync(bookId),
                () => new DistributedCacheEntryOptions
                {
                    AbsoluteExpiration = DateTimeOffset.Now.AddHours(1)
                }
            );
        }
        private Task<BookCacheItem> GetBookFromDatabaseAsync(Guid bookId)
        {
            //TODO: 从数据库获取
        }
    }
}
  • 此示例服务使用GetOrAddAsync()方法从缓存中获取书籍项。
  • 由于缓存显式实现为使用Guid作为缓存键,因此将Guid值传递给_cache_GetOrAddAsync()方法。

复杂类型作为缓存键

IDistributedCache<TCacheItem, TCacheKey>在内部使用键对象的ToString()方法将其转换为字符串。如果你需要使用复杂对象作为缓存键,需要重写类的ToString方法。

用作缓存键的示例类:

public class UserInOrganizationCacheKey
{
    public Guid UserId { get; set; }
    
    public Guid OrganizationId { get; set; }
    
    //构建缓存键
    public override string ToString()
    {
        return $"{UserId}_{OrganizationId}";
    }
}

示例用法:

public class BookService : ITransientDependency
{
    private readonly IDistributedCache<UserCacheItem, UserInOrganizationCacheKey> _cache;

    public BookService(
        IDistributedCache<UserCacheItem, UserInOrganizationCacheKey> cache)
    {
        _cache = cache;
    }
    
    ...
}

配置

AbpDistributedCacheOptions

AbpDistributedCacheOptions是配置缓存的主要 选项类

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

Configure<AbpDistributedCacheOptions>(options =>
{
    options.KeyPrefix = "MyApp1";
});

模块类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...方法(对每个项调用一次)。

缓存实体

ABP提供了一个 分布式实体缓存系统 用于缓存实体。如果你希望使用缓存来更快地访问实体,而不是重复从数据库查询,这非常有用。

它设计为只读,并在实体更新或删除时自动使缓存实体失效。

更多信息请参见 实体缓存 文档。

高级主题

工作单元级缓存

分布式缓存服务提供了一个有趣的功能。假设你在数据库中更新了书籍的价格,然后将新价格设置到缓存中,以便以后可以使用缓存值。如果在设置缓存后出现异常,并且你回滚了更新书籍价格的事务,会发生什么?在这种情况下,缓存值将不正确。

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

IDistributedCacheSerializer

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

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

IDistributedCacheKeyNormalizer

IDistributedCacheKeyNormalizer默认由DistributedCacheKeyNormalizer类实现。它向缓存键添加缓存名称、应用缓存前缀和当前租户ID。如果你需要更高级的键规范化,可以用自己的实现 替换 此服务。

另请参阅

在本文档中