0. 前言
之前写了几篇文章介绍了一些AOP的常识,
然而还没有亮进去AOP的姿态,
兴许姿态丑陋一点,
大家会对AOP有点趣味
内容大抵会分为如下几篇:(毕竟人懒,一下子写完太累了,没有能源)
AOP的姿态之 简化 MemoryCache 应用形式
AOP的姿态之 简化混用 MemoryCache 和 DistributedCache 应用形式
AOP的姿态之 如何把 HttpClient 变为申明式
至于AOP框架在这儿示例仍然会应用我本人基于emit实现的动静代理AOP框架: https://github.com/fs7744/Nor...
毕竟是本人写的,魔改/加性能都很不便,
万一万一大家如果有疑难,(尽管大略不会有),我也好答复, (当然如果大家认可,在github给个star,就切实是太让人开心了)
1. 十分重要的注意事项
本篇次要目标是介绍如何利用AOP简化应用Cache的代码的形式
然而在实在业务场景如果要混用 MemoryCache 和 DistributedCache,
最好贴合场景好好思考一下,为何要这样用?
每多加一个cache就是减少一层复杂度,
如果一层cache不能解决问题?
那么两层就能吗?三层就能吗?特地是缓存穿透等等怎么办呢?
一层不能解决问题的起因是什么呢?
心愿大家三思而行,哈哈
2. 如何混用呢?
2.1 对立模型,对立接口
MemoryCache 和 DistributedCache 的接口定义尽管类似度和思维很靠近,
然而呢,还是存在不一样,
大家能够看上面的接口定义
public interface IMemoryCache : IDisposable { // // 摘要: // Create or overwrite an entry in the cache. // // 参数: // key: // An object identifying the entry. // // 返回后果: // The newly created Microsoft.Extensions.Caching.Memory.ICacheEntry instance. ICacheEntry CreateEntry(object key); // // 摘要: // Removes the object associated with the given key. // // 参数: // key: // An object identifying the entry. void Remove(object key); // // 摘要: // Gets the item associated with this key if present. // // 参数: // key: // An object identifying the requested entry. // // value: // The located value or null. // // 返回后果: // true if the key was found. bool TryGetValue(object key, out object value); }
public interface IDistributedCache { // // 摘要: // Gets a value with the given key. // // 参数: // key: // A string identifying the requested value. // // 返回后果: // The located value or null. byte[] Get(string key); // // 摘要: // Gets a value with the given key. // // 参数: // key: // A string identifying the requested value. // // token: // Optional. The System.Threading.CancellationToken used to propagate notifications // that the operation should be canceled. // // 返回后果: // The System.Threading.Tasks.Task that represents the asynchronous operation, containing // the located value or null. Task<byte[]> GetAsync(string key, CancellationToken token = default); // // 摘要: // Refreshes a value in the cache based on its key, resetting its sliding expiration // timeout (if any). // // 参数: // key: // A string identifying the requested value. void Refresh(string key); // // 摘要: // Refreshes a value in the cache based on its key, resetting its sliding expiration // timeout (if any). // // 参数: // key: // A string identifying the requested value. // // token: // Optional. The System.Threading.CancellationToken used to propagate notifications // that the operation should be canceled. // // 返回后果: // The System.Threading.Tasks.Task that represents the asynchronous operation. Task RefreshAsync(string key, CancellationToken token = default); // // 摘要: // Removes the value with the given key. // // 参数: // key: // A string identifying the requested value. void Remove(string key); // // 摘要: // Removes the value with the given key. // // 参数: // key: // A string identifying the requested value. // // token: // Optional. The System.Threading.CancellationToken used to propagate notifications // that the operation should be canceled. // // 返回后果: // The System.Threading.Tasks.Task that represents the asynchronous operation. Task RemoveAsync(string key, CancellationToken token = default); // // 摘要: // Sets a value with the given key. // // 参数: // key: // A string identifying the requested value. // // value: // The value to set in the cache. // // options: // The cache options for the value. void Set(string key, byte[] value, DistributedCacheEntryOptions options); // // 摘要: // Sets the value with the given key. // // 参数: // key: // A string identifying the requested value. // // value: // The value to set in the cache. // // options: // The cache options for the value. // // token: // Optional. The System.Threading.CancellationToken used to propagate notifications // that the operation should be canceled. // // 返回后果: // The System.Threading.Tasks.Task that represents the asynchronous operation. Task SetAsync(string key, byte[] value, DistributedCacheEntryOptions options, CancellationToken token = default); }
那么咱们为了让多个不同实现的缓存接口能被同一段缓存操作代码所应用,
就须要定义对立的接口并适配各种不同的缓存接口
(当然,咱们不这样做也能凑出代码达到雷同成果,然而呢,他人看到这样的实现,难免会吐槽咱们的代码,如果不小心听见,体面有点挂不住呀)
这里呢,咱们就这样简略定义一个这样的接口
public interface ICacheAdapter { // Cache 实现的名字,以此能指定应用哪种缓存实现 string Name { get; } // 尝试获取缓存 bool TryGetValue<T>(string key, out T result); // 存入缓存数据,这里为了简略,咱们就只反对ttl过期策略 void Set<T>(string key, T result, TimeSpan ttl); }
2.2 适配MemoryCache
[NonAspect] public class MemoryCacheAdapter : ICacheAdapter { private readonly IMemoryCache cache; public MemoryCacheAdapter(IMemoryCache cache) { this.cache = cache; } // 取个固定名字 public string Name => "memory"; public void Set<T>(string key, T result, TimeSpan ttl) { cache.Set(key, result, ttl); } public bool TryGetValue<T>(string key, out T result) { return cache.TryGetValue(key, out result); } }
2.3 适配DistributedCache
[NonAspect] public class DistributedCacheAdapter : ICacheAdapter { private readonly IDistributedCache cache; private readonly string name; public DistributedCacheAdapter(IDistributedCache cache, string name) { this.cache = cache; this.name = name; } /// 这里咱们就不固定名字了,大家想用 redis 就能够本人名字取redis public string Name => name; public void Set<T>(string key, T result, TimeSpan ttl) { cache.Set(key, JsonSerializer.SerializeToUtf8Bytes(result), // 为了简略,咱们就不在扩大更多不同序列化器了,这里就用System.Text.Json new DistributedCacheEntryOptions() { AbsoluteExpirationRelativeToNow = ttl }); // 同样,为了简略,也只反对ttl缓存策略 } public bool TryGetValue<T>(string key, out T result) { var data = cache.Get(key); if (data == null) { result = default; return false; } else { result = JsonSerializer.Deserialize<T>(data); return true; } } }
2.4 定义CacheAttribute
这里咱们仍然应用 attribute 这种对大家应用最简略的形式
然而呢,因为有多个缓存实现应用,
咱们间接应用 InterceptorAttribute
很难管制不同缓存实现的应用,
所以咱们这里拆分 缓存应用的定义 与 真正缓存的调用逻辑CacheAttribute
只是缓存应用的定义
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] public class CacheAttribute : Attribute { // 因为多个缓存实现,咱们须要有应用程序指定 public int Order { get; set; } public string CacheKey { get; set; } public string Ttl { get; set; } public string CacheName { get; set; } }
2.5 实现CacheInterceptor
public class CacheInterceptor : AbstractInterceptor { public override bool CanAspect(MethodReflector method) { return method.IsDefined<CacheAttribute>(); // 限度只对有缓存定义的办法起效 } public override async Task InvokeAsync(AspectContext context, AsyncAspectDelegate next) { var caches = context.ServiceProvider.GetRequiredService<IEnumerable<ICacheAdapter>>() .ToDictionary(i => i.Name, StringComparer.OrdinalIgnoreCase); var cas = context.Method.GetReflector() .GetCustomAttributes<CacheAttribute>() .OrderBy(i => i.Order) .ToArray(); // 为了简略,咱们就应用最简略的反射模式调用 var m = typeof(CacheInterceptor).GetMethod(nameof(CacheInterceptor.GetOrCreateAsync)) .MakeGenericMethod(context.Method.ReturnType.GetGenericArguments()[0]); await (Task)m.Invoke(this, new object[] { caches, cas, context, next, 0 }); } public async Task<T> GetOrCreateAsync<T>(Dictionary<string, ICacheAdapter> adapters, CacheAttribute[] options, AspectContext context, AsyncAspectDelegate next, int index) { if (index >= options.Length) { Console.WriteLine($"No found Cache at {DateTime.Now}."); // 所有cache 都找完了,没有找到无效cache,所以须要拿真正的后果 await next(context); // 为了简略,咱们就只反对 Task<T> 的后果 return ((Task<T>)context.ReturnValue).Result; } var op = options[index]; T result; var cacheName = op.CacheName; if (adapters.TryGetValue(cacheName, out var adapter)) { if (!adapter.TryGetValue<T>(op.CacheKey, out result)) { // 以后缓存找不到后果,移到下一个缓存获取后果 result = await GetOrCreateAsync<T>(adapters, options, context, next, ++index); adapter.Set(op.CacheKey, result, TimeSpan.Parse(op.Ttl)); // 更新以后缓存实现的存储 context.ReturnValue = Task.FromResult(result); // 为了简略,咱们就在这儿更新返回后果,其实不该在这儿的,为什么,大家能够猜一猜为什么? } else { Console.WriteLine($"Get Cache From {cacheName} at {DateTime.Now}."); context.ReturnValue = Task.FromResult(result); // 为了简略,咱们就在这儿更新返回后果,其实不该在这儿的,为什么,大家能够猜一猜为什么? } } else { throw new ArgumentException($"No such cache: {cacheName}."); } return result; } }
2.6 测试
public class DoCacheTest { [Cache(CacheKey = nameof(Do), CacheName = "memory", Order = 0, Ttl = "00:00:01")] // 1秒过期 [Cache(CacheKey = nameof(Do), CacheName = "distribute", Order = 1, Ttl = "00:00:05")] // 5秒过期 public virtual Task<string> Do() => Task.FromResult(DateTime.Now.ToString()); } class Program { static async Task Main(string[] args) { var sut = new ServiceCollection() .AddTransient<DoCacheTest>() .ConfigureAop(i => i.GlobalInterceptors.Add(new CacheInterceptor())) // 设置Cache拦截器 .AddMemoryCache() .AddDistributedMemoryCache() // 为了测试,咱们就不应用redis之类的货色了,用个内存实现模仿就好 .AddSingleton<ICacheAdapter, MemoryCacheAdapter>() // 增加缓存适配器 .AddSingleton<ICacheAdapter>(i => new DistributedCacheAdapter(i.GetRequiredService<IDistributedCache>(), "distribute")) .BuildServiceProvider() .GetRequiredService<DoCacheTest>(); for (int i = 0; i < 20; i++) { Console.WriteLine($"Get: {await sut.Do()}"); await Task.Delay(500); // 每隔半秒,察看缓存变动 } } }
后果:
No found Cache at 2021/1/3 11:56:10.Get: 2021/1/3 11:56:10Get Cache From memory at 2021/1/3 11:56:10.Get: 2021/1/3 11:56:10Get Cache From distribute at 2021/1/3 11:56:11.Get: 2021/1/3 11:56:10Get Cache From memory at 2021/1/3 11:56:11.Get: 2021/1/3 11:56:10Get Cache From distribute at 2021/1/3 11:56:12.Get: 2021/1/3 11:56:10Get Cache From memory at 2021/1/3 11:56:12.Get: 2021/1/3 11:56:10Get Cache From distribute at 2021/1/3 11:56:13.Get: 2021/1/3 11:56:10Get Cache From memory at 2021/1/3 11:56:13.Get: 2021/1/3 11:56:10Get Cache From distribute at 2021/1/3 11:56:14.Get: 2021/1/3 11:56:10Get Cache From memory at 2021/1/3 11:56:14.Get: 2021/1/3 11:56:10No found Cache at 2021/1/3 11:56:15.Get: 2021/1/3 11:56:15Get Cache From memory at 2021/1/3 11:56:15.Get: 2021/1/3 11:56:15Get Cache From distribute at 2021/1/3 11:56:16.Get: 2021/1/3 11:56:15Get Cache From memory at 2021/1/3 11:56:16.Get: 2021/1/3 11:56:15Get Cache From distribute at 2021/1/3 11:56:17.Get: 2021/1/3 11:56:15Get Cache From memory at 2021/1/3 11:56:17.Get: 2021/1/3 11:56:15Get Cache From distribute at 2021/1/3 11:56:18.Get: 2021/1/3 11:56:15Get Cache From memory at 2021/1/3 11:56:18.Get: 2021/1/3 11:56:15Get Cache From distribute at 2021/1/3 11:56:19.Get: 2021/1/3 11:56:15Get Cache From memory at 2021/1/3 11:56:19.Get: 2021/1/3 11:56:15
就是这样,大家就能够很简略的混用 各种缓存了,
然而呢,多个缓存有没有用?缓存穿透等等问题须要大家最好想好才应用哦
残缺的demo 放在 https://github.com/fs7744/AopDemoList/tree/master/MultipleCache/MultipleCache