[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.点此跳转到总目录

相关文章
相关标签/搜索