ABP - 缓存模块(2)
阅读原文时间:2023年08月09日阅读:1

1. 缓存模块源码解析

个人觉得 ABP 分布式缓存模块有三个值得关注的核心点。首先是 AbpRedisCache 类继承了微软原生的 RedisCache,并 通过反射的方式获取RedisCache的私有方法对 RedisCache 进行扩展,实现了 ABP 分布式缓存中的批量操作方法。

为什么要这么做呢?因为基于 Redis 缓存的批量操作需要使用到 StackExchange.Redis 原生的SDK,而且在进行操作前总需要先进行 Redis 连接,而相应的方法和属性原生的 RedisCache 中都有,但是都是私有(private)的,在继承类中也无法使用,所以使用反射的方式提取出相应的方法和属性,以便在继承类中复用。这也是对于类功能进行继承扩展的时候的一种很有用的方式。

第二点是 ABP 缓存模块通过 IDistributedCache 接口扩展了原生的 IDistributedCache 接口的功能,而在具体实现上是将原有的 IDistributedCache 服务注入进行复用相应功能的,并在前后增加额外的逻辑对功能进行扩展增强,实际上就是适配器模式。

而最常使用的 IDistributedCache<CacheItem> 接口,以前其实现类是直接继承 DistributedCache 的,现在改成了直接用适配器模式。

最后一个是缓存的事务性,通过工作单元使缓存和其他事务操作保持原子性,避免缓存已经更新而其他事务失败回滚导致数据不一致的问题。实际上就是先不真正地更新缓存,而是将缓存数据通过字典保存在工作单元中,保证一个工作单元内拿到的缓存数据是最新的,同时注册工作单元提交事件,在工作单元正在提交成功的时候才执行真正更新缓存的逻辑。

工作单元相关的内容就后面再在专门的章节讲吧,这部分的内容比较复杂,一时半会比较难讲清。

2. 自己扩展的 IDistributedCache

