一、介绍

  由于CPU从内存中读取数据的速度比从磁盘读取快几个数量级,并且存在内存中,减小了数据库访问的压力,所以缓存几乎每个项目都会用到。一般常用的有MemoryCache、Redis。MemoryCache将存入的对象都作为Object对象存储,Redis分为五种类型存储,在微软提供的缓存组件中也包含Redis和SQL Server缓存,具体下次文章详细讲解。微软缓存组件源码在https://github.com/aspnet/Caching

二、简单使用

  组件的使用很简单,只需要添加组件,在需要使用的时候,注入缓存组件。下面为一个简单的例子,新建Api项目,修改为下面代码

public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddMemoryCache();//添加MemoryCache组件
}

内存缓存过期时间有3种,绝对过期时间、相对现在的过期时间和滑动过期时间。

public class ValuesController : Controller
{
private readonly IMemoryCache _memoryCache;//注入MemoryCache
public ValuesController(IMemoryCache memoryCache)
{
this._memoryCache = memoryCache;
} [HttpGet]
public async Task<IEnumerable<string>> Get()
{
//1、绝对过期时间
_memoryCache.Set("key1", "", new DateTimeOffset(new DateTime(, , )));
//2、相对于现在的过期时间 缓存相对于现在的时间后两秒失效
_memoryCache.Set("key2", "", new TimeSpan(, , ));
//3、滑动过期时间 缓存在两秒内没有被命中,则失效,命中后缓存时间又变为2秒
_memoryCache.Set("key3", "", new MemoryCacheEntryOptions()
{
SlidingExpiration = new TimeSpan(, , ),
Priority = CacheItemPriority.NeverRemove
});
      
Thread.Sleep();
Console.WriteLine($@"value1:{_memoryCache.Get("key1")} value2:{_memoryCache.Get("key2")} value3:{_memoryCache.Get("key3")}");
//因为在1.5秒被命中 所以key3的值,缓存时间重新变为2秒
Thread.Sleep();
Console.WriteLine($@"value1:{_memoryCache.Get("key1")} value2:{_memoryCache.Get("key2")} value3:{_memoryCache.Get("key3")}");
       //创建一个相对现在时间2秒的缓存ke4,并注册缓存失效时的回调函数(注意此回调函数并不是真的当缓存失效立马调用,看下面源码详解)
_memoryCache.GetOrCreate("key4", cacheEntry =>
{
cacheEntry.AbsoluteExpirationRelativeToNow = new TimeSpan(, , );
cacheEntry.RegisterPostEvictionCallback(Test);//注册缓存过期时的回调函数
return cacheEntry.Value = "";
});
Thread.Sleep();
_memoryCache.Remove("key4"); return new string[] { "value1", "value2" };
}
//当缓存过期时触发此函数
public void Test(object key, object value, EvictionReason reason, object state)
{
Console.WriteLine($@"缓存{key}已经移除");
}
}

输出结果为:

value1:1 value2:2 value3:3
value1:1 value2:   value3:3  // 注意此处的value2的值为空
缓存key4已经移除

  关于绝对过期时间、相对现在的过期时间、滑动过期时间就不必多说了,上面注释已经写了。需要注意的是上面key4,设置了2秒后缓存过期,触发回调函数,但是如果代码改为下面形式(注释掉移除"key4”的代码),那么会一直不触发回调函数。

 _memoryCache.GetOrCreate("key4", cacheEntry =>
{
cacheEntry.AbsoluteExpirationRelativeToNow = new TimeSpan(, , );
cacheEntry.RegisterPostEvictionCallback(Test);//注册缓存过期时的回调函数
return cacheEntry.Value = "";
});
Thread.Sleep();
//_memoryCache.Remove("key4");

比如上面已经sleep 3秒了,key4的缓存时间为2秒,key4缓存的已经过期了,如果删除那一行代码,按理应该在第二秒是触发回调函数,但是测试不会。只有对key4操作的时候才会触发回调函数(后面源码详细分析)。

在MemoryCache类中有CreateEntry,这个方法比较特殊

var entry = _memoryCache.CreateEntry("key5");
entry.Value = "";
var a = _memoryCache.Get("key5");//这里输出为null,并不是我们想象的值

正确的使用方法为:需要entry.Dispose()或者使用using。(具体原因下面源码分析)

var entry = _memoryCache.CreateEntry("key5");
entry.Value = "";
entry.Dispose();
var a = _memoryCache.Get("key5");//输出为5

三、MemoryCache源码解析

  首先看我们在Startup中声明添加的内存缓存的对应的源码。

   services.AddMemoryCache();//添加MemoryCache组件
public static IServiceCollection AddMemoryCache(this IServiceCollection services)
{
if (services == null)
{
throw new ArgumentNullException(nameof(services));
} services.AddOptions();
    //实际上就是注入一个单例的MemoryCache
services.TryAdd(ServiceDescriptor.Singleton<IMemoryCache, MemoryCache>());
return services;
}

MemoryCacheOptions类:主要是配置一些参数

  ExpirationScanFrequency:此字段表明隔一段时间扫描缓存,移除过期缓存,默认频率为一分钟。

  SizeLimit:设置缓存的大小。

  CompactionPercentage:压缩比例 默认为百分之五。

举例:

 public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddMemoryCache(x =>
{
x.ExpirationScanFrequency = new TimeSpan(, , );//设置扫描频率为3分钟
x.SizeLimit = * * ; //大小为500M
});
}

