1 分布式缓存中间件的配置定义
1.1 Core.Configuration.CacheConfig
namespace Core.Configuration
{
///
/// 【缓存配置--类】
///
/// 摘要:
/// 通过该类中的属性成员实例对“appsettings.json”文件中的1个指定缓存项(键/值对)在内存或指定分布式软件中保存的缓存时间等数据进行设定性读写操作,为1个指定缓存项(键/值对)在内存或指定分布式软件中保存的缓存时间提供数据支撑。
/// 说明:
/// 属性成员的名称必须与JSON键/值对中的键相同,且属性成员的个数与键的个数也必须相等,
/// 否则ConfigurationBinder.Bind方法将不支持通过“appsettings.json”文件中数据库连接相关数据与当前类中的属性成员实例的设定性读写操作。
///
///
public partial class CacheConfig : IConfig
{
///
/// 【默认缓存时间】
///
/// 摘要:
/// 获取/设置1个指定缓存项(键/值对)在内存或指定分布式软件中保存的默认缓存时间(默认值:60分钟=1小时)。
///
///
public int DefaultCacheTime { get; private set; } = 60;
///
/// 【短期缓存时间】
///
/// 摘要:
/// 获取/设置1个指定缓存项(键/值对)在内存或指定分布式软件中保存的最短缓存时间(默认值:3分钟)。
///
///
public int ShortTermCacheTime { get; private set; } = 3;
///
/// 【打包文件缓存时间】
///
/// 摘要:
/// 获取/设置1个指定缓存项(键/值对)在内存或指定分布式软件中保存的(JS/CSS)绑定文件中所有数据的缓存时间(默认值:120分钟=2小时)。
///
///
public int BundledFilesCacheTime { get; private set; } = 120;
}
}
1.2 Core.Configuration.DistributedCacheType
using System.Runtime.Serialization;
namespace Core.Configuration
{
///
/// 【分布式缓存中间件类型--枚举】
///
/// 摘要:
/// 该枚举实例定义了3种分布式缓中间件,通过枚举实例选定其中的1种方式,实现当前程序对所有缓存项(键/值对)进行集中管理。
///
///
public enum DistributedCacheType
{
///
/// 【内存】
///
/// 摘要:
/// 指定通过“Microsoft.Extensions.Caching.Memory”程序集,对当前程序中所有缓存项(键/值对)进行集中管理。
/// EnumMember特性:
/// 在JSON序列化操作时把该枚举成员实例的值序列化为“memory”字符串,赋值给键/值对中的值。
///
///
[EnumMember(Value = "memory")]
Memory,
///
/// 【SqlServer】
///
/// 摘要:
/// 指定通过“Microsoft.Extensions.Caching.SqlServer”中间件使用“Microsoft SQL Server”数据库软件中的指定数据库中的指定表,对当前程序中所有缓存项(键/值对)进行集中管理。
/// EnumMember特性:
/// 在JSON序列化操作时把该枚举成员实例的值序列化为“sqlserver”字符串,赋值给键/值对中的值。
///
///
[EnumMember(Value = "sqlserver")]
SqlServer,
///
/// 【Redis】
///
/// 摘要:
/// 指定通过“Microsoft.Extensions.Caching.StackExchangeRedis”中间件使用“Redis”分页式数据库软件,对当前程序中所有缓存项(键/值对)进行集中管理。
/// EnumMember特性:
/// 在JSON序列化操作时把该枚举成员实例的值序列化为“redis”字符串,赋值给键/值对中的值。
///
///
[EnumMember(Value = "redis")]
Redis
}
}
1.3 Core.Configuration.DistributedCacheConfig
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
namespace Core.Configuration
{
///
/// 【分页式缓存配置--类】
///
/// 摘要:
/// 通过该类中的属性成员实例对“appsettings.json”文件中的1个指定分布式软件所需数据进行设定性读写操作,为当前程序与1个指定分布式数据库软件建立连接关系提供数据支撑。
/// 说明:
/// 属性成员的名称必须与JSON键/值对中的键相同,且属性成员的个数与键的个数也必须相等,
/// 否则ConfigurationBinder.Bind方法将不支持通过“appsettings.json”文件中的1个指定分布式软件所需数据进行设定性读写操作。
///
///
public class DistributedCacheConfig : IConfig
{
///
///
/// 【分布式缓存类型】
///
/// 摘要:
/// 获取/设置1个指定分布式数据库软件与当前程序建立连接关系,默认指定通过“Redis”分布式数据库软件与当前程序进行数据交互操作。
/// JsonConverter特性:
/// 当前“DataConfig”类的实例通过JSON中的方法进行序列化/反序化时,需要根据字符字符串枚举方式来实例当前属性成员实例值与JSON键/值对中值的相对转换操作。
///
///
[JsonConverter(typeof(StringEnumConverter))]
public DistributedCacheType DistributedCacheType { get; private set; } = DistributedCacheType.Redis;
///
/// 【启用?】
///
/// 摘要:
/// 获取/设置1个值false(默认值:禁用)/true(启用),该值指示当前程序是否已经启用了1个指定分布式数据库软件与当前程序进行数据交互操作。
///
///
public bool Enabled { get; private set; } = false;
///
/// 【连接字符串】
///
/// 摘要:
/// 获取/设置1个连接字符串,该连接字符串只为实例化“Redis”分布式数据库软件与当前程序的连接提供数据支撑。
///
///
public string ConnectionString { get; private set; } = "127.0.0.1:6379,ssl=False";
///
/// 【方案名】
///
/// 摘要:
/// 获取/设置1个方案名,该连接字符串只用于实例化“Microsoft SQL Server”数据库软件中的指定表的方案名,为建立“Microsoft SQL Server”数据库软件中的指定数据库中的指定表与当前程序的连接提供数据支撑。
///
///
public string SchemaName { get; private set; } = "dbo";
///
/// 【表名】
///
/// 摘要:
/// 获取/设置1个表名,该连接字符串只用于实例化“Microsoft SQL Server”数据库软件中的指定表,为建立“Microsoft SQL Server”数据库软件中的指定数据库中的指定表与当前程序的连接提供数据支撑。
///
///
public string TableName { get; private set; } = "DistributedCache";
}
}
2 分布式缓存中间件的调用定义
2.1 Core.HashHelper
using System.Security.Cryptography;
namespace Core
{
///
/// 【哈希助手--类】
///
/// 摘要:
/// 该类通过1个指定哈希加密算法生成1个唯一性的字符串(当前安全性较强的SHA-2包括有:SHA-224、SHA-256、SHA-384,和 SHA-512等; 比较常用但已经不安全的有:MD5、SHA1等)。
///
///
public class HashHelper
{
/// name="data">一个字节数组实例,把任意类型的一个指定数据以二进制的格式储着到该实例中。
/// name="hashAlgorithm">
/// 一个指定哈希加密算法名称的字符串常量(当前安全性较强的SHA-2包括有:SHA-224、SHA-256、SHA-384,和 SHA-512等; 比较常用但已经不安全的有:MD5、SHA1等)。
///
/// name="trimByteCount">
/// 截断长度值,该值指示字节数组实例中,有多少个二进制数据被用于构建哈希字符串,默认值为:0,即不执行截断操作,字节数组实例中的所有二进制数据都将被用于构建哈希字符串。
///
///
/// 【创建哈希】
///
/// 返回:
/// 1个唯一性的字符串。
///
///
/// 摘要:
/// 通过指定的哈希加密算法,生成1个唯一性的字符串。
/// 注意:
/// 1、该方法用于生成一个唯一性的字符串,该字符串相当于一个唯一个的编号值,此时该方法与“Guid.NewGuid()”方法的功能相当;或用于安全性求较高的加/解密操作。
/// 2、该方法的应用场景取决于哈希加密算法名称,如果用于生成一个唯一个的编号值,则使用“MD5、SHA1”即可;如果用于安全性求较高的加/解密操作,则只有“SHA512”。
///
///
public static string CreateHash(byte[] data, string hashAlgorithm, int trimByteCount = 0)
{
if (string.IsNullOrEmpty(hashAlgorithm))
throw new ArgumentNullException(nameof(hashAlgorithm));
var algorithm = (HashAlgorithm)CryptoConfig.CreateFromName(hashAlgorithm);
if (algorithm == null)
throw new ArgumentException("使用未知的哈希加密算法,或不存该名称的哈希加密算法。");
if (trimByteCount > 0 && data.Length > trimByteCount)
{
var newData = new byte[trimByteCount];
Array.Copy(data, newData, trimByteCount);
return BitConverter.ToString(algorithm.ComputeHash(newData)).Replace("-", string.Empty);
}
return BitConverter.ToString(algorithm.ComputeHash(data)).Replace("-", string.Empty);
}
}
}
2.2 Core.Caching.EntityCacheDefaults
using Core.Domain;
namespace Core.Caching
{
///
/// 【默认实体缓存--类】
///
/// 摘要:
/// 通过该类中属性成员为1个指定实体类构建相应的键字符串;或构建键字符串的前缀字符串。
///
///
public static class EntityCacheDefaults<TEntity> where TEntity : BaseEntity
{
///
/// 【实体类型名称】
///
/// 摘要:
/// 获取1个指定实体类的名称字符串(该名称字符串被格式化为全小写),该字符串只能为其它相应完整键字符串的拼接操作提供数据支撑。
///
///
public static string EntityTypeName => typeof(TEntity).Name.ToLowerInvariant();
///
/// 【编号缓存键】
///
/// 摘要:
/// 获取缓存键类的1个指定实例,该实例支持拼接操作并包含: 1个指定实体类1个指定的编号值对应1个指定实例的键字符串、前缀列表实例,缓存时间等。
/// {0} : 1个指定实体类1个指定的编号值。
///
///
public static CacheKey ByIdCacheKey => new($"{EntityTypeName}.byid.{{0}}", ByIdPrefix, Prefix);
///
/// 【编号集缓存键】
///
/// 摘要:
/// 获取缓存键类的1个指定实例,该实例支持拼接操作并包含: 1个指定实体类n个指定的编号值对应n个指定实例的键字符串、前缀列表实例,缓存时间等。
/// {0} : 键字符串的拼接字符串,该字符串是把n个指定的编号值经过哈西加密算法操作后获取。
///
///
public static CacheKey ByIdsCacheKey => new($"{EntityTypeName}.byids.{{0}}", ByIdsPrefix, Prefix);
///
/// 【所有缓存键】
///
/// 摘要:
/// 获取缓存键类的1个指定实例,该实例包含:1个指定实体的所有实例的的键字符串、前缀列表实例,缓存时间等。
///
///
public static CacheKey AllCacheKey => new($"Nop.{EntityTypeName}.all.", AllPrefix, Prefix);
///
/// 【根前缀】
///
/// 摘要:
/// 把1个指定实体类的名称字符串作为指定键字符串的根前缀,该字符串只能为其它相应完整键字符串的拼接操作提供数据支撑。
///
///
public static string Prefix => $"{EntityTypeName}.";
///
/// 【编号前缀】
///
/// 摘要:
/// 获取1个指定实体类1个指定的编号值对应1个指定实例的键字符串的根前缀,该字符串只能为其它相应完整键字符串的拼接操作提供数据支撑。
///
///
public static string ByIdPrefix => $"{EntityTypeName}.byid.";
///
/// 【编号集前缀】
///
/// 摘要:
/// 获取1个指定实体类n个指定的编号值对应n个指定实例的键字符串的根前缀,该字符串只能为其它相应完整键字符串的拼接操作提供数据支撑。
///
///
public static string ByIdsPrefix => $"{EntityTypeName}.byids.";
///
/// 【所有前缀】
///
/// 摘要:
/// 获取1个指定实体的所有实例的键字符串,该键字符串也可以作为其它相应键字符串的前缀字符串。
///
///
public static string AllPrefix => $"{EntityTypeName}.all.";
}
}
2.2 Core.Caching.CacheKey
using Core.Configuration;
using Core.Infrastructure;
namespace Core.Caching
{
///
/// 【缓存键--类】
///
/// 摘要:
/// 该类用于实例化1个指定的缓存键实例,该实例包含:键字符串、前缀列表实例,缓存时间等。
///
///
public class CacheKey
{
#region 拷贝构造方法
/// name="key">1个指定的键字符串
/// name="prefixes">数组实例,该实例存储着1缓存键实例所对应所有前缀字符串,为相应键字符串的拼接提供数据支撑。
///
/// 【拷贝构造方法】
///
/// 摘要:
/// 通过拷贝构造方法,对当前类中的同名属性成员进行实例化。
///
///
public CacheKey(string key, params string[] prefixes)
{
Key = key;
Prefixes.AddRange(prefixes.Where(prefix => !string.IsNullOrEmpty(prefix)));
}
#endregion
#region 属性
///
/// 【键】
///
/// 摘要:
/// 获取/设置1个指定的键字符串。
///
///
public string Key { get; protected set; }
///
/// 【前缀】
///
/// 摘要:
/// 获取/设置列表实例,该实例存储着1缓存键实例所对应所有前缀字符串,为相应键字符串的拼接提供数据支撑。
///
///
public List<string> Prefixes { get; protected set; } = new List<string>();
///
/// 【缓存时间】
///
/// 摘要:
/// 获取/设置1个指定的键字符串在内存中的缓存时间(默认设置为:60分钟=1小时)。
/// 说明:
/// 1个指定缓存项(键/值对)在内存中的缓存时间,实际是通过键字符串的缓存时间进行控制的,即只要过了指定键字符串的缓存时间,其所对应的值也同时被销毁了,
/// 那么其所构建的缓存项(键/值对)在内存中也就不存在了。
///
///
public int CacheTime { get; set; } = Singleton<AppSettings>.Instance.Get<CacheConfig>().DefaultCacheTime;
#endregion
#region 方法
/// name="createCacheKeyParameters">1个具有返回值的委托方法实例(这里特指:“CacheKeyService.CreateCacheKeyParameters”方法,该实例用于把下面的数组实例中的所有数据值转换为指定的格式。
/// name=" keyObjects">数组实例,该实例中存储着n个泛型实例,这些实例为缓存键字符串的拼接提供数据支撑。
///
/// 【构建】
///
/// 摘要:
/// 根据数组实例,获取缓存键类的1个指定实例,,该实例包含:键字符串、前缀列表实例,缓存时间等。
///
///
/// 返回:
/// 缓存键类的1个指定实例。
///
///
public virtual CacheKey Create(Func<object, object> createCacheKeyParameters, params object[] keyObjects)
{
//获取缓存键类的1个指定实例。
var cacheKey = new CacheKey(Key, Prefixes.ToArray());
//如果数组实例中不存在任何的实例成员,则直接返回缓存键类的1个指定实例。
if (!keyObjects.Any())
return cacheKey;
//如果数组实例中存在实例成员,且该实例成员能够进行拼接操作,则对缓存键类的1个指定实例中的键字符串进行重新拼接。
cacheKey.Key = string.Format(cacheKey.Key, keyObjects.Select(createCacheKeyParameters).ToArray());
//如果列表成员中的实例能够进行拼接操作,则对该实例中的键字符串进行重新拼接。
for (var i = 0; i < cacheKey.Prefixes.Count; i++)
cacheKey.Prefixes[i] = string.Format(cacheKey.Prefixes[i], keyObjects.Select(createCacheKeyParameters).ToArray());
return cacheKey;
}
#endregion
#region 嵌套类
///
/// 【缓存键相等比较--类】
///
/// 摘要:
/// 通过该类中的方法成员来验证缓存键类的两个实例是否相等。
///
///
public class CacheKeyEqualityComparer : IEqualityComparer<CacheKey>
{
/// name="x">缓存键类的1个指定实例。
/// name="y">缓存键类的另1个指定实例。
///
/// 【相等?】
///
/// 摘要:
/// 获取1个值false(不相等)/true(相等),该值指示缓存键类的两个实例是否相等。
///
///
/// 返回:
/// 1个值false(不相等)/true(相等)。
///
///
public bool Equals(CacheKey x, CacheKey y)
{
if (x == null && y == null)
return true;
return x?.Key.Equals(y?.Key, StringComparison.OrdinalIgnoreCase) ?? false;
}
/// name="obj">缓存键类的1个指定实例。
///
/// 【哈西转换】
///
/// 摘要:
/// 缓存键类的1个指定实例中的键字符串经过了哈西加密操作,则把该键字符串转换为相应的整型值。
/// 说明:
/// 在该类中,相等比较方法是必须的,而哈西转换方法只是为了所继承接口同名方法的必须实现。
///
///
/// 返回:
/// 经过哈西加密操作字符串所转换的整型值。
///
///
public int GetHashCode(CacheKey obj)
{
return obj.Key.GetHashCode();
}
}
#endregion
}
}
2.3 Core.Caching.CacheKeyService
using System.Collections.Concurrent;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Primitives;
using Core.Configuration;
namespace Core.Caching
{
///
/// 【内存缓存管理器--类】
///
/// 摘要:
/// 通过该类中的方法成员实现了通过“Microsoft.Extensions.Caching.Memory”程序集对缓存数据的管理操作,这些管理操作包含:缓存数据加载,缓存数据移除和缓存数据清理等。
/// 说明:
/// 1、当前程序中所定义的通过时间进行触发的计划任务,在默认情况下是利用“Microsoft.Extensions.Caching.Memory”程序集的缓存时间的控制模式及其缓存时间,定时触发这些计划任务。
/// 即如果当前程序没有启用分布式数据库,那么在默认状况下会通过当前类中的“PerformActionWithLockAsync”重写方法定时触发这些计划任务;
/// 如果删除该类定义当前程序则必须的启用分布式数据库,才能保证计划任务进行定时触发,否则将不能定时触发计划任务。
/// 2、为了保证在默认情况下定时触发计划任务,没有删除该类的定义,如果当前程序一开始就启用分布式数据库,可以删除该类的定义。
///
///
public class MemoryCacheManager : CacheKeyService, ILocker, IStaticCacheManager
{
#region 变量--私有/保护
///
/// 【已经销毁?】
///
/// 摘要:
/// 设置1个值false(默认值:未销毁)/true(已经销毁),该值指示当前类的实例(非托管资源)是否已经被操作系统标记为:“已经销毁”状态;或已经被操作系统所销毁。
///
///
private bool _disposed;
///
/// 【内存缓存】
///
/// 摘要:
/// 内存缓存接口实例,该实例实际上是:“Microsoft.Extensions.Caching.Memory”程序集的实例,通过该实例实现当前程序通过“Microsoft.Extensions.Caching.Memory”程序集的实例与内存缓存数据库的交互操作。
///
///
private readonly IMemoryCache _memoryCache;
///
/// 【项集】
///
/// 摘要:
/// 线程安全字典实例,该实例以键/值对的形式存储着当前程序中的所有缓存键实例及其所对应的值。
/// 线程安全字典:
/// 1、如果不使用线程安全字典实例,则在多线程下每次实例的加载都要通过锁实例进行控制。
/// 2、在默认情况下,如果使用线程安全字典实例,微软默认隐式设定锁实例数量是CPU核的个数。
/// 3、当然开发者也可以自定义更多的线程(>CPU核的个数),但同时开发者也必须显式的为这些线程定义同样多的锁实例数量(>CPU核的个数)。
/// 说明:
/// 字典实例中所存储键/值对中的值是1个指定实体的1/n实例;而分布式缓存数据库中所存储键/值对中的值是经过JSON格式编码后的1个指定实体的1/n实例。
///
///
private static readonly ConcurrentDictionary<string, CancellationTokenSource> _prefixes = new();
///
/// 【取消标记】
///
/// 摘要:
/// 一个取消标记资源(CancellationTokenSource)实例,当缓存项中的保留时间过期后,自动调用该实例的相应方法来移除已经过期的缓存项,该实例的的初始化/实例化操作是直接通过“new”关键字实现的。
/// 为什么需要CancellationToken?:
/// 因为Task没有方法支持在外部取消Task,只能通过一个公共变量存放线程的取消状态,在线程内部通过变量判断线程是否被取消,当CancellationToken是取消状态,
/// Task内部未启动的任务不会启动新线程。取消标记(CancellationToken) ,正确并合理的使用 CancellationToken 可以让业务达到简化代码、
/// 提升服务性能的效果;当在业务开发中,需要对一些特定的应用场景进行深度干预的时候,CancellationToken 将发挥非常重要的作用。
/// 注意:
/// 该实例被限定为静态,即如果不执行其它强制性的操作,该实例的生命周期,将会存在于程序执行的整个过程中。
///
///
private static CancellationTokenSource _clearToken = new();
#endregion
#region 拷贝构造方法
/// name="appSettings">应用配置类的1个指定实例。
/// name="memoryCache">内存缓存接口实例,该实例实际上是:“Microsoft.Extensions.Caching.Memory”程序集的实例,通过该实例实现当前程序通过“Microsoft.Extensions.Caching.Memory”程序集的实例与内存缓存数据库的交互操作。
///
/// 【拷贝构造方法】
///
/// 摘要:
/// 通过拷贝构造方法,对当前类中的同名变量成员进行实例化。
///
///
public MemoryCacheManager(AppSettings appSettings, IMemoryCache memoryCache) : base(appSettings)
{
_memoryCache = memoryCache;
}
#endregion
#region 方法----私有/保护
/// name="key">缓存键类的1个指定实例。
///
/// 【入口操作预处理】
///
/// 摘要:
/// 获取内存缓存入口操作实例,该实例为键字符串的缓存时间控制模式及其控制时间提供数据支撑。
/// 说明:
/// 1个指定缓存项(键/值对)在内存中的缓存时间,实际是通过键字符串的缓存时间进行控制的,即只要过了指定键字符串的缓存时间,其所对应的值也同时被销毁了,
/// 那么其所构建的缓存项(键/值对)在内存中也就不存在了。
///
///
/// 返回:
/// 内存缓存入口操作实例。
///
///
private MemoryCacheEntryOptions PrepareEntryOptions(CacheKey key)
{
//AbsoluteExpirationRelativeToNow:以绝对过期时间模式,对指定键字符串的缓存时间进行控制;在缓存时间不管该指定缓存项(键/值对)经过多少次调用,到缓存时间该指定缓存项(键/值对)就要被销毁。
//SlidingExpiration:相对对过期时间模式,对指定键字符串的缓存时间进行控制;在缓存时间在最后1次调用的时间后顺延1个单位的缓存时间,只有完事度过整个缓存时间该指定缓存项(键/值对)才能被销毁。
var options = new MemoryCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(key.CacheTime)
};
//设置取消标记资源(CancellationTokenSource)实例,当过了缓存时间过期后,该实例将自动释放内存缓存中所有的过期缓存项(键/值对)。
options.AddExpirationToken(new CancellationChangeToken(_clearToken.Token));
foreach (var keyPrefix in key.Prefixes.ToList())
{
var tokenSource = _prefixes.GetOrAdd(keyPrefix, new CancellationTokenSource());
options.AddExpirationToken(new CancellationChangeToken(tokenSource.Token));
}
return options;
}
/// name="cacheKey">缓存键类的1个指定实例。
/// name="cacheKeyParameters">数组实例,该实例中存储着n个泛型实例,这些实例为缓存键字符串的拼接提供数据支撑。
///
/// 【移除】
///
/// 摘要:
/// 根据缓存键类的1个新的指定实例,从内存缓存中移除1个指定的缓存项(键/值对)。
///
///
private void Remove(CacheKey cacheKey, params object[] cacheKeyParameters)
{
//拼接出缓存键类的1个新的指定实例。
cacheKey = PrepareKey(cacheKey, cacheKeyParameters);
//根据缓存键类的1个新的指定实例,从内存缓存中移除1个指定的缓存项(键/值对)。
_memoryCache.Remove(cacheKey.Key);
}
/// name="key">缓存键类的1个指定实例。
/// name="data">1个指定的泛型实例,该实例中存储着该1个指定实体的1/n实例。
///
/// 【设置】
///
/// 摘要:
/// 把构建的1个指定的缓存项(键/值对),及其缓存时间模式和缓存时间,加载到内存缓存中。
///
///
private void Set(CacheKey key, object data)
{
//如果缓存键实例的缓存缓存时间小于等于0或1个指定的泛型实例的实例值为:null,则不再执行任何缓存操作,直接退出该方法。
if ((key?.CacheTime ?? 0) <= 0 || data == null)
return;
//把构建的1个指定的缓存项(键/值对),及其缓存时间模式和缓存时间,加载到内存缓存中。
_memoryCache.Set(key.Key, data, PrepareEntryOptions(key));
}
#endregion
#region 方法--销毁
///
/// 【销毁】
///
/// 摘要:
/// 通过显式调用当前方法把当前类的实例被操作系统标(非托管资源)标记为:“已经销毁”状态;或已经被操作系统所销毁。
///
///
public void Dispose()
{
Dispose(true);
// SuppressFinalize:当开发者已经显式调用当前方法(Dispose或者Close),通过操作系统已经把当前类的实例(非托管资源)标记为:“已经销毁”状态时,
// 如果开发者重复通过显式调用当前方法来销毁当前类的实例(非托管资源)时,由于当前类的实例(非托管资源)已经处于“已经销毁”状态,或已经被销毁,
// 因而需要“ SuppressFinalize”强制通过终止执行当前类的析构方法来避免当前类的实例(非托管资源)再1次的销毁操作,从而避免未知异常的产生。
GC.SuppressFinalize(this);
}
/// name="disposing">指示当前类的实例(非托管资源)是否需要执行销毁操作,默认值:true,即执行销毁操作。
///
/// 【销毁】
///
/// 摘要:
/// 为当前类的实例(非托管资源)被操作系统标(非托管资源)标记为:“已经销毁”状态;或已经被操作系统所销毁,提供方法支撑。
///
///
protected virtual void Dispose(bool disposing)
{
if (_disposed)
return;
//显式的把内存缓存接口实例(非托管资源)标记为:“已经销毁”状态。
if (disposing)
_memoryCache.Dispose();
_disposed = true;
}
#endregion
#region 方法--接口实现--ILocker
/// name="resource">1个指定缓存键字符串(这里特指1个指定计划任务的命名字符串)。
/// name="expirationTime">1个指定缓存项(键/值对,这里特指计划任务实例的键/值对缓存项)在Redis分布式缓存数据库中的缓存时间。
/// name="action">1个无返回值的异步委托方法实例,该异步委托方法实例定时触发指定的计划任务实例。
///
/// 【异步执行加锁操作?】
///
/// 摘要:
/// 获取1个值false(失败)/true(成功),该值指示已经通过内存缓存时间的控制模式和缓存时间,定时触发指定的计划任务实例是否已经成功触发并执行完成。
/// 说明:
/// 在默认情况下使用内存缓存缓存时间的控制模式和缓存时间,定时触发指定的计划任务实例。
///
///
/// 返回:
/// 1个值false(失败)/true(成功)
///
///
public async Task<bool> PerformActionWithLockAsync(string resource, TimeSpan expirationTime, Func<Task> action)
{
//AbsoluteExpirationRelativeToNow:以绝对过期时间模式,对指定键字符串的缓存时间进行控制;在缓存时间不管该指定缓存项(键/值对)经过多少次调用,到缓存时间该指定缓存项(键/值对)就要被销毁。
//SlidingExpiration:相对对过期时间模式,对指定键字符串的缓存时间进行控制;在缓存时间在最后1次调用的时间后顺延1个单位的缓存时间,只有完事度过整个缓存时间该指定缓存项(键/值对)才能被销毁。
var isSet = await _memoryCache.GetOrCreateAsync(resource, cacheEntry =>
{
cacheEntry.AbsoluteExpiration = DateTimeOffset.Now;
return Task.FromResult(false);
});
if (isSet)
return false;
try
{
//把1指定的计划任务实例以键/值对的形式存储到内存缓存中,为指定的计划任务实例的定时触发,提供内存缓存时间的控制模式和缓存时间。
await _memoryCache.GetOrCreateAsync(resource, cacheEntry =>
{
cacheEntry.AbsoluteExpirationRelativeToNow = expirationTime;
return Task.FromResult(true);
});
//异步委托方法实例,通过内存缓存时间的控制模式和缓存时间, 定时触发指定的计划任务实例。
await action();
return true;
}
finally
{
//如果定时触发指定的计划任务实例失败,则从内存缓存中移除指定的计划任务实例的键/值对缓存项。
_memoryCache.Remove(resource);
}
}
/// name="key">1个指定缓存键字符串(这里特指1个指定计划任务的命名字符串)。
/// name="expirationTime">1个指定缓存项(键/值对,这里特指计划任务实例的键/值对缓存项)在Redis分布式缓存数据库中的缓存时间。
/// name="action">1个无返回值的异步委托方法实例,该异步委托方法实例定时触发指定的计划任务实例。
///
/// 【执行加锁操作?】
///
/// 摘要:
/// 获取1个值false(失败)/true(成功),该值指示已经通过内存缓存时间的控制模式和缓存时间,定时触发指定的计划任务实例是否已经成功触发并执行完成。
/// 说明:
/// 在默认情况下使用内存缓存缓存时间的控制模式和缓存时间,定时触发指定的计划任务实例。
///
///
/// 返回:
/// 1个值false(失败)/true(成功)
///
///
public bool PerformActionWithLock(string key, TimeSpan expirationTime, Action action)
{
//如果内存缓存已经存在指定的计划任务实例的键/值对缓存项,则不需要加载操作,并直接退出该方法。
if (_memoryCache.TryGetValue(key, out _))
return false;
try
{
//如果内存缓存不存在指定的计划任务实例的键/值对缓存项,则把该指定的计划任务实例的键/值对缓存项加载到内存缓存中。
_memoryCache.Set(key, key, expirationTime);
//委托方法实例,通过内存缓存时间的控制模式和缓存时间, 定时触发指定的计划任务实例。
action();
return true;
}
finally
{
//如果定时触发指定的计划任务实例失败,则从内存缓存中移除指定的计划任务实例的键/值对缓存项。
_memoryCache.Remove(key);
}
}
#endregion
#region 方法--接口实现--IStaticCacheManager
/// name="cacheKey">缓存键类的1个指定实例。
/// name="cacheKeyParameters">数组实例,该实例中存储着n个泛型实例,这些实例为缓存键字符串的拼接提供数据支撑。
///
/// 【异步移除】
///
/// 摘要:
/// 根据缓存键类的1个新的指定实例,从内存缓存中移除1个指定的缓存项(键/值对)。
///
/// -
public Task RemoveAsync(CacheKey cacheKey, params object[] cacheKeyParameters)
{
Remove(cacheKey, cacheKeyParameters);
return Task.CompletedTask;
}
///
/// name="key">缓存键类的1个指定实例。
/// name="acquire">1个具有返回值的泛型异步委托方法实例,该泛型异步委托方法实例用于获取1个指定实体的1/n个实例。
///
/// 【异步获取】
///
/// 摘要:
/// 获取一个指定的实体的1个指定实体的1/n个实例。
///
///
/// 返回:
/// 1个指定实体的1/n个实例。
///
///
public async Task<T> GetAsync<T>(CacheKey key, Func<Task<T>> acquire)
{
//如果缓存键实例的缓存时间小于等于:0,就不要对1个指定实体的1/n实例进行缓存管理,则直接退出当前方法,并获取1个指定实体的1/n实例。
if ((key?.CacheTime ?? 0) <= 0)
return await acquire();
//如果内存缓存中存在指定的键/值对,则获取1个指定实体的1/n实例。
if (_memoryCache.TryGetValue(key.Key, out T result))
return result;
//如果未缓存,则调用1个指定实体的1/n实例的委托方法。
result = await acquire();
//如果存在1个指定实体的1/n实例,则把该实例键/值对形式存储到内存缓存中进行管理。
if (result != null)
await SetAsync(key, result);
//返回1个指定实体的1/n实例。
return result;
}
///
/// name="key">缓存键类的1个指定实例。
/// name="acquire">1个具有返回值的泛型委托方法实例,该泛型委托方法实例用于获取1个指定实体的1/n个实例。
///
/// 【异步获取】
///
/// 摘要:
/// 获取一个指定的实体的1个指定实体的1/n个实例。
///
///
/// 返回:
/// 1个指定实体的1/n个实例。
///
///
public async Task<T> GetAsync<T>(CacheKey key, Func<T> acquire)
{
//如果缓存键实例的缓存时间小于等于:0,就不要对1个指定实体的1/n实例进行缓存管理,则直接退出当前方法,并获取1个指定实体的1/n实例。
if ((key?.CacheTime ?? 0) <= 0)
return acquire();
//如果内存缓存中存在指定的键/值对,则获取1个指定实体的1/n实例;如果内存缓存中不存在指定的键/值对,则把1个指定实体的1/n实例以键/值对的形式存储到内存缓存中,并返回1个指定实体的1/n实例。
var result = _memoryCache.GetOrCreate(key.Key, entry =>
{
entry.SetOptions(PrepareEntryOptions(key));
return acquire();
});
//如果被缓存的实例值为:null,则从内存缓存中移除缓存键类的1个指定实例
if (result == null)
await RemoveAsync(key);
//返回1个指定实体的1/n实例。
return result;
}
///
/// name="key">缓存键类的1个指定实例。
/// name="acquire">1个具有返回值的泛型委托方法实例,该泛型委托方法实例用于获取1个指定实体的1/n个实例。
///
/// 【获取】
///
/// 摘要:
/// 获取一个指定的实体的1个指定实体的1/n个实例。
///
///
/// 返回:
/// 1个指定实体的1/n个实例。
///
///
public T Get<T>(CacheKey key, Func<T> acquire)
{
//如果缓存键实例的缓存时间小于等于:0,就不要对1个指定实体的1/n实例进行缓存管理,则直接退出当前方法,并获取1个指定实体的1/n实例。
if ((key?.CacheTime ?? 0) <= 0)
return acquire();
//如果内存缓存中存在1个指定实体的1/n实例,则获取1个指定实体的1/n实例。
if (_memoryCache.TryGetValue(key.Key, out T result))
return result;
//内存缓存中不存在1个指定实体的1/n实例,则调用1个指定实体的1/n实例的委托方法。
result = acquire();
//如果被缓存的实例值不为:null,则把1个指定实体的1/n实例以键/值对的形式存储到内存缓存中。
if (result != null)
Set(key, result);
//返回1个指定实体的1/n实例。
return result;
}
/// name="key">缓存键类的1个指定实例。
/// name="data">1个指定的泛型实例,该实例中存储着该1个指定实体的1/n实例。
///
/// 【异步设置】
///
/// 摘要:
/// 把构建的1个指定的缓存项(键/值对),及其缓存时间模式和缓存时间,加载到内存缓存中。
///
///
public Task SetAsync(CacheKey key, object data)
{
Set(key, data);
return Task.CompletedTask;
}
/// name="prefix">1个指定的前缀字符串。
/// name="prefixParameters">数组实例,该实例中存储着n个泛型实例,这些实例为前缀字符串的拼接提供数据支撑。
///
/// 【异步通过前缀字符串移除】
///
/// 摘要:
/// 根据1个新的前缀字符串,从字典实例中移除指定的键/值对实例。。
///
///
public Task RemoveByPrefixAsync(string prefix, params object[] prefixParameters)
{
RemoveByPrefix(prefix, prefixParameters);
return Task.CompletedTask;
}
/// name="prefix">1个指定的前缀字符串。
/// name="prefixParameters">数组实例,该实例中存储着n个泛型实例,这些实例为前缀字符串的拼接提供数据支撑。
///
/// 【通过前缀字符串移除】
///
/// 摘要:
/// 根据1个新的前缀字符串,从字典实例中移除指定的键/值对实例。。
///
///
public void RemoveByPrefix(string prefix, params object[] prefixParameters)
{
//拼接出1个新的前缀字符串。
prefix = PrepareKeyPrefix(prefix, prefixParameters);
//根据1个新的前缀字符串,从字典实例中移除指定的键/值对实例。
_prefixes.TryRemove(prefix, out var tokenSource);
//显式销毁释放标记资源(CancellationTokenSource)实例。
tokenSource?.Cancel();
tokenSource?.Dispose();
}
///
/// 【异步清理】
///
/// 摘要:
/// 销毁释放字典实例中存储着的所有键/值对实例。
///
///
public Task ClearAsync()
{
//销毁旧的取消标记资源(CancellationTokenSource)实例。
_clearToken.Cancel();
_clearToken.Dispose();
//实例化一个新的取消标记资源(CancellationTokenSource)实例。
_clearToken = new CancellationTokenSource();
//销毁释放字典实例中存储着的所有键/值对实例,并抛出该字典实例中取消标记资源(CancellationTokenSource)实例(字典实例中的值)。
foreach (var prefix in _prefixes.Keys.ToList())
{
_prefixes.TryRemove(prefix, out var tokenSource);
//显式销毁释放标记资源(CancellationTokenSource)实例。
tokenSource?.Dispose();
}
return Task.CompletedTask;
}
#endregion
}
}
2.4 Core.Caching.ILocker
namespace Core.Caching
{
///
/// 【锁--接口】
///
/// 摘要:
/// 继承于该接口的具体体实现类指示已经通过内存/分布式数据库缓存时间的控制模式和缓存时间,定时触发指定的计划任务实例是否已经成功触发并执行完成。
/// 说明:
/// 在默认情况下使用内存缓存缓存时间的控制模式和缓存时间,定时触发指定的计划任务实例;如果启用对分布式数据库的支持后,使用Redis分布式数据库缓存时间的控制模式和缓存时间,定时触发指定的计划任务实例。
///
///
public interface ILocker
{
/// name="resource">1个指定缓存键字符串(这里特指1个指定计划任务的命名字符串)。
/// name="expirationTime">1个指定缓存项(键/值对,这里特指计划任务实例的键/值对缓存项)在Redis分布式缓存数据库中的缓存时间。
/// name="action">1个无返回值的异步委托方法实例,该异步委托方法实例定时触发指定的计划任务实例。
///
/// 【异步执行加锁操作?】
///
/// 摘要:
/// 获取1个值false(失败)/true(成功),该值指示已经通过内存/分布式数据库缓存时间的控制模式和缓存时间,定时触发指定的计划任务实例是否已经成功触发并执行完成。
/// 说明:
/// 在默认情况下使用内存缓存缓存时间的控制模式和缓存时间,定时触发指定的计划任务实例;如果启用对分布式数据库的支持后,使用Redis分布式数据库缓存时间的控制模式和缓存时间,定时触发指定的计划任务实例。
///
///
/// 返回:
/// 1个值false(失败)/true(成功)
///
///
Task<bool> PerformActionWithLockAsync(string resource, TimeSpan expirationTime, Func<Task> action);
}
}
2.5 Core.Caching.IStaticCacheManager
namespace Core.Caching
{
///
/// 【静态缓存管理器--接口】
///
/// 摘要:
/// 通过继承于该接口的具体类中的方法成员实现了通过Microsoft.Extensions.Caching.Memory”程序集/Redis软件对缓存数据的管理操作,这些管理操作包含:缓存数据加载,缓存数据移除和缓存数据清理等。
///
///
public interface IStaticCacheManager : IDisposable
{
///
/// name="key">缓存键类的1个指定实例。
/// name="acquire">1个具有返回值的泛型异步委托方法实例,该泛型异步委托方法实例用于获取1个指定实体的1/n个实例。
///
/// 【异步获取】
///
/// 摘要:
/// 获取一个指定的实体的1个指定实体的1/n个实例。
///
///
/// 返回:
/// 1个指定实体的1/n个实例。
///
///
Task<T> GetAsync<T>(CacheKey key, Func<Task<T>> acquire);
///
/// name="key">缓存键类的1个指定实例。
/// name="acquire">1个具有返回值的泛型委托方法实例,该泛型委托方法实例用于获取1个指定实体的1/n个实例。
///
/// 【异步获取】
///
/// 摘要:
/// 获取一个指定的实体的1个指定实体的1/n个实例。
///
///
/// 返回:
/// 1个指定实体的1/n个实例。
///
///
Task<T> GetAsync<T>(CacheKey key, Func<T> acquire);
///
/// name="key">缓存键类的1个指定实例。
/// name="acquire">1个具有返回值的泛型委托方法实例,该泛型委托方法实例用于获取1个指定实体的1/n个实例。
///
/// 【获取】
///
/// 摘要:
/// 获取一个指定的实体的1个指定实体的1/n个实例。
///
///
/// 返回:
/// 1个指定实体的1/n个实例。
///
///
T Get<T>(CacheKey key, Func<T> acquire);
/// name="cacheKey">缓存键类的1个指定实例。
/// name="cacheKeyParameters">数组实例,该实例中存储着n个泛型实例,这些实例为缓存键字符串的拼接提供数据支撑。
///
/// 【异步移除】
///
/// 摘要:
/// 根据缓存键类的1个新的指定实例,从分布式数据库中移除1个指定的缓存项(键/(“JSON”编码格式的)值对)和字典实例中移除1个指定的键/值对。
///
///
Task RemoveAsync(CacheKey cacheKey, params object[] cacheKeyParameters);
/// name="key">缓存键类的1个指定实例。
/// name="data">1个指定的泛型实例,该实例中存储着该1个指定实体的1/n实例。
///
/// 【异步设置】
///
/// 摘要:
/// 把构建的1个指定的缓存项(键/(“JSON”编码格式的)值对),及其缓存时间模式和缓存时间,加载到分布式缓存中。
///
///
Task SetAsync(CacheKey key, object data);
/// name="prefix">1个指定的前缀字符串。
/// name="prefixParameters">数组实例,该实例中存储着n个泛型实例,这些实例为前缀字符串的拼接提供数据支撑。
///
/// 【异步通过前缀字符串移除实例】
///
/// 摘要:
/// 该方法用于根据1个新的前缀字符串,从缓存数据库中移除1/n个指定的缓存项(键/(“JSON”编码格式的)值对)。
///
///
Task RemoveByPrefixAsync(string prefix, params object[] prefixParameters);
/// name="prefix">1个指定的前缀字符串。
/// name="prefixParameters">数组实例,该实例中存储着n个泛型实例,这些实例为前缀字符串的拼接提供数据支撑。
///
/// 【通过前缀字符串移除实例】
///
/// 摘要:
/// 该方法用于根据1个新的前缀字符串,从缓存数据库中移除1/n个指定的缓存项(键/(“JSON”编码格式的)值对)。
///
///
void RemoveByPrefix(string prefix, params object[] prefixParameters);
///
/// 【异步清理】
///
/// 摘要:
/// 该方法用于从缓存数据库中移除1/n个指定的缓存项(键/(“JSON”编码格式的)值对),并销毁字典实例中存储着的所有键/值对实例。
///
///
Task ClearAsync();
/// name="cacheKey">缓存键类的1个指定实例。
/// name="cacheKeyParameters">数组实例,该实例中存储着n个泛型实例,这些实例为缓存键字符串的拼接提供数据支撑。
///
/// 【键预处理】
///
/// 摘要:
/// 如果数组实例中存在实例成员,且该实例成员能够进行拼接操作,则对缓存键类的1个指定实例中的键字符串进行重新拼接。
///
///
/// 返回:
/// 缓存键类的1个指定实例。
///
///
CacheKey PrepareKey(CacheKey cacheKey, params object[] cacheKeyParameters);
/// name="cacheKey">缓存键类的1个指定实例。
/// name="cacheKeyParameters">数组实例,该实例中存储着n个泛型实例,这些实例为缓存键字符串的拼接提供数据支撑。
///
/// 【键默认缓存预处理】
///
/// 摘要:
/// 如果数组实例中存在实例成员,且该实例成员能够进行拼接操作,则对缓存键类的1个指定实例中的键字符串进行重新拼接,
/// 同时对缓存键类的1个指定实例设定默认的缓存时间(默认值:60分钟=1小时,从“appsettings.json”文件设定中获取)。
///
///
/// 返回:
/// 缓存键类的1个指定实例,该实例包含:缓存时间。
///
///
CacheKey PrepareKeyForDefaultCache(CacheKey cacheKey, params object[] cacheKeyParameters);
/// name="cacheKey">缓存键类的1个指定实例。
/// name="cacheKeyParameters">数组实例,该实例中存储着n个泛型实例,这些实例为缓存键字符串的拼接提供数据支撑。
///
/// 【键最短缓存预处理】
///
/// 摘要:
/// 如果数组实例中存在实例成员,且该实例成员能够进行拼接操作,则对缓存键类的1个指定实例中的键字符串进行重新拼接,
/// 同时对缓存键类的1个指定实例设定默认的缓存时间(默认值:3分钟,从“appsettings.json”文件设定中获取)。
///
///
/// 返回:
/// 缓存键类的1个指定实例,该实例包含:缓存时间。
///
///
CacheKey PrepareKeyForShortTermCache(CacheKey cacheKey, params object[] cacheKeyParameters);
}
}
2.6 Core.Caching.MemoryCacheManager
using System.Collections.Concurrent;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Primitives;
using Core.Configuration;
namespace Core.Caching
{
///
/// 【内存缓存管理器--类】
///
/// 摘要:
/// 通过该类中的方法成员实现了通过“Microsoft.Extensions.Caching.Memory”程序集对缓存数据的管理操作,这些管理操作包含:缓存数据加载,缓存数据移除和缓存数据清理等。
/// 说明:
/// 1、当前程序中所定义的通过时间进行触发的计划任务,在默认情况下是利用“Microsoft.Extensions.Caching.Memory”程序集的缓存时间的控制模式及其缓存时间,定时触发这些计划任务。
/// 即如果当前程序没有启用分布式数据库,那么在默认状况下会通过当前类中的“PerformActionWithLockAsync”重写方法定时触发这些计划任务;
/// 如果删除该类定义当前程序则必须的启用分布式数据库,才能保证计划任务进行定时触发,否则将不能定时触发计划任务。
/// 2、为了保证在默认情况下定时触发计划任务,没有删除该类的定义,如果当前程序一开始就启用分布式数据库,可以删除该类的定义。
///
///
public class MemoryCacheManager : CacheKeyService, ILocker, IStaticCacheManager
{
#region 变量--私有/保护
///
/// 【已经销毁?】
///
/// 摘要:
/// 设置1个值false(默认值:未销毁)/true(已经销毁),该值指示当前类的实例(非托管资源)是否已经被操作系统标记为:“已经销毁”状态;或已经被操作系统所销毁。
///
///
private bool _disposed;
///
/// 【内存缓存】
///
/// 摘要:
/// 内存缓存接口实例,该实例实际上是:“Microsoft.Extensions.Caching.Memory”程序集的实例,通过该实例实现当前程序通过“Microsoft.Extensions.Caching.Memory”程序集的实例与内存缓存数据库的交互操作。
///
///
private readonly IMemoryCache _memoryCache;
///
/// 【项集】
///
/// 摘要:
/// 线程安全字典实例,该实例以键/值对的形式存储着当前程序中的所有缓存键实例及其所对应的值。
/// 线程安全字典:
/// 1、如果不使用线程安全字典实例,则在多线程下每次实例的加载都要通过锁实例进行控制。
/// 2、在默认情况下,如果使用线程安全字典实例,微软默认隐式设定锁实例数量是CPU核的个数。
/// 3、当然开发者也可以自定义更多的线程(>CPU核的个数),但同时开发者也必须显式的为这些线程定义同样多的锁实例数量(>CPU核的个数)。
/// 说明:
/// 字典实例中所存储键/值对中的值是1个指定实体的1/n实例;而分布式缓存数据库中所存储键/值对中的值是经过JSON格式编码后的1个指定实体的1/n实例。
///
///
private static readonly ConcurrentDictionary<string, CancellationTokenSource> _prefixes = new();
///
/// 【取消标记】
///
/// 摘要:
/// 一个取消标记资源(CancellationTokenSource)实例,当缓存项中的保留时间过期后,自动调用该实例的相应方法来移除已经过期的缓存项,该实例的的初始化/实例化操作是直接通过“new”关键字实现的。
/// 为什么需要CancellationToken?:
/// 因为Task没有方法支持在外部取消Task,只能通过一个公共变量存放线程的取消状态,在线程内部通过变量判断线程是否被取消,当CancellationToken是取消状态,
/// Task内部未启动的任务不会启动新线程。取消标记(CancellationToken) ,正确并合理的使用 CancellationToken 可以让业务达到简化代码、
/// 提升服务性能的效果;当在业务开发中,需要对一些特定的应用场景进行深度干预的时候,CancellationToken 将发挥非常重要的作用。
/// 注意:
/// 该实例被限定为静态,即如果不执行其它强制性的操作,该实例的生命周期,将会存在于程序执行的整个过程中。
///
///
private static CancellationTokenSource _clearToken = new();
#endregion
#region 拷贝构造方法
/// name="appSettings">应用配置类的1个指定实例。
/// name="memoryCache">内存缓存接口实例,该实例实际上是:“Microsoft.Extensions.Caching.Memory”程序集的实例,通过该实例实现当前程序通过“Microsoft.Extensions.Caching.Memory”程序集的实例与内存缓存数据库的交互操作。
///
/// 【拷贝构造方法】
///
/// 摘要:
/// 通过拷贝构造方法,对当前类中的同名变量成员进行实例化。
///
///
public MemoryCacheManager(AppSettings appSettings, IMemoryCache memoryCache) : base(appSettings)
{
_memoryCache = memoryCache;
}
#endregion
#region 方法----私有/保护
/// name="key">缓存键类的1个指定实例。
///
/// 【入口操作预处理】
///
/// 摘要:
/// 获取内存缓存入口操作实例,该实例为键字符串的缓存时间控制模式及其控制时间提供数据支撑。
/// 说明:
/// 1个指定缓存项(键/值对)在内存中的缓存时间,实际是通过键字符串的缓存时间进行控制的,即只要过了指定键字符串的缓存时间,其所对应的值也同时被销毁了,
/// 那么其所构建的缓存项(键/值对)在内存中也就不存在了。
///
///
/// 返回:
/// 内存缓存入口操作实例。
///
///
private MemoryCacheEntryOptions PrepareEntryOptions(CacheKey key)
{
//AbsoluteExpirationRelativeToNow:以绝对过期时间模式,对指定键字符串的缓存时间进行控制;在缓存时间不管该指定缓存项(键/值对)经过多少次调用,到缓存时间该指定缓存项(键/值对)就要被销毁。
//SlidingExpiration:相对对过期时间模式,对指定键字符串的缓存时间进行控制;在缓存时间在最后1次调用的时间后顺延1个单位的缓存时间,只有完事度过整个缓存时间该指定缓存项(键/值对)才能被销毁。
var options = new MemoryCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(key.CacheTime)
};
//设置取消标记资源(CancellationTokenSource)实例,当过了缓存时间过期后,该实例将自动释放内存缓存中所有的过期缓存项(键/值对)。
options.AddExpirationToken(new CancellationChangeToken(_clearToken.Token));
foreach (var keyPrefix in key.Prefixes.ToList())
{
var tokenSource = _prefixes.GetOrAdd(keyPrefix, new CancellationTokenSource());
options.AddExpirationToken(new CancellationChangeToken(tokenSource.Token));
}
return options;
}
/// name="cacheKey">缓存键类的1个指定实例。
/// name="cacheKeyParameters">数组实例,该实例中存储着n个泛型实例,这些实例为缓存键字符串的拼接提供数据支撑。
///
/// 【移除】
///
/// 摘要:
/// 根据缓存键类的1个新的指定实例,从内存缓存中移除1个指定的缓存项(键/值对)。
///
///
private void Remove(CacheKey cacheKey, params object[] cacheKeyParameters)
{
//拼接出缓存键类的1个新的指定实例。
cacheKey = PrepareKey(cacheKey, cacheKeyParameters);
//根据缓存键类的1个新的指定实例,从内存缓存中移除1个指定的缓存项(键/值对)。
_memoryCache.Remove(cacheKey.Key);
}
/// name="key">缓存键类的1个指定实例。
/// name="data">1个指定的泛型实例,该实例中存储着该1个指定实体的1/n实例。
///
/// 【设置】
///
/// 摘要:
/// 把构建的1个指定的缓存项(键/值对),及其缓存时间模式和缓存时间,加载到内存缓存中。
///
///
private void Set(CacheKey key, object data)
{
//如果缓存键实例的缓存缓存时间小于等于0或1个指定的泛型实例的实例值为:null,则不再执行任何缓存操作,直接退出该方法。
if ((key?.CacheTime ?? 0) <= 0 || data == null)
return;
//把构建的1个指定的缓存项(键/值对),及其缓存时间模式和缓存时间,加载到内存缓存中。
_memoryCache.Set(key.Key, data, PrepareEntryOptions(key));
}
#endregion
#region 方法--销毁
///
/// 【销毁】
///
/// 摘要:
/// 通过显式调用当前方法把当前类的实例被操作系统标(非托管资源)标记为:“已经销毁”状态;或已经被操作系统所销毁。
///
///
public void Dispose()
{
Dispose(true);
// SuppressFinalize:当开发者已经显式调用当前方法(Dispose或者Close),通过操作系统已经把当前类的实例(非托管资源)标记为:“已经销毁”状态时,
// 如果开发者重复通过显式调用当前方法来销毁当前类的实例(非托管资源)时,由于当前类的实例(非托管资源)已经处于“已经销毁”状态,或已经被销毁,
// 因而需要“ SuppressFinalize”强制通过终止执行当前类的析构方法来避免当前类的实例(非托管资源)再1次的销毁操作,从而避免未知异常的产生。
GC.SuppressFinalize(this);
}
/// name="disposing">指示当前类的实例(非托管资源)是否需要执行销毁操作,默认值:true,即执行销毁操作。
///
/// 【销毁】
///
/// 摘要:
/// 为当前类的实例(非托管资源)被操作系统标(非托管资源)标记为:“已经销毁”状态;或已经被操作系统所销毁,提供方法支撑。
///
///
protected virtual void Dispose(bool disposing)
{
if (_disposed)
return;
//显式的把内存缓存接口实例(非托管资源)标记为:“已经销毁”状态。
if (disposing)
_memoryCache.Dispose();
_disposed = true;
}
#endregion
#region 方法--接口实现--ILocker
/// name="resource">1个指定缓存键字符串(这里特指1个指定计划任务的命名字符串)。
/// name="expirationTime">1个指定缓存项(键/值对,这里特指计划任务实例的键/值对缓存项)在Redis分布式缓存数据库中的缓存时间。
/// name="action">1个无返回值的异步委托方法实例,该异步委托方法实例定时触发指定的计划任务实例。
///
/// 【异步执行加锁操作?】
///
/// 摘要:
/// 获取1个值false(失败)/true(成功),该值指示已经通过内存缓存时间的控制模式和缓存时间,定时触发指定的计划任务实例是否已经成功触发并执行完成。
/// 说明:
/// 在默认情况下使用内存缓存缓存时间的控制模式和缓存时间,定时触发指定的计划任务实例。
///
///
/// 返回:
/// 1个值false(失败)/true(成功)
///
///
public async Task<bool> PerformActionWithLockAsync(string resource, TimeSpan expirationTime, Func<Task> action)
{
//AbsoluteExpirationRelativeToNow:以绝对过期时间模式,对指定键字符串的缓存时间进行控制;在缓存时间不管该指定缓存项(键/值对)经过多少次调用,到缓存时间该指定缓存项(键/值对)就要被销毁。
//SlidingExpiration:相对对过期时间模式,对指定键字符串的缓存时间进行控制;在缓存时间在最后1次调用的时间后顺延1个单位的缓存时间,只有完事度过整个缓存时间该指定缓存项(键/值对)才能被销毁。
var isSet = await _memoryCache.GetOrCreateAsync(resource, cacheEntry =>
{
cacheEntry.AbsoluteExpiration = DateTimeOffset.Now;
return Task.FromResult(false);
});
if (isSet)
return false;
try
{
//把1指定的计划任务实例以键/值对的形式存储到内存缓存中,为指定的计划任务实例的定时触发,提供内存缓存时间的控制模式和缓存时间。
await _memoryCache.GetOrCreateAsync(resource, cacheEntry =>
{
cacheEntry.AbsoluteExpirationRelativeToNow = expirationTime;
return Task.FromResult(true);
});
//异步委托方法实例,通过内存缓存时间的控制模式和缓存时间, 定时触发指定的计划任务实例。
await action();
return true;
}
finally
{
//如果定时触发指定的计划任务实例失败,则从内存缓存中移除指定的计划任务实例的键/值对缓存项。
_memoryCache.Remove(resource);
}
}
/// name="key">1个指定缓存键字符串(这里特指1个指定计划任务的命名字符串)。
/// name="expirationTime">1个指定缓存项(键/值对,这里特指计划任务实例的键/值对缓存项)在Redis分布式缓存数据库中的缓存时间。
/// name="action">1个无返回值的异步委托方法实例,该异步委托方法实例定时触发指定的计划任务实例。
///
/// 【执行加锁操作?】
///
/// 摘要:
/// 获取1个值false(失败)/true(成功),该值指示已经通过内存缓存时间的控制模式和缓存时间,定时触发指定的计划任务实例是否已经成功触发并执行完成。
/// 说明:
/// 在默认情况下使用内存缓存缓存时间的控制模式和缓存时间,定时触发指定的计划任务实例。
///
///
/// 返回:
/// 1个值false(失败)/true(成功)
///
///
public bool PerformActionWithLock(string key, TimeSpan expirationTime, Action action)
{
//如果内存缓存已经存在指定的计划任务实例的键/值对缓存项,则不需要加载操作,并直接退出该方法。
if (_memoryCache.TryGetValue(key, out _))
return false;
try
{
//如果内存缓存不存在指定的计划任务实例的键/值对缓存项,则把该指定的计划任务实例的键/值对缓存项加载到内存缓存中。
_memoryCache.Set(key, key, expirationTime);
//委托方法实例,通过内存缓存时间的控制模式和缓存时间, 定时触发指定的计划任务实例。
action();
return true;
}
finally
{
//如果定时触发指定的计划任务实例失败,则从内存缓存中移除指定的计划任务实例的键/值对缓存项。
_memoryCache.Remove(key);
}
}
#endregion
#region 方法--接口实现--IStaticCacheManager
/// name="cacheKey">缓存键类的1个指定实例。
/// name="cacheKeyParameters">数组实例,该实例中存储着n个泛型实例,这些实例为缓存键字符串的拼接提供数据支撑。
///
/// 【异步移除】
///
/// 摘要:
/// 根据缓存键类的1个新的指定实例,从内存缓存中移除1个指定的缓存项(键/值对)。
///
/// -
public Task RemoveAsync(CacheKey cacheKey, params object[] cacheKeyParameters)
{
Remove(cacheKey, cacheKeyParameters);
return Task.CompletedTask;
}
///
/// name="key">缓存键类的1个指定实例。
/// name="acquire">1个具有返回值的泛型异步委托方法实例,该泛型异步委托方法实例用于获取1个指定实体的1/n个实例。
///
/// 【异步获取】
///
/// 摘要:
/// 获取一个指定的实体的1个指定实体的1/n个实例。
///
///
/// 返回:
/// 1个指定实体的1/n个实例。
///
///
public async Task<T> GetAsync<T>(CacheKey key, Func<Task<T>> acquire)
{
//如果缓存键实例的缓存时间小于等于:0,就不要对1个指定实体的1/n实例进行缓存管理,则直接退出当前方法,并获取1个指定实体的1/n实例。
if ((key?.CacheTime ?? 0) <= 0)
return await acquire();
//如果内存缓存中存在指定的键/值对,则获取1个指定实体的1/n实例。
if (_memoryCache.TryGetValue(key.Key, out T result))
return result;
//如果未缓存,则调用1个指定实体的1/n实例的委托方法。
result = await acquire();
//如果存在1个指定实体的1/n实例,则把该实例键/值对形式存储到内存缓存中进行管理。
if (result != null)
await SetAsync(key, result);
//返回1个指定实体的1/n实例。
return result;
}
///
/// name="key">缓存键类的1个指定实例。
/// name="acquire">1个具有返回值的泛型委托方法实例,该泛型委托方法实例用于获取1个指定实体的1/n个实例。
///
/// 【异步获取】
///
/// 摘要:
/// 获取一个指定的实体的1个指定实体的1/n个实例。
///
///
/// 返回:
/// 1个指定实体的1/n个实例。
///
///
public async Task<T> GetAsync<T>(CacheKey key, Func<T> acquire)
{
//如果缓存键实例的缓存时间小于等于:0,就不要对1个指定实体的1/n实例进行缓存管理,则直接退出当前方法,并获取1个指定实体的1/n实例。
if ((key?.CacheTime ?? 0) <= 0)
return acquire();
//如果内存缓存中存在指定的键/值对,则获取1个指定实体的1/n实例;如果内存缓存中不存在指定的键/值对,则把1个指定实体的1/n实例以键/值对的形式存储到内存缓存中,并返回1个指定实体的1/n实例。
var result = _memoryCache.GetOrCreate(key.Key, entry =>
{
entry.SetOptions(PrepareEntryOptions(key));
return acquire();
});
//如果被缓存的实例值为:null,则从内存缓存中移除缓存键类的1个指定实例
if (result == null)
await RemoveAsync(key);
//返回1个指定实体的1/n实例。
return result;
}
///
/// name="key">缓存键类的1个指定实例。
/// name="acquire">1个具有返回值的泛型委托方法实例,该泛型委托方法实例用于获取1个指定实体的1/n个实例。
///
/// 【获取】
///
/// 摘要:
/// 获取一个指定的实体的1个指定实体的1/n个实例。
///
///
/// 返回:
/// 1个指定实体的1/n个实例。
///
///
public T Get<T>(CacheKey key, Func<T> acquire)
{
//如果缓存键实例的缓存时间小于等于:0,就不要对1个指定实体的1/n实例进行缓存管理,则直接退出当前方法,并获取1个指定实体的1/n实例。
if ((key?.CacheTime ?? 0) <= 0)
return acquire();
//如果内存缓存中存在1个指定实体的1/n实例,则获取1个指定实体的1/n实例。
if (_memoryCache.TryGetValue(key.Key, out T result))
return result;
//内存缓存中不存在1个指定实体的1/n实例,则调用1个指定实体的1/n实例的委托方法。
result = acquire();
//如果被缓存的实例值不为:null,则把1个指定实体的1/n实例以键/值对的形式存储到内存缓存中。
if (result != null)
Set(key, result);
//返回1个指定实体的1/n实例。
return result;
}
/// name="key">缓存键类的1个指定实例。
/// name="data">1个指定的泛型实例,该实例中存储着该1个指定实体的1/n实例。
///
/// 【异步设置】
///
/// 摘要:
/// 把构建的1个指定的缓存项(键/值对),及其缓存时间模式和缓存时间,加载到内存缓存中。
///
///
public Task SetAsync(CacheKey key, object data)
{
Set(key, data);
return Task.CompletedTask;
}
/// name="prefix">1个指定的前缀字符串。
/// name="prefixParameters">数组实例,该实例中存储着n个泛型实例,这些实例为前缀字符串的拼接提供数据支撑。
///
/// 【异步通过前缀字符串移除】
///
/// 摘要:
/// 根据1个新的前缀字符串,从字典实例中移除指定的键/值对实例。。
///
///
public Task RemoveByPrefixAsync(string prefix, params object[] prefixParameters)
{
RemoveByPrefix(prefix, prefixParameters);
return Task.CompletedTask;
}
/// name="prefix">1个指定的前缀字符串。
/// name="prefixParameters">数组实例,该实例中存储着n个泛型实例,这些实例为前缀字符串的拼接提供数据支撑。
///
/// 【通过前缀字符串移除】
///
/// 摘要:
/// 根据1个新的前缀字符串,从字典实例中移除指定的键/值对实例。。
///
///
public void RemoveByPrefix(string prefix, params object[] prefixParameters)
{
//拼接出1个新的前缀字符串。
prefix = PrepareKeyPrefix(prefix, prefixParameters);
//根据1个新的前缀字符串,从字典实例中移除指定的键/值对实例。
_prefixes.TryRemove(prefix, out var tokenSource);
//显式销毁释放标记资源(CancellationTokenSource)实例。
tokenSource?.Cancel();
tokenSource?.Dispose();
}
///
/// 【异步清理】
///
/// 摘要:
/// 销毁释放字典实例中存储着的所有键/值对实例。
///
///
public Task ClearAsync()
{
//销毁旧的取消标记资源(CancellationTokenSource)实例。
_clearToken.Cancel();
_clearToken.Dispose();
//实例化一个新的取消标记资源(CancellationTokenSource)实例。
_clearToken = new CancellationTokenSource();
//销毁释放字典实例中存储着的所有键/值对实例,并抛出该字典实例中取消标记资源(CancellationTokenSource)实例(字典实例中的值)。
foreach (var prefix in _prefixes.Keys.ToList())
{
_prefixes.TryRemove(prefix, out var tokenSource);
//显式销毁释放标记资源(CancellationTokenSource)实例。
tokenSource?.Dispose();
}
return Task.CompletedTask;
}
#endregion
}
}
2.7 Core.Caching.DistributedCacheManager
using System.Collections.Concurrent;
using System.Text.RegularExpressions;
using Microsoft.Extensions.Caching.Distributed;
using Newtonsoft.Json;
using Nito.AsyncEx;
using Core.Configuration;
using static Core.Caching.CacheKey;
namespace Core.Caching
{
///
/// 【分布式缓存管理器--类】
///
/// 摘要:
/// 通过该类中的方法成员实现了通过Redis软件对缓存数据的管理操作,这些管理操作包含:缓存数据加载,缓存数据移除和缓存数据清理等。
/// 说明:
/// 1、当前程序中所定义的通过时间进行触发的计划任务,如果当前程序启用分布式数据库,
/// 将利用“Microsoft.Extensions.Caching.StackExchangeRedis”中间件实例的缓存时间的控制模式及其缓存时间和当前类中的“PerformActionWithLockAsync”重写方法,定时触发这些计划任务。
/// 即如果当前程序没有启用分布式数据库,那么在默认状况下会利用“Microsoft.Extensions.Caching.Memory”程序集的缓存时间的控制模式及其缓存时间,定时触发这些计划任务。
/// 2、该类是抽象类不被实例化,更不能依赖注入到内置容器中。
///
///
public abstract class DistributedCacheManager: CacheKeyService, ILocker, IStaticCacheManager
{
#region 变量--私有/保护
///
/// 【分布式缓存】
///
/// 摘要:
/// 分布式缓存接口实例,该实例实际上是:“Microsoft.Extensions.Caching.StackExchangeRedis”中间件的实例,通过该实例实现当前程序通过“Microsoft.Extensions.Caching.StackExchangeRedis”中间件的实例与Redis分布式缓存数据库的交互操作。
///
///
protected readonly IDistributedCache _distributedCache;
///
/// 【项集】
///
/// 摘要:
/// 线程安全字典实例,该实例以键/值对的形式存储着当前程序中的所有缓存键实例及其所对应的值。
/// 线程安全字典:
/// 1、如果不使用线程安全字典实例,则在多线程下每次实例的加载都要通过锁实例进行控制。
/// 2、在默认情况下,如果使用线程安全字典实例,微软默认隐式设定锁实例数量是CPU核的个数。
/// 3、当然开发者也可以自定义更多的线程(>CPU核的个数),但同时开发者也必须显式的为这些线程定义同样多的锁实例数量(>CPU核的个数)。
/// 说明:
/// 字典实例中所存储键/值对中的值是1个指定实体的1/n实例;而分布式缓存数据库中所存储键/值对中的值是经过JSON格式编码后的1个指定实体的1/n实例。
///
///
protected readonly ConcurrentDictionary<CacheKey, object> _items;
///
/// 【锁】
///
/// 摘要:
/// 异步锁实例,该实例实际上是:“Nito.AsyncEx.Coordination”中间件的实例,在异步多线程操作中,通过该实例来保证,在同1时间内,有且只有1个线程能够对缓存项(键/值对)进行强制移除操作(在缓存项(键/值对)实例长时间不被使用时,且在缓存时间内,则需要强制销毁,以释放内存空间)。
/// 说明:
/// 1、如果不使用异步锁实例,在异步多线程操作中就会现出对1个已经销毁的实例重复性的进行强制销毁操作,从而产生不可预测的异常。
/// 2、缓存项(键/值对)实例的构建定义中对重复性构建同1缓存项(键/值对)实例进行了排除操作,所以缓存项(键/值对)实例的构建就不需要使用异步锁实例了。
///
///
protected static readonly AsyncLock _locker;
/// name="key">缓存键类的1个指定实例。
///
/// 【缓存键委托变更事件】
///
/// 摘要:
/// 1个委托事件方法,该方法通过缓存键实例,对事件中的操作进行实例化。
///
///
protected delegate void OnKeyChanged(CacheKey key);
///
/// 【键加载委托事件】
///
/// 摘要:
/// 1个委托事件方法,该方法通过缓存键实例,对缓存键委托加载事件中的操作进行实例化。
///
///
protected OnKeyChanged _onKeyAdded;
///
/// 【键移除委托事件】
///
/// 摘要:
/// 1个委托事件方法,该方法通过缓存键实例,对缓存键委托移除事件中的操作进行实例化。
///
///
protected OnKeyChanged _onKeyRemoved;
#endregion
#region 构造方法
///
/// 【默认构造方法】
///
/// 摘要:
/// 通过默认构造方法,实例化异步锁实例,该实例实际上是:“Nito.AsyncEx.Coordination”中间件的实例,在异步多线程操作中,通过该实例来保证,在同1时间内,有且只有1个线程能够对缓存项(键/值对)进行强制移除操作(在缓存项(键/值对)实例长时间不被使用时,且在缓存时间内,则需要强制销毁,以释放内存空间)。
///
///
static DistributedCacheManager()
{
_locker = new AsyncLock();
}
/// name="appSettings">应用配置类的1个指定实例。
/// name="distributedCache">分布式缓存接口实例,该实例实际上是:“Microsoft.Extensions.Caching.StackExchangeRedis”中间件的实例,通过该实例实现当前程序通过“Microsoft.Extensions.Caching.StackExchangeRedis”中间件的实例与Redis分布式缓存数据库的交互操作。
///
/// 【拷贝构造方法】
///
/// 摘要:
/// 通过拷贝构造方法,对当前类中的同名变量成员进行实例化。
///
///
protected DistributedCacheManager(AppSettings appSettings, IDistributedCache distributedCache) :base(appSettings)
{
_distributedCache = distributedCache;
_items = new ConcurrentDictionary<CacheKey, object>(new CacheKeyEqualityComparer());
}
#endregion
#region 方法----私有/保护
///
/// 【清理实例】
///
/// 摘要:
/// 销毁字典实例中存储着的所有数据实例。
///
///
protected void ClearInstanceData()
{
_items.Clear();
}
/// name="prefix">1个指定的前缀字符串。
/// name="prefixParameters">数组实例,该实例中存储着n个泛型实例,这些实例为前缀字符串的拼接提供数据支撑。
///
/// 【异步通过前缀字符串移除实例】
///
/// 摘要:
/// 根据字典实例中与前缀字符串验证规则相匹配的缓存键类的所有实例,从字典实例中依次移除相匹配的键/值对。
///
///
protected async Task RemoveByPrefixInstanceDataAsync(string prefix, params object[] prefixParameters)
{
//在异步多线程操作中,先启用异步锁操作,然后再执行当前方法中的移除操作。
using var _ = await _locker.LockAsync();
//拼接出1个新的前缀字符串。
prefix = PrepareKeyPrefix(prefix, prefixParameters);
//构建前缀字符串的验证规则实例。
//RegexOptions.Singleline:指定单行模式,该模式下只能对1行字符串进行匹配验证操作(对\n之外的每个字符将不进行匹配验证操作)。
//RegexOptions.Compiled:指在匹配验证操作时,对每1个字符都需要进行匹配验证,注意:最好不要使用RegexOptions.Compiled进行字符串的匹配验证操作,因为它被称为性能杀手。
//RegexOptions.IgnoreCase:在匹配验证操作时忽略字符串中每个字符的大小写。
var regex = new Regex(prefix,
RegexOptions.Singleline | RegexOptions.Compiled | RegexOptions.IgnoreCase);
//实例化1个列表实例,该实例存储着缓存键类的n个实例。
var matchesKeys = new List<CacheKey>();
//把字典实例中与前缀字符串验证规则相匹配的缓存键类的所有实例,存储到列表实例中。
matchesKeys.AddRange(_items.Keys.Where(key => regex.IsMatch(key.Key)).ToList());
//根据列表实例,从字典实例中依次移除相匹配的键/值对。
if (matchesKeys.Any())
foreach (var key in matchesKeys)
_items.TryRemove(key, out var _);
}
/// name="prefix">1个指定的前缀字符串。
/// name="prefixParameters">数组实例,该实例中存储着n个泛型实例,这些实例为前缀字符串的拼接提供数据支撑。
///
/// 【通过前缀字符串移除实例】
///
/// 摘要:
/// 根据字典实例中与前缀字符串验证规则相匹配的缓存键类的所有实例,从字典实例中依次移除相匹配的键/值对。
///
///
protected void RemoveByPrefixInstanceData(string prefix, params object[] prefixParameters)
{
//在异步多线程操作中,先启用异步锁操作,然后再执行当前方法中的移除操作。
using var _ = _locker.Lock();
//拼接出1个新的前缀字符串。
prefix = PrepareKeyPrefix(prefix, prefixParameters);
//构建前缀字符串的验证规则实例。
//RegexOptions.Singleline:指定单行模式,该模式下只能对1行字符串进行匹配验证操作(对\n之外的每个字符将不进行匹配验证操作)。
//RegexOptions.Compiled:指在匹配验证操作时,对每1个字符都需要进行匹配验证,注意:最好不要使用RegexOptions.Compiled进行字符串的匹配验证操作,因为它被称为性能杀手。
//RegexOptions.IgnoreCase:在匹配验证操作时忽略字符串中每个字符的大小写。
var regex = new Regex(prefix,
RegexOptions.Singleline | RegexOptions.Compiled | RegexOptions.IgnoreCase);
//实例化1个列表实例,该实例存储着缓存键类的n个实例。
var matchesKeys = new List<CacheKey>();
//把字典实例中与前缀字符串验证规则相匹配的缓存键类的所有实例,存储到列表实例中。
matchesKeys.AddRange(_items.Keys.Where(key => regex.IsMatch(key.Key)).ToList());
//根据列表实例,从字典实例中依次移除相匹配的键/值对。
if (matchesKeys.Any())
foreach (var key in matchesKeys)
_items.TryRemove(key, out var _);
}
/// name="key">缓存键类的1个指定实例。
///
/// 【入口操作预处理】
///
/// 摘要:
/// 获取分布式缓存入口操作实例,该实例为键字符串的缓存时间控制模式及其控制时间提供数据支撑。
/// 说明:
/// 1个指定缓存项(键/值对)在内存中的缓存时间,实际是通过键字符串的缓存时间进行控制的,即只要过了指定键字符串的缓存时间,其所对应的值也同时被销毁了,
/// 那么其所构建的缓存项(键/值对)在内存中也就不存在了。
///
///
/// 返回:
/// 分布式缓存入口操作实例。
///
///
private DistributedCacheEntryOptions PrepareEntryOptions(CacheKey key)
{
//AbsoluteExpirationRelativeToNow:以绝对过期时间模式,对指定键字符串的缓存时间进行控制;在缓存时间不管该指定缓存项(键/值对)经过多少次调用,到缓存时间该指定缓存项(键/值对)就要被销毁。
//SlidingExpiration:相对对过期时间模式,对指定键字符串的缓存时间进行控制;在缓存时间在最后1次调用的时间后顺延1个单位的缓存时间,只有完事度过整个缓存时间该指定缓存项(键/值对)才能被销毁。
//如果AbsoluteExpirationRelativeToNow和SlidingExpiration都未设定,则Redis中指定键字符串的absexp和sldexp所对应的值都=-1。
var options = new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(key.CacheTime)
};
return options;
}
///
/// name="key">缓存键类的1个指定实例。
///
/// 【异步尝试获取缓存项】
///
/// 摘要:
/// 如果(JSON编码格式的)缓存值不为空,则获取true和1个指定实体的1/n个实例;如果(JSON编码格式的)缓存值为空,则获取false和1个指定实体的空实例。
///
///
/// 返回:
/// true和1个指定实体的1/n个实例。
///
///
private async Task<(bool isSet, T item)> TryGetItemAsync<T>(CacheKey key)
{
var json = await _distributedCache.GetStringAsync(key.Key);
//如果(JSON编码格式的)缓存值为空,则获取false和1个指定实体的空实例。
if (string.IsNullOrEmpty(json))
return (false, default);
//通过缓存键实例,对缓存键委托加载事件中的操作进行实例化。
_onKeyAdded?.Invoke(key);
//如果(JSON编码格式的)缓存值不为空,则获取true和1个指定实体的1/n个实例。
return (true, JsonConvert.DeserializeObject<T>(json));
}
///
/// name="key">缓存键类的1个指定实例。
///
/// 【尝试获取缓存项】
///
/// 摘要:
/// 如果(JSON编码格式的)缓存值不为空,则获取true和1个指定实体的1/n个实例;如果(JSON编码格式的)缓存值为空,则获取false和1个指定实体的空实例。
///
///
/// 返回:
/// true和1个指定实体的1/n个实例。
///
///
private (bool isSet, T item) TryGetItem<T>(CacheKey key)
{
var json = _distributedCache.GetString(key.Key);
//如果(JSON编码格式的)缓存值为空,则获取false和1个指定实体的空实例。
if (string.IsNullOrEmpty(json))
return (false, default);
//通过缓存键实例,对缓存键委托加载事件中的操作进行实例化。
_onKeyAdded?.Invoke(key);
//如果(JSON编码格式的)缓存值不为空,则获取true和1个指定实体的1/n个实例。
return (true, JsonConvert.DeserializeObject<T>(json));
}
/// name="key">缓存键类的1个指定实例。
/// name="data">1个指定的泛型实例,该实例中存储着该1个指定实体的1/n实例。
///
/// 【设置】
///
/// 摘要:
/// 把构建的1个指定的缓存项(键/(“JSON”编码格式的)值对),及其缓存时间模式和缓存时间,加载到分布式缓存中。
///
///
private void Set(CacheKey key, object data)
{
//如果缓存键实例的缓存缓存时间小于等于0或1个指定的泛型实例的实例值为:null,则不再执行任何缓存操作,直接退出该方法。
if ((key?.CacheTime ?? 0) <= 0 || data == null)
return;
//把构建的1个指定的缓存项(键/(“JSON”编码格式的)值对),及其缓存时间模式和缓存时间,加载到分布式缓存中。
_distributedCache.SetString(key.Key, JsonConvert.SerializeObject(data), PrepareEntryOptions(key));
//把缓存键类的1个指定实例和1个指定实体的1/n实例,以键/值对的形式加载到字典实例中。
_items.TryAdd(key, data);
//通过缓存键实例,对缓存键委托加载事件中的操作进行实例化。
_onKeyAdded?.Invoke(key);
}
#endregion
#region 方法--销毁
///
/// 【销毁】
///
/// 摘要:
/// 如果操作中有非托管资源实例产生,则通过显式调用该方法以保证销毁这些有非托管资源实例。
///
///
public void Dispose()
{
}
#endregion
#region 方法--接口实现--ILocker
/// name="resource">1个指定缓存键字符串(这里特指1个指定计划任务的命名字符串)。
/// name="expirationTime">1个指定缓存项(键/值对,这里特指计划任务实例的键/值对缓存项)在Redis分布式缓存数据库中的缓存时间。
/// name="action">1个无返回值的异步委托方法实例,该异步委托方法实例定时触发指定的计划任务实例。
///
/// 【异步执行加锁操作?】
///
/// 摘要:
/// 获取1个值false(失败)/true(成功),该值指示已经通过Redis分布式数据库缓存时间的控制模式和缓存时间,定时触发指定的计划任务实例是否已经成功触发并执行完成。
/// 说明:
/// 如果想要通过Redis分布式数据库缓存时间的控制模式和缓存时间,定时触发指定的计划任务实例,当前程序必须先启用对分布式数据库的支持;如果不启用则使用内存缓存缓存时间的控制模式和缓存时间,定时触发指定的计划任务实例。
///
///
/// 返回:
/// 1个值false(失败)/true(成功)
///
///
public async Task<bool> PerformActionWithLockAsync(string resource, TimeSpan expirationTime, Func<Task> action)
{
if (!string.IsNullOrEmpty(await _distributedCache.GetStringAsync(resource)))
return false;
try
{
//AbsoluteExpirationRelativeToNow:以绝对过期时间模式,对指定键字符串的缓存时间进行控制;在缓存时间不管该指定缓存项(键/值对)经过多少次调用,到缓存时间该指定缓存项(键/值对)就要被销毁。
//SlidingExpiration:相对对过期时间模式,对指定键字符串的缓存时间进行控制;在缓存时间在最后1次调用的时间后顺延1个单位的缓存时间,只有完事度过整个缓存时间该指定缓存项(键/值对)才能被销毁。
//如果AbsoluteExpirationRelativeToNow和SlidingExpiration都未设定,则Redis中指定键字符串的absexp和sldexp所对应的值都=-1。
await _distributedCache.SetStringAsync(resource, resource, new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = expirationTime
});
//异步委托方法实例,通过Redis分布式数据库缓存时间的控制模式和缓存时间, 定时触发指定的计划任务实例。
await action();
return true;
}
finally
{
//如果定时触发指定的计划任务实例失败,则从Redis分布式缓存数据库中移除指定的计划任务实例的键/值对缓存项。
await _distributedCache.RemoveAsync(resource);
}
}
#endregion
#region 方法--接口实现--IStaticCacheManager
///
/// name="key">缓存键类的1个指定实例。
/// name="acquire">1个具有返回值的泛型异步委托方法实例,该泛型异步委托方法实例用于获取1个指定实体的1/n个实例。
///
/// 【异步获取】
///
/// 摘要:
/// 获取一个指定的实体的1个指定实体的1/n个实例。
///
///
/// 返回:
/// 1个指定实体的1/n个实例。
///
///
public async Task<T> GetAsync<T>(CacheKey key, Func<Task<T>> acquire)
{
//对重复性构建同1缓存项(键/(“JSON”编码格式的)值对)实例进行排除操作,如果字典实例中存在指定的键/值对,则直接退出当前方法,并获取1个指定实体的1/n实例。
if (_items.ContainsKey(key))
return (T)_items.GetOrAdd(key, acquire);
//如果缓存键实例的缓存时间小于等于:0,就不要对1个指定实体的1/n实例进行缓存管理,则直接退出当前方法,并获取1个指定实体的1/n实例。
if (key.CacheTime <= 0)
return await acquire();
//查看分布式数据库中是否已经缓存了该缓存项(键/(“JSON”编码格式的)值对)。
var (isSet, item) = await TryGetItemAsync<T>(key);
//如果已经缓存则把取1个指定实体的1/n实例,以键/值对的形式存储到字典实例中后,获取1个指定实体的1/n实例。
if (isSet)
{
if (item != null)
_items.TryAdd(key, item);
return item;
}
//如果未缓存,则调用1个指定实体的1/n实例的委托方法。
var result = await acquire();
//如果存在1个指定实体的1/n实例,则把该实例以“JSON”编号格式存储到分布式数据库中进行管理。
if (result != null)
await SetAsync(key, result);
//返回1个指定实体的1/n实例。
return result;
}
///
/// name="key">缓存键类的1个指定实例。
/// name="acquire">1个具有返回值的泛型委托方法实例,该泛型委托方法实例用于获取1个指定实体的1/n个实例。
///
/// 【异步获取】
///
/// 摘要:
/// 获取一个指定的实体的1个指定实体的1/n个实例。
///
///
/// 返回:
/// 1个指定实体的1/n个实例。
///
///
public async Task<T> GetAsync<T>(CacheKey key, Func<T> acquire)
{
//对重复性构建同1缓存项(键/(“JSON”编码格式的)值对)实例进行排除操作,如果字典实例中存在指定的键/值对,则直接退出当前方法,并获取1个指定实体的1/n实例。
if (_items.ContainsKey(key))
return (T)_items.GetOrAdd(key, acquire);
//如果缓存键实例的缓存时间小于等于:0,就不要对1个指定实体的1/n实例进行缓存管理,则直接退出当前方法,并获取1个指定实体的1/n实例。
if (key.CacheTime <= 0)
return acquire();
//查看分布式数据库中是否已经缓存了该缓存项(键/(“JSON”编码格式的)值对)。
var (isSet, item) = await TryGetItemAsync<T>(key);
//如果已经缓存则把取1个指定实体的1/n实例,以键/值对的形式存储到字典实例中后,获取1个指定实体的1/n实例。
if (isSet)
{
if (item != null)
_items.TryAdd(key, item);
return item;
}
//如果未缓存,则调用1个指定实体的1/n实例的委托方法。
var result = acquire();
//如果存在1个指定实体的1/n实例,则把该实例以“JSON”编号格式存储到分布式数据库中进行管理。
if (result != null)
await SetAsync(key, result);
//返回1个指定实体的1/n实例。
return result;
}
///
/// name="key">缓存键类的1个指定实例。
/// name="acquire">1个具有返回值的泛型委托方法实例,该泛型委托方法实例用于获取1个指定实体的1/n个实例。
///
/// 【获取】
///
/// 摘要:
/// 获取一个指定的实体的1个指定实体的1/n个实例。
///
///
/// 返回:
/// 1个指定实体的1/n个实例。
///
///
public T Get<T>(CacheKey key, Func<T> acquire)
{
//对重复性构建同1缓存项(键/(“JSON”编码格式的)值对)实例进行排除操作,如果字典实例中存在指定的键/值对,则直接退出当前方法,并获取1个指定实体的1/n实例。
if (_items.ContainsKey(key))
return (T)_items.GetOrAdd(key, acquire);
//如果缓存键实例的缓存时间小于等于:0,就不要对1个指定实体的1/n实例进行缓存管理,则直接退出当前方法,并获取1个指定实体的1/n实例。
if (key.CacheTime <= 0)
return acquire();
//查看分布式数据库中是否已经缓存了该缓存项(键/(“JSON”编码格式的)值对)。
var (isSet, item) = TryGetItem<T>(key);
//如果已经缓存则把取1个指定实体的1/n实例,以键/值对的形式存储到字典实例中后,获取1个指定实体的1/n实例。
if (isSet)
{
if (item != null)
_items.TryAdd(key, item);
return item;
}
//如果未缓存,则调用1个指定实体的1/n实例的委托方法。
var result = acquire();
//如果存在1个指定实体的1/n实例,则把该实例以“JSON”编号格式存储到分布式数据库中进行管理。
if (result != null)
Set(key, result);
//返回1个指定实体的1/n实例。
return result;
}
/// name="cacheKey">缓存键类的1个指定实例。
/// name="cacheKeyParameters">数组实例,该实例中存储着n个泛型实例,这些实例为缓存键字符串的拼接提供数据支撑。
///
/// 【异步移除】
///
/// 摘要:
/// 根据缓存键类的1个新的指定实例,从分布式数据库中移除1个指定的缓存项(键/(“JSON”编码格式的)值对)和字典实例中移除1个指定的键/值对。
///
///
public async Task RemoveAsync(CacheKey cacheKey, params object[] cacheKeyParameters)
{
//拼接出缓存键类的1个新的指定实例。
cacheKey = PrepareKey(cacheKey, cacheKeyParameters);
//根据缓存键类的1个新的指定实例,从分布式数据库中移除1个指定的缓存项(键/(“JSON”编码格式的)值对)。
await _distributedCache.RemoveAsync(cacheKey.Key);
//根据缓存键类的1个新的指定实例,从字典实例中移除1个指定的键/值对。
_items.TryRemove(cacheKey, out _);
//通过缓存键实例,对缓存键委托移除事件中的操作进行实例化。
_onKeyRemoved?.Invoke(cacheKey);
}
/// name="key">缓存键类的1个指定实例。
/// name="data">1个指定的泛型实例,该实例中存储着该1个指定实体的1/n实例。
///
/// 【异步设置】
///
/// 摘要:
/// 把构建的1个指定的缓存项(键/(“JSON”编码格式的)值对),及其缓存时间模式和缓存时间,加载到分布式缓存中。
///
///
public async Task SetAsync(CacheKey key, object data)
{
//如果缓存键实例的缓存缓存时间小于等于0或1个指定的泛型实例的实例值为:null,则不再执行任何缓存操作,直接退出该方法。
if ((key?.CacheTime ?? 0) <= 0 || data == null)
return;
//把构建的1个指定的缓存项(键/(“JSON”编码格式的)值对),及其缓存时间模式和缓存时间,加载到分布式缓存中。
await _distributedCache.SetStringAsync(key.Key, JsonConvert.SerializeObject(data), PrepareEntryOptions(key));
//把缓存键类的1个指定实例和1个指定实体的1/n实例,以键/值对的形式加载到字典实例中。
_items.TryAdd(key, data);
//通过缓存键实例,对缓存键委托加载事件中的操作进行实例化。
_onKeyAdded?.Invoke(key);
}
/// name="prefix">1个指定的前缀字符串。
/// name="prefixParameters">数组实例,该实例中存储着n个泛型实例,这些实例为前缀字符串的拼接提供数据支撑。
///
/// 【异步通过前缀字符串移除实例】
///
/// 摘要:
/// 该方法是抽象方法,该方法的覆写方法用于根据1个新的前缀字符串,从缓存数据库中移除1/n个指定的缓存项(键/(“JSON”编码格式的)值对)。
///
///
public abstract Task RemoveByPrefixAsync(string prefix, params object[] prefixParameters);
/// name="prefix">1个指定的前缀字符串。
/// name="prefixParameters">数组实例,该实例中存储着n个泛型实例,这些实例为前缀字符串的拼接提供数据支撑。
///
/// 【通过前缀字符串移除实例】
///
/// 摘要:
/// 该方法是抽象方法,该方法的覆写方法用于根据1个新的前缀字符串,从缓存数据库中移除1/n个指定的缓存项(键/(“JSON”编码格式的)值对)。
///
///
public abstract void RemoveByPrefix(string prefix, params object[] prefixParameters);
///
/// 【异步清理】
///
/// 摘要:
/// 该方法是抽象方法,该方法的覆写方法用于从缓存数据库中移除1/n个指定的缓存项(键/(“JSON”编码格式的)值对),并销毁字典实例中存储着的所有键/值对实例。
///
///
public abstract Task ClearAsync();
#endregion
}
}
对以上功能更为具体实现和注释见230508_005ShopRazor(分布式缓存中间件的配置及其调用定义)。