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 的方式的更多相关文章

  1. AOP的姿势之 简化 MemoryCache 使用方式

    0. 前言 之前写了几篇文章介绍了一些AOP的知识, 但是还没有亮出来AOP的姿势, 也许姿势漂亮一点, 大家会对AOP有点兴趣 内容大致会分为如下几篇:(毕竟人懒,一下子写完太累了,没有动力) AO ...

  2. AOP的具体实践-简化结果返回的处理

    原因: 以前学习Spring的时候着重学习过AOP概念,但是一直也没有用上,唯一碰到过的就是Spring内置的事务管理.现在碰到过一些结果后面的操作适合用到,所以这里就拿出来用一下,并且复习一下落下的 ...

  3. spring AOP 代理机制、执行过程、四种实现方式及示例详解

    1.加载过程 spring首先检测配置文件中的代理配置,然后去加载bean; 如果配置文件中没有配置代理,自然代理不会生效,如果配置了代理,但是代理还没有生效,那么有可能是加载顺序的问题,即在检测到代 ...

  4. 8 -- 深入使用Spring -- 4...6 AOP代理:基于注解的XML配置文件的管理方式

    8.4.6 基于XML配置文件的管理方式 Spring 2.x 提供一个新的aop:命名空间来定义切面.切入点和增强处理. XML配置方式优点: ⊙ 如果应用没有使用JDK 1.5 以上版本,那么应用 ...

  5. :Spring-06 -AOP [面向切面编程] -配置异常通知的两种方式--AspectJ 方式 -Schema-based 方式

    三.配置异常通知的步骤(AspectJ 方式) 1.只有当切点报异常才能触发异常通知 2.在spring 中有AspectJ 方式提供了异常通知的办法 3.实现步骤: 3.1新建类,在类写任意名称的方 ...

  6. Spring之AOP原理、代码、使用详解(XML配置方式)

    Spring 的两大核心,一是IOC,另一个是AOP,本博客从原理.AOP代码以及AOP使用三个方向来讲AOP.先给出一张AOP相关的结构图,可以放大查看. 一.Spring AOP 接口设计 1.P ...

  7. 浅谈MemoryCache的原生插值方式

    .NET运行时内置了常用的缓存模块: MemoryCache 标准的MemoryCache暴露了如下几个属性和方法: public int Count { get; } public void Com ...

  8. [转]彻底征服 Spring AOP 之 理论篇

    基本知识 其实, 接触了这么久的 AOP, 我感觉, AOP 给人难以理解的一个关键点是它的概念比较多, 而且坑爹的是, 这些概念经过了中文翻译后, 变得面目全非, 相同的一个术语, 在不同的翻译下, ...

  9. java框架篇---spring AOP 实现原理

    什么是AOP AOP(Aspect-OrientedProgramming,面向方面编程),可以说是OOP(Object-Oriented Programing,面向对象编程)的补充和完善.OOP引入 ...

随机推荐

  1. 文艺splay,占坑等着填

    昨天CF上去就A了前三道题,然后自闭罚坐一个小时什么也没写出来23333.似乎D题人均wa3发就很烦.还是肤浅了 今天精神状态不太好,可能是晚睡的缘故,那不如明天一起写了算了 蹲一波大选结果,蹲一波s ...

  2. Python学习随笔:使用xlwings读取和操作Execl文件的数字需要注意的问题

    老猿Python博文目录 专栏:使用PyQt开发图形界面Python应用 老猿Python博客地址 在使用xlwings读取Excel文件中的数据时,所有的数字不论是整数.浮点数还是文本存放的数字,在 ...

  3. PyQt学习随笔:使用QPropertyAnimation开发简单动画

    QPropertyAnimation是PyQt5.QtCore模块提供的动画设计类,使用该类可以针对PyQt的界面对象进行动画播放,如果要针对一个指定对象进行动画播放,包括如下步骤: 一.创建动画对象 ...

  4. jdk源码之 hashmap 与hashtable 的区别

      hashmap hashtable 线程安全 否,但jdk5之后,提供ConcurrentHashMap,可 替代HashTable. 是,synchronized value是否允许为空 是 否 ...

  5. 从零开始的sql注入学习(挖坑不填)

    首先,本人是小白,这篇文章也只是总结了一下大佬们的sql注入方法,要是有错,请各位大佬指出,以便学习. 虽然我是菜鸡,但是太过基础的sql注入问题也就不再重复的解释了.直接从常用的说起. 实战中常用的 ...

  6. pytorch实战(一)hw1——李宏毅老师作业1

    任务描述:利用前9小时数据,预测第10小时的pm2.5的数值,回归任务 kaggle地址:https://www.kaggle.com/c/ml2020spring-hw1 训练集为: 12个月*20 ...

  7. 全国11省市出台区块链专项政策,Panda Global发现 "区块链+政务"被寄予厚望!

    2020年已经过半,回顾2020年的上半年,不难发现其实区块链的变化非常大,今天Panda Global就给大家回顾下上半年全国关于区块链政策的发布情况.今年上半年,全国已有11个省市出台区块链专项政 ...

  8. spark有个节点特别慢,解决办法

    除解决数据倾斜问题外,还要开启推测执行,寻找另一个executor执行task,哪个先完成就取哪个结果,再kill掉另一个.

  9. nginx学习之——虚拟主机配置

    例子1: 基于域名的虚拟主机 server { listen 80;  #监听端口 server_name a.com; #监听域名 location / { root /var/www/a.com; ...

  10. airtest数据线连接手机

    1.用USB数据将手机和电脑进行连接,手机打开开发者模式,并且开启USB调试   2.下载adb调试:只是用户检查有没有设备连接,不下载也行,但是最好下载 使用方法:解压 方法一:使用cmd命令进入解 ...