[Abp 源碼分析]8、緩存管理

0.簡介

緩存在一個業務系統中十分重要,經常使用的場景就是用來儲存調用頻率較高的數據。Abp 也提供了一套緩存機制供用戶使用,在使用 Abp 框架的時候能夠經過注入 ICacheManager 來新建/設置緩存。html

同時 Abp 框架也提供了 Redis 版本的 ICacheManager 實現,你也能夠很方便的將現有的內存緩存替換爲 Redis 緩存。redis

0.1 典型使用方法

public class TestAppService : ApplicationService
{
    private readonly ICacheManager _cacheMgr;
    private readonly IRepository<TestEntity> _rep;

    // 注入緩存管理器與測試實體的倉儲
    public TestAppService(ICacheManager cacheMgr, IRepository<TestEntity> rep)
    {
        _cacheMgr = cacheMgr;
        _rep = rep;
    }

    public void TestMethod()
    {
        // 獲取/建立一個新的緩存
        var cache = _cacheMgr.GetCache("緩存1");
        // 轉換爲強類型的緩存
        var typedCache = cache.AsTyped<int, string>();

        // 獲取緩存的數據,若是存在則直接返回。
        // 若是不存在則執行工廠方法,將其值存放到
        // 緩存項當中,最後返回緩存項數據。
        var cacheValue = typedCache.Get(10, id => _rep.Get(id).Name);

        Console.WriteLine(cacheValue);
    }
}

1.啓動流程

同其餘的基礎設施同樣,緩存管理器 ICacheManager 在 Abp 框架啓動的時候就自動被注入到了 Ioc 容器當中,由於他的基類 CacheManagerBase 繼承了 ISingletonDependency 接口。緩存

public abstract class CacheManagerBase : ICacheManager, ISingletonDependency
{
    // ... 其餘代碼
}

其次就是他的 ICachingConfiguration 緩存配置是在 AbpCoreInstaller 注入到 Ioc 容器,而且同其餘基礎設施的配置一塊兒被集成到了 IAbpStartupConfigurationapp

internal class AbpCoreInstaller : IWindsorInstaller
    {
        public void Install(IWindsorContainer container, IConfigurationStore store)
        {
            container.Register(
                // 其餘被注入的基礎設施配置
                
                Component.For<ICachingConfiguration, CachingConfiguration>().ImplementedBy<CachingConfiguration>().LifestyleSingleton()
                
                // 其餘被注入的基礎設施配置
                );
        }
    }

你能夠在其餘模塊的 PreInitialize() 方法裏面能夠直接經過 Configuration.Caching 來配置緩存過時時間等功能。框架

public override void PreInitialize()
{
    Configuration.Caching.ConfigureAll(z=>z.DefaultSlidingExpireTime = TimeSpan.FromHours(1));
}

2. 代碼分析

緩存這塊多是 Abp 框架實現當中最簡單的一部分了,代碼量很少,可是設計思路仍是值得借鑑的。異步

2.1 緩存管理器

2.1.1 基本定義

緩存管理器即 ICacheManager ,一般它用於管理全部緩存,他的接口定義十分簡單,就兩個方法:ide

public interface ICacheManager : IDisposable
{
    // 得到全部緩存
    IReadOnlyList<ICache> GetAllCaches();
    
    // 根據緩存名稱獲取緩存
    [NotNull] ICache GetCache([NotNull] string name);
}

2.1.2 獲取/建立緩存

Abp 實現了一個抽象基類 CacheBase 實現了本接口,在 CacheBase 內部維護了一個 ConcurrentDictionary<string,ICache> 字典,這個字典裏面就是存放的全部緩存。測試

同時在他的 GetCache(string name) 內部呢,經過傳入的緩存名字來從字典獲取已經存在的緩存,若是不存在呢,執行其工廠方法來建立一個新的緩存。this