ABP 框架扩展的 IDistributedCache 泛型接口在内部帮我们处理实例对象进行序列化/反序列化以及转码为 byte 数组的问题,大大提升了我们使用分布式缓存的方便性,但也存在一些问题。

  • 它是基于类的泛型,如果我们在一个服务中需要使用多个缓存类型的话,我们就得注入多个的泛型接口,还是有些不方便的

  • 它的缓存键是以默认是以泛型类型的全类名作为前缀,虽然我们可以通过特性指定类型名称,但是对于集合,缓存键就很不清晰了

    由于这些情况的存在,也基于我们在日常开发中的使用习惯,我在工作中又基于 ABP 的 IDistributedCache<CacheItem> 接口进行扩展。内部实现基本一致,主要就是将基于类的泛型,改成基于方法的泛型,并且提供自己的 IDistributedCacheKeyNormalizer 实现类,将缓存键的设置规则交给了缓存存取时进行设置。 代码如下:

    public interface IWantDistributedCache
    {
    ///

    /// Gets a cache item with the given key. If no cache item is found for the given key then returns null. ///
    /// The key of cached item to be retrieved from the cache.
    /// Indicates to throw or hide the exceptions for the distributed cache.
    /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache.
    /// The cache item, or null.
    TCacheItem Get(
    TCacheKey key,
    string cacheName,
    bool? hideErrors = null,
    bool considerUow = false
    ) where TCacheItem : class;

    /// <summary>
    /// Gets multiple cache items with the given keys.
    ///
    /// The returned list contains exactly the same count of items specified in the given keys.
    /// An item in the return list can not be null, but an item in the list has null value
    /// if the related key not found in the cache.
    /// </summary>
    /// <param name="keys">The keys of cached items to be retrieved from the cache.</param>
    /// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param>
    /// <param name="considerUow">This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache.</param>
    /// <returns>List of cache items.</returns>
    KeyValuePair<TCacheKey, TCacheItem>[] GetMany<TCacheItem, TCacheKey>(
        IEnumerable<TCacheKey> keys,
        string cacheName,
        bool? hideErrors = null,
        bool considerUow = false
    ) where TCacheItem : class;
    
    /// <summary>
    /// Gets multiple cache items with the given keys.
    ///
    /// The returned list contains exactly the same count of items specified in the given keys.
    /// An item in the return list can not be null, but an item in the list has null value
    /// if the related key not found in the cache.
    ///
    /// </summary>
    /// <param name="keys">The keys of cached items to be retrieved from the cache.</param>
    /// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param>
    /// <param name="considerUow">This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache.</param>
    /// /// <param name="token">The <see cref="T:System.Threading.CancellationToken" /> for the task.</param>
    /// <returns>List of cache items.</returns>
    Task<KeyValuePair<TCacheKey, TCacheItem>[]> GetManyAsync<TCacheItem, TCacheKey>(
        IEnumerable<TCacheKey> keys,
        string cacheName,
        bool? hideErrors = null,
        bool considerUow = false,
        CancellationToken token = default
    ) where TCacheItem : class;
    
    /// <summary>
    /// Gets a cache item with the given key. If no cache item is found for the given key then returns null.
    /// </summary>
    /// <param name="key">The key of cached item to be retrieved from the cache.</param>
    /// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param>
    /// <param name="considerUow">This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache.</param>
    /// <param name="token">The <see cref="T:System.Threading.CancellationToken" /> for the task.</param>
    /// <returns>The cache item, or null.</returns>
    Task<TCacheItem> GetAsync<TCacheItem, TCacheKey>(
        [NotNull] TCacheKey key,
        string cacheName,
        bool? hideErrors = null,
        bool considerUow = false,
        CancellationToken token = default
    ) where TCacheItem : class;
    
    /// <summary>
    /// Gets or Adds a cache item with the given key. If no cache item is found for the given key then adds a cache item
    /// provided by <paramref name="factory" /> delegate and returns the provided cache item.
    /// </summary>
    /// <param name="key">The key of cached item to be retrieved from the cache.</param>
    /// <param name="factory">The factory delegate is used to provide the cache item when no cache item is found for the given <paramref name="key" />.</param>
    /// <param name="optionsFactory">The cache options for the factory delegate.</param>
    /// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param>
    /// <param name="considerUow">This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache.</param>
    /// <returns>The cache item.</returns>
    TCacheItem GetOrAdd<TCacheItem, TCacheKey>(
        TCacheKey key,
        Func<TCacheItem> factory,
        string cacheName,
        Func<DistributedCacheEntryOptions> optionsFactory = null,
        bool? hideErrors = null,
        bool considerUow = false
    ) where TCacheItem : class;
    
    /// <summary>
    /// Gets or Adds a cache item with the given key. If no cache item is found for the given key then adds a cache item
    /// provided by <paramref name="factory" /> delegate and returns the provided cache item.
    /// </summary>
    /// <param name="key">The key of cached item to be retrieved from the cache.</param>
    /// <param name="factory">The factory delegate is used to provide the cache item when no cache item is found for the given <paramref name="key" />.</param>
    /// <param name="optionsFactory">The cache options for the factory delegate.</param>
    /// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param>
    /// <param name="considerUow">This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache.</param>
    /// <param name="token">The <see cref="T:System.Threading.CancellationToken" /> for the task.</param>
    /// <returns>The cache item.</returns>
    Task<TCacheItem> GetOrAddAsync<TCacheItem, TCacheKey>(
        [NotNull] TCacheKey key,
        Func<Task<TCacheItem>> factory,
        string cacheName,
        Func<DistributedCacheEntryOptions> optionsFactory = null,
        bool? hideErrors = null,
        bool considerUow = false,
        CancellationToken token = default
    ) where TCacheItem : class;
    
    /// <summary>
    /// Sets the cache item value for the provided key.
    /// </summary>
    /// <param name="key">The key of cached item to be retrieved from the cache.</param>
    /// <param name="value">The cache item value to set in the cache.</param>
    /// <param name="options">The cache options for the value.</param>
    /// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param>
    /// <param name="considerUow">This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache.</param>
    void Set<TCacheItem, TCacheKey>(
        TCacheKey key,
        TCacheItem value,
        string cacheName,
        DistributedCacheEntryOptions options = null,
        bool? hideErrors = null,
        bool considerUow = false
    ) where TCacheItem : class;
    
    /// <summary>
    /// Sets the cache item value for the provided key.
    /// </summary>
    /// <param name="key">The key of cached item to be retrieved from the cache.</param>
    /// <param name="value">The cache item value to set in the cache.</param>
    /// <param name="options">The cache options for the value.</param>
    /// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param>
    /// <param name="considerUow">This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache.</param>
    /// <param name="token">The <see cref="T:System.Threading.CancellationToken" /> for the task.</param>
    /// <returns>The <see cref="T:System.Threading.Tasks.Task" /> indicating that the operation is asynchronous.</returns>
    Task SetAsync<TCacheItem, TCacheKey>(
        [NotNull] TCacheKey key,
        [NotNull] TCacheItem value,
        string cacheName,
        [CanBeNull] DistributedCacheEntryOptions options = null,
        bool? hideErrors = null,
        bool considerUow = false,
        CancellationToken token = default
    ) where TCacheItem : class;
    
    /// <summary>
    /// Sets multiple cache items.
    /// Based on the implementation, this can be more efficient than setting multiple items individually.
    /// </summary>
    /// <param name="items">Items to set on the cache</param>
    /// <param name="options">The cache options for the value.</param>
    /// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param>
    /// <param name="considerUow">This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache.</param>
    void SetMany<TCacheItem, TCacheKey>(
        IEnumerable<KeyValuePair<TCacheKey, TCacheItem>> items,
        string cacheName,
        DistributedCacheEntryOptions options = null,
        bool? hideErrors = null,
        bool considerUow = false
    ) where TCacheItem : class;
    
    /// <summary>
    /// Sets multiple cache items.
    /// Based on the implementation, this can be more efficient than setting multiple items individually.
    /// </summary>
    /// <param name="items">Items to set on the cache</param>
    /// <param name="options">The cache options for the value.</param>
    /// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param>
    /// <param name="considerUow">This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache.</param>
    /// <param name="token">The <see cref="T:System.Threading.CancellationToken" /> for the task.</param>
    /// <returns>The <see cref="T:System.Threading.Tasks.Task" /> indicating that the operation is asynchronous.</returns>
    Task SetManyAsync<TCacheItem, TCacheKey>(
        IEnumerable<KeyValuePair<TCacheKey, TCacheItem>> items,
        string cacheName,
        DistributedCacheEntryOptions options = null,
        bool? hideErrors = null,
        bool considerUow = false,
        CancellationToken token = default
    ) where TCacheItem : class;
    
    /// <summary>
    /// Refreshes the cache value of the given key, and resets its sliding expiration timeout.
    /// </summary>
    /// <param name="key">The key of cached item to be retrieved from the cache.</param>
    /// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param>
    void Refresh<TCacheKey>(
        TCacheKey key,
        string cacheName,
        bool? hideErrors = null
    );
    
    /// <summary>
    /// Refreshes the cache value of the given key, and resets its sliding expiration timeout.
    /// </summary>
    /// <param name="key">The key of cached item to be retrieved from the cache.</param>
    /// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param>
    /// <param name="token">The <see cref="T:System.Threading.CancellationToken" /> for the task.</param>
    /// <returns>The <see cref="T:System.Threading.Tasks.Task" /> indicating that the operation is asynchronous.</returns>
    Task RefreshAsync<TCacheKey>(
        TCacheKey key,
        string cacheName,
        bool? hideErrors = null,
        CancellationToken token = default
    );
    
    /// <summary>
    /// Removes the cache item for given key from cache.
    /// </summary>
    /// <param name="key">The key of cached item to be retrieved from the cache.</param>
    /// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param>
    /// <param name="considerUow">This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache.</param>
    void Remove<TCacheItem, TCacheKey>(
        TCacheKey key,
        string cacheName,
        bool? hideErrors = null,
        bool considerUow = false
    ) where TCacheItem : class;
    
    /// <summary>
    /// Removes the cache item for given key from cache.
    /// </summary>
    /// <param name="key">The key of cached item to be retrieved from the cache.</param>
    /// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param>
    /// <param name="considerUow">This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache.</param>
    /// <param name="token">The <see cref="T:System.Threading.CancellationToken" /> for the task.</param>
    /// <returns>The <see cref="T:System.Threading.Tasks.Task" /> indicating that the operation is asynchronous.</returns>
    Task RemoveAsync<TCacheItem, TCacheKey>(
        TCacheKey key,
        string cacheName,
        bool? hideErrors = null,
        bool considerUow = false,
        CancellationToken token = default
    ) where TCacheItem : class;

    }

    public class WantDistributedCache : IWantDistributedCache
    {
    public const string UowCacheName = "WantDistributedCache";

    public ILogger<WantDistributedCache> Logger { get; set; }
    
    protected string CacheName { get; set; }
    
    protected bool IgnoreMultiTenancy { get; set; }
    
    protected IDistributedCache Cache { get; }
    
    protected ICancellationTokenProvider CancellationTokenProvider { get; }
    
    protected IDistributedCacheSerializer Serializer { get; }
    
    protected IDistributedCacheKeyNormalizer KeyNormalizer { get; }
    
    protected IHybridServiceScopeFactory ServiceScopeFactory { get; }
    
    protected IUnitOfWorkManager UnitOfWorkManager { get; }
    
    protected SemaphoreSlim SyncSemaphore { get; }
    
    protected DistributedCacheEntryOptions DefaultCacheOptions;
    
    private readonly AbpDistributedCacheOptions _distributedCacheOption;
    
    public WantDistributedCache(
        IOptions<AbpDistributedCacheOptions> distributedCacheOption,
        IDistributedCache cache,
        ICancellationTokenProvider cancellationTokenProvider,
        IDistributedCacheSerializer serializer,
        IDistributedCacheKeyNormalizer keyNormalizer,
        IHybridServiceScopeFactory serviceScopeFactory,
        IUnitOfWorkManager unitOfWorkManager)
    {
        _distributedCacheOption = distributedCacheOption.Value;
        Cache = cache;
        CancellationTokenProvider = cancellationTokenProvider;
        Logger = NullLogger<WantDistributedCache>.Instance;
        Serializer = serializer;
        KeyNormalizer = keyNormalizer;
        ServiceScopeFactory = serviceScopeFactory;
        UnitOfWorkManager = unitOfWorkManager;
    SyncSemaphore = new SemaphoreSlim(1, 1);
    
    SetDefaultOptions();
    } protected virtual string NormalizeKey<TCacheKey>(TCacheKey key, string cacheName) { return KeyNormalizer.NormalizeKey( new DistributedCacheKeyNormalizeArgs( key.ToString(), cacheName, IgnoreMultiTenancy ) ); } protected virtual DistributedCacheEntryOptions GetDefaultCacheEntryOptions() { foreach (var configure in _distributedCacheOption.CacheConfigurators) { var options = configure.Invoke(CacheName); if (options != null) { return options; } }
    return _distributedCacheOption.GlobalCacheEntryOptions;
    } protected virtual void SetDefaultOptions() { //CacheName = CacheNameAttribute.GetCacheName(typeof(TCacheItem));
    ////IgnoreMultiTenancy
    //IgnoreMultiTenancy = typeof(TCacheItem).IsDefined(typeof(IgnoreMultiTenancyAttribute), true);
    
    //Configure default cache entry options
    DefaultCacheOptions = GetDefaultCacheEntryOptions();
    } /// <summary> /// Gets a cache item with the given key. If no cache item is found for the given key then returns null. /// </summary> /// <param name="key">The key of cached item to be retrieved from the cache.</param> /// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param> /// <param name="considerUow">This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache.</param> /// <returns>The cache item, or null.</returns> public virtual TCacheItem Get<TCacheItem, TCacheKey>( TCacheKey key, string cacheName, bool? hideErrors = null, bool considerUow = false) where TCacheItem : class { hideErrors = hideErrors ?? _distributedCacheOption.HideErrors;
    if (ShouldConsiderUow(considerUow))
    {
        var value = GetUnitOfWorkCache&lt;TCacheItem, TCacheKey&gt;().GetOrDefault(key)?.GetUnRemovedValueOrNull();
        if (value != null)
        {
            return value;
        }
    }
    
    byte[] cachedBytes;
    
    try
    {
        cachedBytes = Cache.Get(NormalizeKey(key, cacheName));
    }
    catch (Exception ex)
    {
        if (hideErrors == true)
        {
            HandleException(ex);
            return null;
        }
    
        throw;
    }
    
    return ToCacheItem&lt;TCacheItem&gt;(cachedBytes);
    } public virtual KeyValuePair<TCacheKey, TCacheItem>[] GetMany<TCacheItem, TCacheKey>( IEnumerable<TCacheKey> keys, string cacheName, bool? hideErrors = null, bool considerUow = false) where TCacheItem : class { var keyArray = keys.ToArray();
    var cacheSupportsMultipleItems = Cache as ICacheSupportsMultipleItems;
    if (cacheSupportsMultipleItems == null)
    {
        return GetManyFallback&lt;TCacheItem, TCacheKey&gt;(
            keyArray,
            cacheName,
            hideErrors,
            considerUow
        );
    }
    
    var notCachedKeys = new List&lt;TCacheKey&gt;();
    var cachedValues = new List&lt;KeyValuePair&lt;TCacheKey, TCacheItem&gt;&gt;();
    if (ShouldConsiderUow(considerUow))
    {
        var uowCache = GetUnitOfWorkCache&lt;TCacheItem, TCacheKey&gt;();
        foreach (var key in keyArray)
        {
            var value = uowCache.GetOrDefault(key)?.GetUnRemovedValueOrNull();
            if (value != null)
            {
                cachedValues.Add(new KeyValuePair&lt;TCacheKey, TCacheItem&gt;(key, value));
            }
        }
    
        notCachedKeys = keyArray.Except(cachedValues.Select(x =&gt; x.Key)).ToList();
        if (!notCachedKeys.Any())
        {
            return cachedValues.ToArray();
        }
    }
    
    hideErrors = hideErrors ?? _distributedCacheOption.HideErrors;
    byte[][] cachedBytes;
    
    var readKeys = notCachedKeys.Any() ? notCachedKeys.ToArray() : keyArray;
    try
    {
        cachedBytes = cacheSupportsMultipleItems.GetMany(readKeys.Select(key =&gt; NormalizeKey(key, cacheName)));
    }
    catch (Exception ex)
    {
        if (hideErrors == true)
        {
            HandleException(ex);
            return ToCacheItemsWithDefaultValues&lt;TCacheItem, TCacheKey&gt;(keyArray);
        }
    
        throw;
    }
    
    return cachedValues.Concat(ToCacheItems&lt;TCacheItem, TCacheKey&gt;(cachedBytes, readKeys)).ToArray();
    } protected virtual KeyValuePair<TCacheKey, TCacheItem>[] GetManyFallback<TCacheItem, TCacheKey>( TCacheKey[] keys, string cacheName, bool? hideErrors = null, bool considerUow = false) where TCacheItem : class { hideErrors = hideErrors ?? _distributedCacheOption.HideErrors;
    try
    {
        return keys
            .Select(key =&gt; new KeyValuePair&lt;TCacheKey, TCacheItem&gt;(
                    key,
                    Get&lt;TCacheItem, TCacheKey&gt;(key, cacheName, false, considerUow)
                )
            ).ToArray();
    }
    catch (Exception ex)
    {
        if (hideErrors == true)
        {
            HandleException(ex);
            return ToCacheItemsWithDefaultValues&lt;TCacheItem, TCacheKey&gt;(keys);
        }
    
        throw;
    }
    } public virtual async Task<KeyValuePair<TCacheKey, TCacheItem>[]> GetManyAsync<TCacheItem, TCacheKey>( IEnumerable<TCacheKey> keys, string cacheName, bool? hideErrors = null, bool considerUow = false, CancellationToken token = default) where TCacheItem : class { var keyArray = keys.ToArray();
    var cacheSupportsMultipleItems = Cache as ICacheSupportsMultipleItems;
    if (cacheSupportsMultipleItems == null)
    {
        return await GetManyFallbackAsync&lt;TCacheItem, TCacheKey&gt;(
            keyArray,
            cacheName,
            hideErrors,
            considerUow,
            token
        );
    }
    
    var notCachedKeys = new List&lt;TCacheKey&gt;();
    var cachedValues = new List&lt;KeyValuePair&lt;TCacheKey, TCacheItem&gt;&gt;();
    if (ShouldConsiderUow(considerUow))
    {
        var uowCache = GetUnitOfWorkCache&lt;TCacheItem, TCacheKey&gt;();
        foreach (var key in keyArray)
        {
            var value = uowCache.GetOrDefault(key)?.GetUnRemovedValueOrNull();
            if (value != null)
            {
                cachedValues.Add(new KeyValuePair&lt;TCacheKey, TCacheItem&gt;(key, value));
            }
        }
    
        notCachedKeys = keyArray.Except(cachedValues.Select(x =&gt; x.Key)).ToList();
        if (!notCachedKeys.Any())
        {
            return cachedValues.ToArray();
        }
    }
    
    hideErrors = hideErrors ?? _distributedCacheOption.HideErrors;
    byte[][] cachedBytes;
    
    var readKeys = notCachedKeys.Any() ? notCachedKeys.ToArray() : keyArray;
    
    try
    {
        cachedBytes = await cacheSupportsMultipleItems.GetManyAsync(
            readKeys.Select(key =&gt; NormalizeKey(key, cacheName)),
            CancellationTokenProvider.FallbackToProvider(token)
        );
    }
    catch (Exception ex)
    {
        if (hideErrors == true)
        {
            await HandleExceptionAsync(ex);
            return ToCacheItemsWithDefaultValues&lt;TCacheItem, TCacheKey&gt;(keyArray);
        }
    
        throw;
    }
    
    return cachedValues.Concat(ToCacheItems&lt;TCacheItem, TCacheKey&gt;(cachedBytes, readKeys)).ToArray();
    } protected virtual async Task<KeyValuePair<TCacheKey, TCacheItem>[]> GetManyFallbackAsync<TCacheItem, TCacheKey>( TCacheKey[] keys, string cacheName, bool? hideErrors = null, bool considerUow = false, CancellationToken token = default) where TCacheItem : class { hideErrors = hideErrors ?? _distributedCacheOption.HideErrors;
    try
    {
        var result = new List&lt;KeyValuePair&lt;TCacheKey, TCacheItem&gt;&gt;();
    
        foreach (var key in keys)
        {
            result.Add(new KeyValuePair&lt;TCacheKey, TCacheItem&gt;(
                key,
                await GetAsync&lt;TCacheItem, TCacheKey&gt;(key, cacheName, false, considerUow, token: token))
            );
        }
    
        return result.ToArray();
    }
    catch (Exception ex)
    {
        if (hideErrors == true)
        {
            await HandleExceptionAsync(ex);
            return ToCacheItemsWithDefaultValues&lt;TCacheItem, TCacheKey&gt;(keys);
        }
    
        throw;
    }
    } /// <summary> /// Gets a cache item with the given key. If no cache item is found for the given key then returns null. /// </summary> /// <param name="key">The key of cached item to be retrieved from the cache.</param> /// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param> /// <param name="considerUow">This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache.</param> /// <param name="token">The <see cref="T:System.Threading.CancellationToken" /> for the task.</param> /// <returns>The cache item, or null.</returns> public virtual async Task<TCacheItem> GetAsync<TCacheItem, TCacheKey>( TCacheKey key, string cacheName, bool? hideErrors = null, bool considerUow = false, CancellationToken token = default) where TCacheItem : class { hideErrors = hideErrors ?? _distributedCacheOption.HideErrors;
    if (ShouldConsiderUow(considerUow))
    {
        var value = GetUnitOfWorkCache&lt;TCacheItem, TCacheKey&gt;().GetOrDefault(key)?.GetUnRemovedValueOrNull();
        if (value != null)
        {
            return value;
        }
    }
    
    byte[] cachedBytes;
    
    try
    {
        cachedBytes = await Cache.GetAsync(
            NormalizeKey(key, cacheName),
            CancellationTokenProvider.FallbackToProvider(token)
        );
    }
    catch (Exception ex)
    {
        if (hideErrors == true)
        {
            await HandleExceptionAsync(ex);
            return null;
        }
    
        throw;
    }
    
    if (cachedBytes == null)
    {
        return null;
    }
    
    return Serializer.Deserialize&lt;TCacheItem&gt;(cachedBytes);
    } /// <summary> /// Gets or Adds a cache item with the given key. If no cache item is found for the given key then adds a cache item /// provided by <paramref name="factory" /> delegate and returns the provided cache item. /// </summary> /// <param name="key">The key of cached item to be retrieved from the cache.</param> /// <param name="factory">The factory delegate is used to provide the cache item when no cache item is found for the given <paramref name="key" />.</param> /// <param name="optionsFactory">The cache options for the factory delegate.</param> /// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param> /// <param name="considerUow">This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache.</param> /// <returns>The cache item.</returns> public virtual TCacheItem GetOrAdd<TCacheItem, TCacheKey>( TCacheKey key, Func<TCacheItem> factory, string cacheName, Func<DistributedCacheEntryOptions> optionsFactory = null, bool? hideErrors = null, bool considerUow = false) where TCacheItem : class { var value = Get<TCacheItem, TCacheKey>(key, cacheName, hideErrors, considerUow); if (value != null) { return value; }
    using (SyncSemaphore.Lock())
    {
        value = Get&lt;TCacheItem, TCacheKey&gt;(key, cacheName, hideErrors, considerUow);
        if (value != null)
        {
            return value;
        }
    
        value = factory();
    
        if (ShouldConsiderUow(considerUow))
        {
            var uowCache = GetUnitOfWorkCache&lt;TCacheItem, TCacheKey&gt;();
            if (uowCache.TryGetValue(key, out var item))
            {
                item.SetValue(value);
            }
            else
            {
                uowCache.Add(key, new UnitOfWorkCacheItem&lt;TCacheItem&gt;(value));
            }
        }
    
        Set(key, value, cacheName, optionsFactory?.Invoke(), hideErrors, considerUow);
    }
    
    return value;
    } /// <summary> /// Gets or Adds a cache item with the given key. If no cache item is found for the given key then adds a cache item /// provided by <paramref name="factory" /> delegate and returns the provided cache item. /// </summary> /// <param name="key">The key of cached item to be retrieved from the cache.</param> /// <param name="factory">The factory delegate is used to provide the cache item when no cache item is found for the given <paramref name="key" />.</param> /// <param name="optionsFactory">The cache options for the factory delegate.</param> /// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param> /// <param name="considerUow">This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache.</param> /// <param name="token">The <see cref="T:System.Threading.CancellationToken" /> for the task.</param> /// <returns>The cache item.</returns> public virtual async Task<TCacheItem> GetOrAddAsync<TCacheItem, TCacheKey>( TCacheKey key, Func<Task<TCacheItem>> factory, string cacheName, Func<DistributedCacheEntryOptions> optionsFactory = null, bool? hideErrors = null, bool considerUow = false, CancellationToken token = default) where TCacheItem : class { token = CancellationTokenProvider.FallbackToProvider(token); var value = await GetAsync<TCacheItem, TCacheKey>(key, cacheName, hideErrors, considerUow, token); if (value != null) { return value; }
    using (await SyncSemaphore.LockAsync(token))
    {
        value = await GetAsync&lt;TCacheItem, TCacheKey&gt;(key, cacheName, hideErrors, considerUow, token);
        if (value != null)
        {
            return value;
        }
    
        value = await factory();
    
        if (ShouldConsiderUow(considerUow))
        {
            var uowCache = GetUnitOfWorkCache&lt;TCacheItem, TCacheKey&gt;();
            if (uowCache.TryGetValue(key, out var item))
            {
                item.SetValue(value);
            }
            else
            {
                uowCache.Add(key, new UnitOfWorkCacheItem&lt;TCacheItem&gt;(value));
            }
        }
    
        await SetAsync(key, value, cacheName, optionsFactory?.Invoke(), hideErrors, considerUow, token);
    }
    
    return value;
    } /// <summary> /// Sets the cache item value for the provided key. /// </summary> /// <param name="key">The key of cached item to be retrieved from the cache.</param> /// <param name="value">The cache item value to set in the cache.</param> /// <param name="options">The cache options for the value.</param> /// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param> /// <param name="considerUow">This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache.</param> public virtual void Set<TCacheItem, TCacheKey>( TCacheKey key, TCacheItem value, string cacheName, DistributedCacheEntryOptions options = null, bool? hideErrors = null, bool considerUow = false) where TCacheItem : class { void SetRealCache() { hideErrors = hideErrors ?? _distributedCacheOption.HideErrors;
        try
        {
            Cache.Set(
                NormalizeKey(key, cacheName),
                Serializer.Serialize(value),
                options ?? DefaultCacheOptions
            );
        }
        catch (Exception ex)
        {
            if (hideErrors == true)
            {
                HandleException(ex);
                return;
            }
    
            throw;
        }
    }
    
    if (ShouldConsiderUow(considerUow))
    {
        var uowCache = GetUnitOfWorkCache&lt;TCacheItem, TCacheKey&gt;();
        if (uowCache.TryGetValue(key, out _))
        {
            uowCache[key].SetValue(value);
        }
        else
        {
            uowCache.Add(key, new UnitOfWorkCacheItem&lt;TCacheItem&gt;(value));
        }
    
        // ReSharper disable once PossibleNullReferenceException
        UnitOfWorkManager.Current.OnCompleted(() =&gt;
        {
            SetRealCache();
            return Task.CompletedTask;
        });
    }
    else
    {
        SetRealCache();
    }
    } /// <summary> /// Sets the cache item value for the provided key. /// </summary> /// <param name="key">The key of cached item to be retrieved from the cache.</param> /// <param name="value">The cache item value to set in the cache.</param> /// <param name="options">The cache options for the value.</param> /// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param> /// <param name="considerUow">This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache.</param> /// <param name="token">The <see cref="T:System.Threading.CancellationToken" /> for the task.</param> /// <returns>The <see cref="T:System.Threading.Tasks.Task" /> indicating that the operation is asynchronous.</returns> public virtual async Task SetAsync<TCacheItem, TCacheKey>( TCacheKey key, TCacheItem value, string cacheName, DistributedCacheEntryOptions options = null, bool? hideErrors = null, bool considerUow = false, CancellationToken token = default) where TCacheItem : class { async Task SetRealCache() { hideErrors = hideErrors ?? _distributedCacheOption.HideErrors;
        try
        {
            await Cache.SetAsync(
                NormalizeKey(key, cacheName),
                Serializer.Serialize(value),
                options ?? DefaultCacheOptions,
                CancellationTokenProvider.FallbackToProvider(token)
            );
        }
        catch (Exception ex)
        {
            if (hideErrors == true)
            {
                await HandleExceptionAsync(ex);
                return;
            }
    
            throw;
        }
    }
    
    if (ShouldConsiderUow(considerUow))
    {
        var uowCache = GetUnitOfWorkCache&lt;TCacheItem, TCacheKey&gt;();
        if (uowCache.TryGetValue(key, out _))
        {
            uowCache[key].SetValue(value);
        }
        else
        {
            uowCache.Add(key, new UnitOfWorkCacheItem&lt;TCacheItem&gt;(value));
        }
    
        // ReSharper disable once PossibleNullReferenceException
        UnitOfWorkManager.Current.OnCompleted(SetRealCache);
    }
    else
    {
        await SetRealCache();
    }
    } public void SetMany<TCacheItem, TCacheKey>( IEnumerable<KeyValuePair<TCacheKey, TCacheItem>> items, string cacheName, DistributedCacheEntryOptions options = null, bool? hideErrors = null, bool considerUow = false) where TCacheItem : class { var itemsArray = items.ToArray();
    var cacheSupportsMultipleItems = Cache as ICacheSupportsMultipleItems;
    if (cacheSupportsMultipleItems == null)
    {
        SetManyFallback(
            itemsArray,
            cacheName,
            options,
            hideErrors,
            considerUow
        );
    
        return;
    }
    
    void SetRealCache()
    {
        hideErrors = hideErrors ?? _distributedCacheOption.HideErrors;
    
        try
        {
            cacheSupportsMultipleItems.SetMany(
                ToRawCacheItems(itemsArray, cacheName),
                options ?? DefaultCacheOptions
            );
        }
        catch (Exception ex)
        {
            if (hideErrors == true)
            {
                HandleException(ex);
                return;
            }
    
            throw;
        }
    }
    
    if (ShouldConsiderUow(considerUow))
    {
        var uowCache = GetUnitOfWorkCache&lt;TCacheItem, TCacheKey&gt;();
    
        foreach (var pair in itemsArray)
        {
            if (uowCache.TryGetValue(pair.Key, out _))
            {
                uowCache[pair.Key].SetValue(pair.Value);
            }
            else
            {
                uowCache.Add(pair.Key, new UnitOfWorkCacheItem&lt;TCacheItem&gt;(pair.Value));
            }
        }
    
        // ReSharper disable once PossibleNullReferenceException
        UnitOfWorkManager.Current.OnCompleted(() =&gt;
        {
            SetRealCache();
            return Task.CompletedTask;
        });
    }
    else
    {
        SetRealCache();
    }
    } protected virtual void SetManyFallback<TCacheItem, TCacheKey>( KeyValuePair<TCacheKey, TCacheItem>[] items, string cacheName, DistributedCacheEntryOptions options = null, bool? hideErrors = null, bool considerUow = false) where TCacheItem : class { hideErrors = hideErrors ?? _distributedCacheOption.HideErrors;
    try
    {
        foreach (var item in items)
        {
            Set(
                item.Key,
                item.Value,
                cacheName,
                options,
                false,
                considerUow
            );
        }
    }
    catch (Exception ex)
    {
        if (hideErrors == true)
        {
            HandleException(ex);
            return;
        }
    
        throw;
    }
    } public virtual async Task SetManyAsync<TCacheItem, TCacheKey>( IEnumerable<KeyValuePair<TCacheKey, TCacheItem>> items, string cacheName, DistributedCacheEntryOptions options = null, bool? hideErrors = null, bool considerUow = false, CancellationToken token = default) where TCacheItem : class { var itemsArray = items.ToArray();
    var cacheSupportsMultipleItems = Cache as ICacheSupportsMultipleItems;
    if (cacheSupportsMultipleItems == null)
    {
        await SetManyFallbackAsync(
            itemsArray,
            cacheName,
            options,
            hideErrors,
            considerUow,
            token
        );
    
        return;
    }
    
    async Task SetRealCache()
    {
        hideErrors = hideErrors ?? _distributedCacheOption.HideErrors;
    
        try
        {
            await cacheSupportsMultipleItems.SetManyAsync(
                ToRawCacheItems(itemsArray, cacheName),
                options ?? DefaultCacheOptions,
                CancellationTokenProvider.FallbackToProvider(token)
            );
        }
        catch (Exception ex)
        {
            if (hideErrors == true)
            {
                await HandleExceptionAsync(ex);
                return;
            }
    
            throw;
        }
    }
    
    if (ShouldConsiderUow(considerUow))
    {
        var uowCache = GetUnitOfWorkCache&lt;TCacheItem, TCacheKey&gt;();
    
        foreach (var pair in itemsArray)
        {
            if (uowCache.TryGetValue(pair.Key, out _))
            {
                uowCache[pair.Key].SetValue(pair.Value);
            }
            else
            {
                uowCache.Add(pair.Key, new UnitOfWorkCacheItem&lt;TCacheItem&gt;(pair.Value));
            }
        }
    
        // ReSharper disable once PossibleNullReferenceException
        UnitOfWorkManager.Current.OnCompleted(SetRealCache);
    }
    else
    {
        await SetRealCache();
    }
    } protected virtual async Task SetManyFallbackAsync<TCacheItem, TCacheKey>( KeyValuePair<TCacheKey, TCacheItem>[] items, string cacheName, DistributedCacheEntryOptions options = null, bool? hideErrors = null, bool considerUow = false, CancellationToken token = default) where TCacheItem : class { hideErrors = hideErrors ?? _distributedCacheOption.HideErrors;
    try
    {
        foreach (var item in items)
        {
            await SetAsync(
                item.Key,
                item.Value,
                cacheName,
                options,
                false,
                considerUow,
                token: token
            );
        }
    }
    catch (Exception ex)
    {
        if (hideErrors == true)
        {
            await HandleExceptionAsync(ex);
            return;
        }
    
        throw;
    }
    } /// <summary> /// Refreshes the cache value of the given key, and resets its sliding expiration timeout. /// </summary> /// <param name="key">The key of cached item to be retrieved from the cache.</param> /// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param> public virtual void Refresh<TCacheKey>( TCacheKey key, string cacheName, bool? hideErrors = null) { hideErrors = hideErrors ?? _distributedCacheOption.HideErrors;
    try
    {
        Cache.Refresh(NormalizeKey(key, cacheName));
    }
    catch (Exception ex)
    {
        if (hideErrors == true)
        {
            HandleException(ex);
            return;
        }
    
        throw;
    }
    } /// <summary> /// Refreshes the cache value of the given key, and resets its sliding expiration timeout. /// </summary> /// <param name="key">The key of cached item to be retrieved from the cache.</param> /// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param> /// <param name="token">The <see cref="T:System.Threading.CancellationToken" /> for the task.</param> /// <returns>The <see cref="T:System.Threading.Tasks.Task" /> indicating that the operation is asynchronous.</returns> public virtual async Task RefreshAsync<TCacheKey>( TCacheKey key, string cacheName, bool? hideErrors = null, CancellationToken token = default) { hideErrors = hideErrors ?? _distributedCacheOption.HideErrors;
    try
    {
        await Cache.RefreshAsync(NormalizeKey(key, cacheName), CancellationTokenProvider.FallbackToProvider(token));
    }
    catch (Exception ex)
    {
        if (hideErrors == true)
        {
            await HandleExceptionAsync(ex);
            return;
        }
    
        throw;
    }
    } /// <summary> /// Removes the cache item for given key from cache. /// </summary> /// <param name="key">The key of cached item to be retrieved from the cache.</param> /// <param name="considerUow">This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache.</param> /// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param> public virtual void Remove<TCacheItem, TCacheKey>( TCacheKey key, string cacheName, bool? hideErrors = null, bool considerUow = false) where TCacheItem : class { void RemoveRealCache() { hideErrors = hideErrors ?? _distributedCacheOption.HideErrors;
        try
        {
            Cache.Remove(NormalizeKey(key, cacheName));
        }
        catch (Exception ex)
        {
            if (hideErrors == true)
            {
                HandleException(ex);
                return;
            }
    
            throw;
        }
    }
    
    if (ShouldConsiderUow(considerUow))
    {
        var uowCache = GetUnitOfWorkCache&lt;TCacheItem, TCacheKey&gt;();
        if (uowCache.TryGetValue(key, out _))
        {
            uowCache[key].RemoveValue();
        }
    
        // ReSharper disable once PossibleNullReferenceException
        UnitOfWorkManager.Current.OnCompleted(() =&gt;
        {
            RemoveRealCache();
            return Task.CompletedTask;
        });
    }
    else
    {
        RemoveRealCache();
    }
    } /// <summary> /// Removes the cache item for given key from cache. /// </summary> /// <param name="key">The key of cached item to be retrieved from the cache.</param> /// <param name="hideErrors">Indicates to throw or hide the exceptions for the distributed cache.</param> /// <param name="considerUow">This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache.</param> /// <param name="token">The <see cref="T:System.Threading.CancellationToken" /> for the task.</param> /// <returns>The <see cref="T:System.Threading.Tasks.Task" /> indicating that the operation is asynchronous.</returns> public virtual async Task RemoveAsync<TCacheItem, TCacheKey>( TCacheKey key, string cacheName, bool? hideErrors = null, bool considerUow = false, CancellationToken token = default) where TCacheItem : class { async Task RemoveRealCache() { hideErrors = hideErrors ?? _distributedCacheOption.HideErrors;
        try
        {
            await Cache.RemoveAsync(NormalizeKey(key, cacheName), CancellationTokenProvider.FallbackToProvider(token));
        }
        catch (Exception ex)
        {
            if (hideErrors == true)
            {
                await HandleExceptionAsync(ex);
                return;
            }
    
            throw;
        }
    }
    
    if (ShouldConsiderUow(considerUow))
    {
        var uowCache = GetUnitOfWorkCache&lt;TCacheItem, TCacheKey&gt;();
        if (uowCache.TryGetValue(key, out _))
        {
            uowCache[key].RemoveValue();
        }
    
        // ReSharper disable once PossibleNullReferenceException
        UnitOfWorkManager.Current.OnCompleted(RemoveRealCache);
    }
    else
    {
        await RemoveRealCache();
    }
    } protected virtual void HandleException(Exception ex) { AsyncHelper.RunSync(() => HandleExceptionAsync(ex)); } protected virtual async Task HandleExceptionAsync(Exception ex) { Logger.LogException(ex, LogLevel.Warning);
    using (var scope = ServiceScopeFactory.CreateScope())
    {
        await scope.ServiceProvider
            .GetRequiredService&lt;IExceptionNotifier&gt;()
            .NotifyAsync(new ExceptionNotificationContext(ex, LogLevel.Warning));
    }
    } protected virtual KeyValuePair<TCacheKey, TCacheItem>[] ToCacheItems<TCacheItem, TCacheKey>(byte[][] itemBytes, TCacheKey[] itemKeys) where TCacheItem: class { if (itemBytes.Length != itemKeys.Length) { throw new AbpException("count of the item bytes should be same with the count of the given keys"); }
    var result = new List&lt;KeyValuePair&lt;TCacheKey, TCacheItem&gt;&gt;();
    
    for (int i = 0; i &lt; itemKeys.Length; i++)
    {
        result.Add(
            new KeyValuePair&lt;TCacheKey, TCacheItem&gt;(
                itemKeys[i],
                ToCacheItem&lt;TCacheItem&gt;(itemBytes[i])
            )
        );
    }
    
    return result.ToArray();
    } [CanBeNull] protected virtual TCacheItem ToCacheItem<TCacheItem>([CanBeNull] byte[] bytes) where TCacheItem : class { if (bytes == null) { return null; }
    return Serializer.Deserialize&lt;TCacheItem&gt;(bytes);
    } protected virtual KeyValuePair<string, byte[]>[] ToRawCacheItems<TCacheItem, TCacheKey>(KeyValuePair<TCacheKey, TCacheItem>[] items, string cacheName) { return items .Select(i => new KeyValuePair<string, byte[]>( NormalizeKey(i.Key, cacheName), Serializer.Serialize(i.Value) ) ).ToArray(); } private static KeyValuePair<TCacheKey, TCacheItem>[] ToCacheItemsWithDefaultValues<TCacheItem, TCacheKey>(TCacheKey[] keys) { return keys .Select(key => new KeyValuePair<TCacheKey, TCacheItem>(key, default)) .ToArray(); } protected virtual bool ShouldConsiderUow(bool considerUow) { return considerUow && UnitOfWorkManager.Current != null; } protected virtual string GetUnitOfWorkCacheKey() { return UowCacheName + CacheName; } protected virtual Dictionary<TCacheKey, UnitOfWorkCacheItem<TCacheItem>> GetUnitOfWorkCache<TCacheItem, TCacheKey>() where TCacheItem : class { if (UnitOfWorkManager.Current == null) { throw new AbpException($"There is no active UOW."); }
    return UnitOfWorkManager.Current.GetOrAddItem(GetUnitOfWorkCacheKey(),
        key =&gt; new Dictionary&lt;TCacheKey, UnitOfWorkCacheItem&lt;TCacheItem&gt;&gt;());
    }

    }

    [Dependency(ReplaceServices = true)]
    public class WantDistributedCacheKeyNormalizer : IDistributedCacheKeyNormalizer, ITransientDependency
    {
    protected AbpDistributedCacheOptions DistributedCacheOptions { get; }

    public SuncereDistributedCacheKeyNormalizer(
        IOptions<AbpDistributedCacheOptions> distributedCacheOptions)
    {
        DistributedCacheOptions = distributedCacheOptions.Value;
    }
    
    public virtual string NormalizeKey(DistributedCacheKeyNormalizeArgs args)
    {
        // 缓存格式: a:appname,c:cachename,k:key
        var normalizedKey = $"a:{DistributedCacheOptions.KeyPrefix},c:{args.CacheName},k:{args.Key}";
        return normalizedKey;
    }

    }

    [DependsOn(typeof(AbpCachingModule))]
    public class WantAbpCacheModule : AbpModule
    {
    public override void ConfigureServices(ServiceConfigurationContext context)
    {
    //注入缓存类
    context.Services.AddSingleton(typeof(ISuncereDistributedCache), typeof(SuncereDistributedCache));
    }
    }

参考文章:

ABP 官方文档 - 缓存

ABP 系列总结:

目录:ABP 系列总结

上一篇:ABP - 缓存模块(1)