MemoryCache类:

  ConcurrentDictionary<object, CacheEntry> _entries:一个多线程安全的字典类型,其实缓存的本质就是这个字典,将所有缓存都放入这个字典中,然后通过字典的key(字典的key其实和缓存实体CacheEntry的key值一样)获取CacheEntry实体(CacheEntry实体包含key和value,也就是我们代码中设置的key和value)。

  _expirationScanFrequency:Span类型,表示扫描过期缓存的频率时间,此字段的值来自MemoryCacheOptions类的ExpirationScanFrequency。需要注意的是:假如设置为一分钟扫描一次缓存集合,这个扫描不是主动扫描的,只有当对缓存集合操作时才会扫描。比如增删改查缓存集合的时候,会判断上一次扫描的时间离现在过去多久了,如果超过扫描设置的时间,才会扫描。源代码如下:

    private void StartScanForExpiredItems()//扫描函数。在添加、查找、删除、修改缓存的时候,会调用此方法扫描过滤过期缓存,并不是主动隔一段时间执行
{
var now = _clock.UtcNow;
if (_expirationScanFrequency < now - _lastExpirationScan)//判断现在的时间和最后一次扫描时间,是不是大于设置的时间段
{
_lastExpirationScan = now;
Task.Factory.StartNew(state => ScanForExpiredItems((MemoryCache)state), this, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
}
}
private static void ScanForExpiredItems(MemoryCache cache)
{
var now = cache._clock.UtcNow;
foreach (var entry in cache._entries.Values)//然后遍历字典集合,移除过期缓存
{
if (entry.CheckExpired(now))
{
cache.RemoveEntry(entry);
}
}
}

  Action<CacheEntry> _setEntry:此委托就是将缓存实体放入上面的字典_entries中,并将字典的key值设置为缓存实体的key。

  Action<CacheEntry> _entryExpirationNotification:此委托是移除字典中缓存。

CacheEntry类:这个类主要就是存储缓存实体,它包含的key、value字段就是我们程序中传入的key、value。对于每一个缓存实体都有其过期时间,所以它还包含每种过期类型。它还包含2个重要的委托。

  Action<CacheEntry> _notifyCacheOfExpiration:此委托会在构造函数中被MemoryCache类中的SetEntry(CacheEntry entry)方法赋值。方法的实现将CacheEntry实体放入MemoryCache类字典中,也就是放入缓存中。

  Action<CacheEntry> _notifyCacheEntryDisposed:此委托会在构造函数中被MemoryCache类中的EntryExpired(CacheEntry entry)方法赋值,方法的实现是移除缓存。

  还包含CacheItemPriority类型的Priority,表示当内存不足时,会移除部分内存,会按照由低到高移除。所以当设置的某个缓存实体使用次数较多时,可以设置为High或NeverRemove。默认设置为Normal。

 public enum CacheItemPriority
{
Low,
Normal,
High,
NeverRemove,
}

  需要注意的是CacheEntry有一个重要的方法Dispose(),因为它继承IDisposable,在Dispost方法中调用了_notifyCacheEntryDisposed委托。

public void Dispose()
{
if (!_added)
{
_added = true;
_scope.Dispose();
_notifyCacheEntryDisposed(this);//在此调用委托,而此委托是被MemoryCache类中的SetEntry赋值。目的是将CacheEntry实体放入MemoryCache类的字典中,也就是放入缓存中。
PropagateOptions(CacheEntryHelper.Current);
}
}

这就解释了为我们上面举的例子需要Dispose(),它只有调用Dispose()才能将缓存实体放入字典中。

var entry = _memoryCache.CreateEntry("key5");
entry.Value = "";
entry.Dispose();
var a = _memoryCache.Get("key5");//输出为5

.Net Core缓存组件(MemoryCache)源码解析的更多相关文章

  1. .Net Core缓存组件(Redis)源码解析

    上一篇文章已经介绍了MemoryCache,MemoryCache存储的数据类型是Object,也说了Redis支持五中数据类型的存储,但是微软的Redis缓存组件只实现了Hash类型的存储.在分析源 ...

  2. Django 之 admin组件使用&源码解析

    admin组件使用 Django 提供了基于 web 的管理工具. Django 自动管理工具是 django.contrib 的一部分.可以在项目的 settings.py 中的 INSTALLED ...

  3. .Net Core中的配置文件源码解析

    一.配置简述 之前在.Net Framework平台开发时,一般配置文件都是xml格式的Web.config,而需要配置其他格式的文件就需要自己去读取内容,加载配置了..而Net Core支持从命令行 ...

  4. .Net Core缓存组件(MemoryCache)【缓存篇(二)】

    一.前言 .Net Core缓存源码 1.上篇.NET Core ResponseCache[缓存篇(一)]中我们提到了使用客户端缓存.和服务端缓存.本文我们介绍MemoryCache缓存组件,说到服 ...

  5. Netty高性能组件——FastThreadLocal源码解析(细微处见真章)

    1. 前言 netty自行封装了FastThreadLocal以替换jdk提供的ThreadLocal,结合封装的FastThreadLocalThread,在多线程环境下的变量提高了ThreadLo ...

  6. 26. SpringBoot 初识缓存及 SimpleCacheConfiguration源码解析

    1.引入一下starter: web.cache.Mybatis.MySQL @MapperScan("com.everjiankang.cache.dao") @SpringBo ...

  7. Spark Core 1.3.1源码解析及个人总结

    本篇源码基于赵星对Spark 1.3.1解析进行整理.话说,我不认为我这下文源码的排版很好,不能适应的还是看总结吧. 虽然1.3.1有点老了,但对于standalone模式下的Master.Worke ...

  8. Java并发包源码学习系列:同步组件CountDownLatch源码解析

    目录 CountDownLatch概述 使用案例与基本思路 类图与基本结构 void await() boolean await(long timeout, TimeUnit unit) void c ...

  9. Java并发包源码学习系列:同步组件CyclicBarrier源码解析

    目录 CyclicBarrier概述 案例学习 类图结构及重要字段 内部类Generation及相关方法 void reset() void breakBarrier() void nextGener ...

随机推荐

  1. 10. Halloween 万圣节

    10. Halloween 万圣节 (1) On October the 31st,across Britain and the USA,thousands of children are dress ...

  2. ubuntu上vsftpd服务配置

    Ubuntu上提供两种常用的ftp服务应用:vsftpd 和 tftpd,区别如下: 1)vsftpd 支持客户端上下传文件,支持浏览器显示及下载,支持用户名密码认证,支持匿名访问,默认端口TCP:2 ...

  3. 利用python itchat给女朋友定时发信息

    利用itchat给女朋友定时发信息 涉及到的技术有itchat,redis,mysql,最主要的还是mysql咯,当然咯,这么多东西,我就只介绍我代码需要用到的,其他的,如果需要了解的话,就需要看参考 ...

  4. Oracle数据库查询基本数据

    ------------------------------------------------------------------找出EMP表select * from EMP;--选择在部门30中 ...

  5. PHP字符串函数之 strcmp strncmp strcasecmp strncasecmp strnatcmp strnatcasecmp

    strcmp – 二进制安全字符串比较 strncmp – 二进制安全比较字符串开头的若干个字符 strcasecmp – 二进制安全比较字符串(不区分大小写) strncasecmp – 二进制安全 ...

  6. 五花八门的CSS

    一.颜色 rgba(0, 0, 0, 0.5) rgba括号中前3个数字代表着 red green blue三种颜色的rgb值,0-255,最后一个是设定这个颜色的透明度即alpha值.范围从0到1, ...

  7. LOJ#6387 「THUPC2018」绿绿与串串 / String (Manacher || hash+二分)

    题目描述 绿绿和 Yazid 是好朋友.他们在一起做串串游戏. 我们定义翻转的操作:把一个串以最后一个字符作对称轴进行翻转复制.形式化地描述就是,如果他翻转的串为 RRR,那么他会将前 ∣R∣−1个字 ...

  8. 12:集合map、工具类

    一.map集合 Map:一次添加一对元素.Collection 一次添加一个元素. Map也称为双列集合,Collection集合称为单列集合. 其实map集合中存储的就是键值对(结婚证书), map ...

  9. 你不知道的javascript读书笔记3

    概述 这是我看<你不知道的JavaScript(中卷)>中关于类型检查的笔记,供以后开发时参考,相信对其他人也有用. typeof 我们知道js中有七种内置类型:undefined, nu ...

  10. JavaScript 全屏展示

    浏览器都有页面全屏的功能 F11 ,那么如何用JavaScript控制页面全屏呢?MDN上提供的的API , 一个小demo验证一下! <!DOCTYPE html> <html l ...