一、介绍

  由于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. 状态机学习(六)解析JSON2

    来自 从零开始的 JSON 库教程 从零开始教授如何写一个符合标准的 C 语言 JSON 库 作者 Milo Yip https://zhuanlan.zhihu.com/json-tutorial ...

  2. AndroidStudio 3 export jar file

    1.  File -> New -> New Module -> Android Library  例子中暂命名ModuleA 2. 修改刚创建 ModuleA下的build.gra ...

  3. vue里router-link标签设置动态路由的3个方法

    1.to绑定data里面的一个值 2.to绑定一个对象,用name 3.to绑定一个对象,用path 例子: <router-link :to="home"><s ...

  4. Windows激活客户端 已停止工作

    win+r 进入运行 slmgr /ipx 新的sn slmgr /ato

  5. 第一节 —— vue2.0 环境安装,工程化开发

    vue的开发有两种,一种是直接的在script标签里引入vue.js文件即可,这样子引入的话个人感觉做小型的多页面会比较舒坦,一旦做大型一点的项目,还是离不开webpack. 所以另一种方法也就是基于 ...

  6. [solution] JZOJ-5795 词典

    [solution]JZOJ-5795 词典 题面 Description 小C有$n$个字符串$T_1 T_n$,给出$m$个询问 第$i$个询问给出一个字符串$S_i$,对于每个询问,我们可以得到 ...

  7. sublime使用技巧汇总

    sublime使用技巧 Ubuntu下安装sublime text 3143版本 Install the GPG key: wget -qO - https://download.sublimetex ...

  8. visual studio单项目一次生成多框架类库、多框架项目合并

    目录 不同平台框架项目使用同一套代码,一次编译生成多个框架类库 需要先了解的东西 分析 添加PropertyGroup 多目标平台 编译符号和输出目录设置 添加依赖 代码文件处理 主副平台项目文件处理 ...

  9. 背水一战 Windows 10 (77) - 控件(控件基类): ContentControl, UserControl, Page

    [源码下载] 背水一战 Windows 10 (77) - 控件(控件基类): ContentControl, UserControl, Page 作者:webabcd 介绍背水一战 Windows ...

  10. bootstrap教程,SQL

    版权声明:未经博主允许不得转载 SQL DDL数据定义语言 TPL事务处理语言 DCL数据控制语言 DML数据操作语言 DML SELECT INSERT UPDATE DELETE Join从句 J ...