public virtual ICache GetCache(string name)
{
    Check.NotNull(name, nameof(name));

    // 從字典根據名稱取得緩存,不存在則使用工廠方法
    return Caches.GetOrAdd(name, (cacheName) =>
    {
        // 獲得建立成功的緩存
        var cache = CreateCacheImplementation(cacheName);

        // 遍歷緩存配置集合,查看當前名字的緩存是否存在配置項
        var configurators = Configuration.Configurators.Where(c => c.CacheName == null || c.CacheName == cacheName);

        // 遍歷這些配置項執行配置操做,更改緩存的過時時間等參數
        foreach (var configurator in configurators)
        {
            configurator.InitAction?.Invoke(cache);
        }

        // 返回配置完成的緩存
        return cache;
    });
}

// 真正建立緩存的方法
protected abstract ICache CreateCacheImplementation(string name);

這裏的 CreateCacheImplementation()由具體的緩存管理器實現的緩存建立方法,由於 Redis 與 MemoryCache 的實現各不同,因此這裏定義了一個抽象方法。設計

2.1.3 緩存管理器銷燬

當緩存管理器被銷燬的時候,首先是遍歷字典內存儲的全部緩存,並經過 IIocManager.Release() 方法來釋放這些緩存,以後則是調用字典的 Clear() 方法清空字典。

public virtual void Dispose()
{
    DisposeCaches();
    // 清空字典
    Caches.Clear();
}

// 遍歷字典,釋放對象
protected virtual void DisposeCaches()
{
    foreach (var cache in Caches)
    {
        IocManager.Release(cache.Value);
    }
}

2.1.4 內存緩存管理器

Abp 對於緩存管理器的默認實現是 AbpMemoryCacheManager ,其實沒多複雜,就是實現了基類的 CreateCacheImplementation() 返回特定的 ICache

public class AbpMemoryCacheManager : CacheManagerBase
{
    // ... 忽略了的代碼

    protected override ICache CreateCacheImplementation(string name)
    {
        // 就 new 一個新的內存緩存而已,內存緩存的實現請看後面的
        // 這裏是由於 AbpMemory 沒有注入到 IOC 容器,因此須要手動 new
        return new AbpMemoryCache(name)
        {
            Logger = Logger
        };
    }

    // 重寫了基類的緩存釋放方法
    protected override void DisposeCaches()
    {
        foreach (var cache in Caches.Values)
        {
            cache.Dispose();
        }
    }
}

2.1.5 Redis 緩存管理器

若是要使用 Redis 緩存管理器,根據模塊的加載順序,你須要在啓動模塊的 PreInitialize() 調用 Abp.Redis 庫提供的集成方法便可。

這裏先來看看他的實現:

public class AbpRedisCacheManager : CacheManagerBase
{
    public AbpRedisCacheManager(IIocManager iocManager, ICachingConfiguration configuration)
        : base(iocManager, configuration)
    {
        // 註冊 Redis 緩存
        IocManager.RegisterIfNot<AbpRedisCache>(DependencyLifeStyle.Transient);
    }

    protected override ICache CreateCacheImplementation(string name)
    {
        // 解析已經注入的 Redis 緩存
        // 這裏能夠看到解析的時候如何傳入構造參數
        return IocManager.Resolve<AbpRedisCache>(new { name });
    }
}

同樣的,很是簡單,沒什麼能夠說的。

2.2 緩存

咱們從緩存管理器當中拿到具體的緩存以後纔可以進行真正的緩存操做,這裏須要明確的一個概念是緩存是一個緩存項的集合,緩存項裏面的值纔是咱們真正緩存的結果。

就如同一個用戶表,他擁有多條用戶數據,那麼咱們要針對這個用戶表作緩存,就會建立一個緩存名稱叫作 "用戶表" 的緩存,在須要得到用戶數據的時候,咱們拿去數據就直接從這個 "用戶表" 緩存當中取得具體的緩存項,也就是具體的用戶數據。

