AOP的姿势之 简化混用 MemoryCache 和 DistributedCache 的方式
0. 前言
之前写了几篇文章介绍了一些AOP的知识,
但是还没有亮出来AOP的姿势,
也许姿势漂亮一点,
大家会对AOP有点兴趣
内容大致会分为如下几篇:(毕竟人懒,一下子写完太累了,没有动力)
AOP的姿势之 简化 MemoryCache 使用方式
AOP的姿势之 简化混用 MemoryCache 和 DistributedCache 使用方式
AOP的姿势之 如何把 HttpClient 变为声明式
至于AOP框架在这儿示例依然会使用我自己基于emit实现的动态代理AOP框架: https://github.com/fs7744/Norns.Urd
毕竟是自己写的,魔改/加功能都很方便,
万一万一大家如果有疑问,(虽然大概不会有),我也好回答, (当然如果大家认可,在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:10
Get Cache From memory at 2021/1/3 11:56:10.
Get: 2021/1/3 11:56:10
Get Cache From distribute at 2021/1/3 11:56:11.
Get: 2021/1/3 11:56:10
Get Cache From memory at 2021/1/3 11:56:11.
Get: 2021/1/3 11:56:10
Get Cache From distribute at 2021/1/3 11:56:12.
Get: 2021/1/3 11:56:10
Get Cache From memory at 2021/1/3 11:56:12.
Get: 2021/1/3 11:56:10
Get Cache From distribute at 2021/1/3 11:56:13.
Get: 2021/1/3 11:56:10
Get Cache From memory at 2021/1/3 11:56:13.
Get: 2021/1/3 11:56:10
Get Cache From distribute at 2021/1/3 11:56:14.
Get: 2021/1/3 11:56:10
Get Cache From memory at 2021/1/3 11:56:14.
Get: 2021/1/3 11:56:10
No found Cache at 2021/1/3 11:56:15.
Get: 2021/1/3 11:56:15
Get Cache From memory at 2021/1/3 11:56:15.
Get: 2021/1/3 11:56:15
Get Cache From distribute at 2021/1/3 11:56:16.
Get: 2021/1/3 11:56:15
Get Cache From memory at 2021/1/3 11:56:16.
Get: 2021/1/3 11:56:15
Get Cache From distribute at 2021/1/3 11:56:17.
Get: 2021/1/3 11:56:15
Get Cache From memory at 2021/1/3 11:56:17.
Get: 2021/1/3 11:56:15
Get Cache From distribute at 2021/1/3 11:56:18.
Get: 2021/1/3 11:56:15
Get Cache From memory at 2021/1/3 11:56:18.
Get: 2021/1/3 11:56:15
Get Cache From distribute at 2021/1/3 11:56:19.
Get: 2021/1/3 11:56:15
Get 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
AOP的姿势之 简化混用 MemoryCache 和 DistributedCache 的方式的更多相关文章
- AOP的姿势之 简化 MemoryCache 使用方式
0. 前言 之前写了几篇文章介绍了一些AOP的知识, 但是还没有亮出来AOP的姿势, 也许姿势漂亮一点, 大家会对AOP有点兴趣 内容大致会分为如下几篇:(毕竟人懒,一下子写完太累了,没有动力) AO ...
- AOP的具体实践-简化结果返回的处理
原因: 以前学习Spring的时候着重学习过AOP概念,但是一直也没有用上,唯一碰到过的就是Spring内置的事务管理.现在碰到过一些结果后面的操作适合用到,所以这里就拿出来用一下,并且复习一下落下的 ...
- spring AOP 代理机制、执行过程、四种实现方式及示例详解
1.加载过程 spring首先检测配置文件中的代理配置,然后去加载bean; 如果配置文件中没有配置代理,自然代理不会生效,如果配置了代理,但是代理还没有生效,那么有可能是加载顺序的问题,即在检测到代 ...
- 8 -- 深入使用Spring -- 4...6 AOP代理:基于注解的XML配置文件的管理方式
8.4.6 基于XML配置文件的管理方式 Spring 2.x 提供一个新的aop:命名空间来定义切面.切入点和增强处理. XML配置方式优点: ⊙ 如果应用没有使用JDK 1.5 以上版本,那么应用 ...
- :Spring-06 -AOP [面向切面编程] -配置异常通知的两种方式--AspectJ 方式 -Schema-based 方式
三.配置异常通知的步骤(AspectJ 方式) 1.只有当切点报异常才能触发异常通知 2.在spring 中有AspectJ 方式提供了异常通知的办法 3.实现步骤: 3.1新建类,在类写任意名称的方 ...
- Spring之AOP原理、代码、使用详解(XML配置方式)
Spring 的两大核心,一是IOC,另一个是AOP,本博客从原理.AOP代码以及AOP使用三个方向来讲AOP.先给出一张AOP相关的结构图,可以放大查看. 一.Spring AOP 接口设计 1.P ...
- 浅谈MemoryCache的原生插值方式
.NET运行时内置了常用的缓存模块: MemoryCache 标准的MemoryCache暴露了如下几个属性和方法: public int Count { get; } public void Com ...
- [转]彻底征服 Spring AOP 之 理论篇
基本知识 其实, 接触了这么久的 AOP, 我感觉, AOP 给人难以理解的一个关键点是它的概念比较多, 而且坑爹的是, 这些概念经过了中文翻译后, 变得面目全非, 相同的一个术语, 在不同的翻译下, ...
- java框架篇---spring AOP 实现原理
什么是AOP AOP(Aspect-OrientedProgramming,面向方面编程),可以说是OOP(Object-Oriented Programing,面向对象编程)的补充和完善.OOP引入 ...
随机推荐
- 这可能是最为详细的Docker入门总结
写在前面 毕设是关于区块链的,自然就用到了docker,感觉到了docker的强大.学习源于总结,所以找了一些资料,这篇文章原作写的不错,看了好多遍哈哈. 这可能是最为详细的Docker入门总结 市面 ...
- PyQt(Python+Qt)学习随笔:model/view架构中的QStringListModel
老猿Python博文目录 专栏:使用PyQt开发图形界面Python应用 老猿Python博客地址 一.QStringListModel介绍 QStringListModel是Qt提供的一个已经实现Q ...
- PyQt(Python+Qt)学习随笔:窗口的布局设置及访问
老猿Python博文目录 老猿Python博客地址 在Qt Designer中,可以在一个窗体上拖拽左边的布局部件,在窗口中进行布局管理,但除了基于窗体之上进行布局之外,还需要窗体本身也进行布局管理才 ...
- PyQt(Python+Qt)学习随笔:Qt Designer中QAbstractButton派生按钮部件的shortcut 属性
shortcut 属性保存与按钮关联的快捷键.可以使用shortcut()和setShortcut(QKeySequence)访问和设置该属性. 关于这个属性官网介绍的不多,经老猿实际验证,它与tex ...
- 关于微信NFC功能开发的链接总结
特此申明:若有侵权,请联系我,我会第一时间删除 一. 小程序开发一般流程: 首先调用 wx.getHCEState(OBJECT), 判断设备是否支持NFC,(ios,android兼容性处理) 调用 ...
- 深入理解Java虚拟机(八)——类加载机制
是什么是类加载机制 Java虚拟机将class文件加载到内存,并对数据进行校验.转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这个过程就是类加载机制. 类的生命周期 一个类从加载到内存 ...
- Linux下修改禅道端自定义端口号
第一种方式 一. 首先,如果我们的服务器的80端口没有开放的话,那么我们就是只能修改Apache应用服务的端口了,其实非常简单,安装完成禅道后,在任意目录下输入命令: /opt/zbox ...
- vue第八单元(组件通信 子父,父子组件通信 自定义事件 事件修饰符 v-model props验证 )
第八单元(组件通信 子父,父子组件通信 自定义事件 事件修饰符 v-model props验证 ) #课程目标 掌握使用props让父组件给子组件传参(重点) 掌握props属性的使用以及prop验证 ...
- 网站开发学习Python实现-Django项目部署-介绍(6.2.1)
@ 目录 1.第一步:找源码 2.第二步:在windows中更改代码 2.第三步:同步到linux中 3.第三步:部署 4.第四步:运行 关于作者 1.第一步:找源码 从github上找一个djang ...
- (第一篇)记一次python分布式web开发(利用docker)
作者:落阳 日期:2020-12-23 在一次项目开发中,决定使用docker+nginx+flask+mysql的技术栈来开发,用此系列文章记录开发的过程. 系列文章,当前为第一篇,记录一次pyth ...