其實每一個緩存項也是幾個 鍵值對 ,鍵就是緩存的鍵,以上面的 "用戶表緩存" 爲例子,那麼他緩存項的鍵就是 int 型的 Id ,他的值呢就是一個用戶實體。

2.2.1 基本定義

全部緩存的定義都在 ICache 當中,每一個緩存都擁有增刪查改這些基本操做,而且還擁有過時時間與名稱等屬性。

一樣,緩存也有一個抽象基類的實現,名字叫作 CacheBase 。與緩存管理器的抽象基類同樣,CacheBase 內部僅實現了 Get 方法的基本邏輯,其餘的都是抽象方法,須要由具體的類型進行實現。

public interface ICache : IDisposable
{
    // 緩存名稱
    string Name { get; }
    
    // 相對過時時間
    TimeSpan DefaultSlidingExpireTime { get; set; }

    // 絕對過時時間
    TimeSpan? DefaultAbsoluteExpireTime { get; set; }

    // 根據緩存項 Key 獲取到緩存的數據,不存在則執行工廠方法
    object Get(string key, Func<string, object> factory);

    // Get 的異步實現
    Task<object> GetAsync(string key, Func<string, Task<object>> factory);

    // 根據緩存項 Key 獲取到緩存的數據,沒有則返回默認值,通常爲 null
    object GetOrDefault(string key);

    // GetOrDefault 的異步實現
    Task<object> GetOrDefaultAsync(string key);

    // 設置緩存項值和過時時間等參數
    void Set(string key, object value, TimeSpan? slidingExpireTime = null, TimeSpan? absoluteExpireTime = null);

    // Set 的異步實現
    Task SetAsync(string key, object value, TimeSpan? slidingExpireTime = null, TimeSpan? absoluteExpireTime = null);

    // 移除指定緩存名稱的緩存項
    void Remove(string key);

    // Remove 的異步實現
    Task RemoveAsync(string key);

    // 清空緩存內全部緩存項
    void Clear();

    // Clear 的異步實現
    Task ClearAsync();
}

2.2.2 內存緩存的實現

這裏咱們以 Abp 的默認 MemoryCache 實現爲例子來看看裏面是什麼構造:

public class AbpMemoryCache : CacheBase
{
    private MemoryCache _memoryCache;
    
    // 初始化 MemoryCahce
    public AbpMemoryCache(string name)
        : base(name)
    {
        _memoryCache = new MemoryCache(new OptionsWrapper<MemoryCacheOptions>(new MemoryCacheOptions()));
    }

    // 從 MemoryCahce 取得緩存
    public override object GetOrDefault(string key)
    {
        return _memoryCache.Get(key);
    }

    // 設置緩存
    public override void Set(string key, object value, TimeSpan? slidingExpireTime = null, TimeSpan? absoluteExpireTime = null)
    {
        // 值爲空的時候拋出異常
        if (value == null)
        {
            throw new AbpException("Can not insert null values to the cache!");
        }

        if (absoluteExpireTime != null)
        {
            _memoryCache.Set(key, value, DateTimeOffset.Now.Add(absoluteExpireTime.Value));
        }
        else if (slidingExpireTime != null)
        {
            _memoryCache.Set(key, value, slidingExpireTime.Value);
        }
        else if (DefaultAbsoluteExpireTime != null)
        {
            _memoryCache.Set(key, value, DateTimeOffset.Now.Add(DefaultAbsoluteExpireTime.Value));
        }
        else
        {
            _memoryCache.Set(key, value, DefaultSlidingExpireTime);
        }
    }

    // 刪除緩存
    public override void Remove(string key)
    {
        _memoryCache.Remove(key);
    }

    // 清空緩存
    public override void Clear()
    {
        _memoryCache.Dispose();
        _memoryCache = new MemoryCache(new OptionsWrapper<MemoryCacheOptions>(new MemoryCacheOptions()));
    }

    public override void Dispose()
    {
        _memoryCache.Dispose();
        base.Dispose();
    }
}

能夠看到在 AbpMemoryCache 內部就是將 MemoryCahce 進行了一個二次包裝而已。

其實能夠看到這些緩存超期時間之類的參數 Abp 本身並無用到,而是將其傳遞給具體的緩存實現來進行管理。

2.2.3 Redis 緩存的實現

Abp.Redis 庫使用的是 StackExchange.Redis 庫來實現對 Redis 的通信的,其實現爲 AbpRedisCache ,裏面也沒什麼好說的,如同內存緩存同樣,實現那些抽象方法就能夠了。

public class AbpRedisCache : CacheBase
{
    private readonly IDatabase _database;
    private readonly IRedisCacheSerializer _serializer;

    public AbpRedisCache(
        string name, 
        IAbpRedisCacheDatabaseProvider redisCacheDatabaseProvider, 
        IRedisCacheSerializer redisCacheSerializer)
        : base(name)
    {
        _database = redisCacheDatabaseProvider.GetDatabase();
        _serializer = redisCacheSerializer;
    }

    // 獲取緩存
    public override object GetOrDefault(string key)
    {
        var objbyte = _database.StringGet(GetLocalizedKey(key));
        return objbyte.HasValue ? Deserialize(objbyte) : null;
    }

    public override void Set(string key, object value, TimeSpan? slidingExpireTime = null, TimeSpan? absoluteExpireTime = null)
    {
        if (value == null)
        {
            throw new AbpException("Can not insert null values to the cache!");
        }

        //TODO: 這裏是一個解決實體序列化的方法.
        //TODO: 一般實體不該該存儲在緩存當中,目前 Abp.Zero 包是這樣來進行處理的,這個問題將會在將來被修正.
        var type = value.GetType();
        if (EntityHelper.IsEntity(type) && type.GetAssembly().FullName.Contains("EntityFrameworkDynamicProxies"))
        {
            type = type.GetTypeInfo().BaseType;
        }

        _database.StringSet(
            GetLocalizedKey(key),
            Serialize(value, type),
            absoluteExpireTime ?? slidingExpireTime ?? DefaultAbsoluteExpireTime ?? DefaultSlidingExpireTime
            );
    }

    // 移除緩存
    public override void Remove(string key)
    {
        _database.KeyDelete(GetLocalizedKey(key));
    }

    // 清空緩存
    public override void Clear()
    {
        _database.KeyDeleteWithPrefix(GetLocalizedKey("*"));
    }

    // 序列化對象
    protected virtual string Serialize(object value, Type type)
    {
        return _serializer.Serialize(value, type);
    }

    // 反序列化對象
    protected virtual object Deserialize(RedisValue objbyte)
    {
        return _serializer.Deserialize(objbyte);
    }

    // 得到緩存的 Key
    protected virtual string GetLocalizedKey(string key)
    {
        return "n:" + Name + ",c:" + key;
    }
}

2.3 緩存配置

緩存配置的做用就是能夠爲每一個緩存配置不一樣的過時時間,咱們最開始說過 Abp 是經過 ICachingConfiguration 來配置緩存的,在這個接口裏面呢定義了這樣幾個東西。

public interface ICachingConfiguration
{
    // 配置項集合
    IReadOnlyList<ICacheConfigurator> Configurators { get; }

    // 配置全部緩存
    void ConfigureAll(Action<ICache> initAction);

    // 配置指定名稱的緩存
    void Configure(string cacheName, Action<ICache> initAction);
}

Emmmm,能夠看到他有個 Configurators 屬性存了一大堆 ICacheConfigurator ,這個玩意兒呢就是對應到具體緩存的配置項了。

public interface ICacheConfigurator
{
    // 關聯的緩存名稱
    string CacheName { get; }

    // 緩存初始化的時候執行的配置操做
    Action<ICache> InitAction { get; }
}

這玩意兒的實現也沒什麼好看的,跟接口差很少,這下咱們知道了緩存的配置呢就是存放在 Configurators 裏面的。

而後呢,就在咱們最開始的地方,緩存管理器建立緩存的時候不是根據名字去遍歷這個 Configurators 集合麼,在那裏面就直接經過這個 ICacheConfiguratorAction<ICache> 來配置緩存的超期時間。

至於 Configure()ConfigureAll() 方法嘛,前者就是根據你傳入的緩存名稱初始化一個 CacheConfigurator ,而後扔到那個列表裏面去。

private readonly List<ICacheConfigurator> _configurators;

public void Configure(string cacheName, Action<ICache> initAction)
{
    _configurators.Add(new CacheConfigurator(cacheName, initAction));
}

後者的話則是添加了一個沒有名字的 CacheConfigurator ,正由於沒有名字,因此他的 cacheName 確定 null,也就是在緩存管理器建立緩存的時候若是該緩存沒有對應的配置,那麼就會使用這個名字爲空的 CacheConfigurator 了。

2.4 強類型緩存

在最開始的使用方法裏面能夠看到咱們經過 AsType<TKey,TValue>() 方法將 ICache 對象轉換爲 ITypedCache ,這樣咱們就無需再將緩存項手動進行強制類型轉換。

注:雖然這裏是指定了泛型操做,可是呢,在其內部實現仍是進行的強制類型轉換,也是會發生裝/拆箱操做的。

Abp 本身則經過 TypedCacheWrapper<TKey, TValue> 來將原有的 ICache 緩存包裝爲 ITypedCache<TKey, TValue>

看看這個擴展方法的定義,他是放在 CacheExtensions 裏面的:

public static ITypedCache<TKey, TValue> AsTyped<TKey, TValue>(this ICache cache)
{
    return new TypedCacheWrapper<TKey, TValue>(cache);
}

Emmm,這裏是 new 了一個 TypedCacheWrapper 來處理的,從方法定義能夠看出來 TypedCacheWrapper 是 ITypedCache 的一個默認實現。

ITypedCache<TKey,TValue> 擁有 ICache 的全部方法簽名,因此使用 ITypedCache<TKey,TValue> 與使用 ICache 的方式是同樣的。

TypedCacheWrapper 的各類方法其實就是調用的傳入的 ICache 對象的方法,只不過在返回值得時候他本身進行了強制類型轉換而已,好比說,看看他的 Get 方法。

public class TypedCacheWrapper<TKey, TValue> : ITypedCache<TKey, TValue>
{
    // 返回的是內部 ICache 的名稱
    public string Name
    {
        get { return InternalCache.Name; }
    }

    public TimeSpan DefaultSlidingExpireTime
    {
        get { return InternalCache.DefaultSlidingExpireTime; }
        set { InternalCache.DefaultSlidingExpireTime = value; }
    }
    public TimeSpan? DefaultAbsoluteExpireTime
    {
        get { return InternalCache.DefaultAbsoluteExpireTime; }
        set { InternalCache.DefaultAbsoluteExpireTime = value; }
    }

    // 調用 AsTyped() 方法時候傳入的 ICache 對象
    public ICache InternalCache { get; private set; }

    public TypedCacheWrapper(ICache internalCache)
    {
        InternalCache = internalCache;
    }

    // 調用的是一個 ICache 的擴展方法
    public TValue Get(TKey key, Func<TKey, TValue> factory)
    {
        return InternalCache.Get(key, factory);
    }
    
    // ..... 忽略了其餘方法
}

看看 InternalCache.Get(key, factory); 這個擴展方法的定義吧:

public static TValue Get<TKey, TValue>(this ICache cache, TKey key, Func<TKey, TValue> factory)
{
    // 本質上就是調用的 ICache 的 Get 方法,返回的時候進行了強制類型轉換而已
    return (TValue)cache.Get(key.ToString(), (k) => (object)factory(key));
}

3.點此跳轉到總目錄

相關文章
相關標籤/搜索