当前位置: 首页 > news >正文

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

一、介绍

  由于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", "1", new DateTimeOffset(new DateTime(2018, 8, 11)));
            //2、相对于现在的过期时间 缓存相对于现在的时间后两秒失效
            _memoryCache.Set("key2", "2", new TimeSpan(0, 0, 2));
            //3、滑动过期时间  缓存在两秒内没有被命中,则失效,命中后缓存时间又变为2秒
            _memoryCache.Set("key3", "3", new MemoryCacheEntryOptions()
            {
                SlidingExpiration = new TimeSpan(0, 0, 2),
                Priority = CacheItemPriority.NeverRemove
            });
        
            Thread.Sleep(1500);
            Console.WriteLine($@"value1:{_memoryCache.Get("key1")} value2:{_memoryCache.Get("key2")} value3:{_memoryCache.Get("key3")}");
            //因为在1.5秒被命中 所以key3的值,缓存时间重新变为2秒
            Thread.Sleep(1500);
            Console.WriteLine($@"value1:{_memoryCache.Get("key1")} value2:{_memoryCache.Get("key2")} value3:{_memoryCache.Get("key3")}");
       //创建一个相对现在时间2秒的缓存ke4,并注册缓存失效时的回调函数(注意此回调函数并不是真的当缓存失效立马调用,看下面源码详解)
             _memoryCache.GetOrCreate("key4", cacheEntry =>
               {
                   cacheEntry.AbsoluteExpirationRelativeToNow = new TimeSpan(0, 0, 2);
                   cacheEntry.RegisterPostEvictionCallback(Test);//注册缓存过期时的回调函数
                   return cacheEntry.Value = "4";
               });
            Thread.Sleep(3000);
            _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(0, 0, 2);
         cacheEntry.RegisterPostEvictionCallback(Test);//注册缓存过期时的回调函数
         return cacheEntry.Value = "4";
 });
  Thread.Sleep(3000);
  //_memoryCache.Remove("key4");

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

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

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

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

var entry = _memoryCache.CreateEntry("key5"); 
entry.Value = "5";
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(0, 0, 3);//设置扫描频率为3分钟
            x.SizeLimit = 500 * 1024 * 1024; //大小为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 = "5";
entry.Dispose();
var a = _memoryCache.Get("key5");//输出为5

转载于:https://www.cnblogs.com/MicroHeart/p/9448869.html

相关文章:

  • php 函数变量 前加,php在函数和变量前面加上@和$符号的区别详解
  • 凸函数与简森不等式(Jensen's inequality)
  • php date参数n,总结PHP date()参数列表
  • 小程序自定义函数—数字千位转换
  • tp3.2.3php环境要求,TP3.2.3开发手册
  • 控件模板
  • php 热点,PHP+jQuery实现中国地图热点数据统计展示效果
  • phpspy.php,一款php后门 phpspy的情况
  • Appium 之处理首次启动手机App时的系统权限弹框
  • 体对角线 matlab,Matlab计算结果显示求不出精确解
  • 数据库ACID操作---事务四原则
  • php html转pdf 简书,Laravel Html 导出 PDF 方案 ----- wkhtmltopdf laravel-snappy
  • aps.net的身份证背景图片
  • matlab怎么画lnx图像,inx图像(lnx的图像函数)
  • matlab计算结果中特别长,matlab运算结果太长Output truncated. Text exceeds maximum line length o......
  • centos安装java运行环境jdk+tomcat
  • CentOS从零开始部署Nodejs项目
  • CentOS学习笔记 - 12. Nginx搭建Centos7.5远程repo
  • CSS 三角实现
  • ES6核心特性
  • exports和module.exports
  • Laravel5.4 Queues队列学习
  • maven工程打包jar以及java jar命令的classpath使用
  • Mysql数据库的条件查询语句
  • OpenStack安装流程(juno版)- 添加网络服务(neutron)- controller节点
  • React16时代,该用什么姿势写 React ?
  • Yii源码解读-服务定位器(Service Locator)
  • 阿里云应用高可用服务公测发布
  • 初识MongoDB分片
  • 从PHP迁移至Golang - 基础篇
  • 通信类
  • 微信端页面使用-webkit-box和绝对定位时,元素上移的问题
  • 新版博客前端前瞻
  • 栈实现走出迷宫(C++)
  • 移动端高清、多屏适配方案
  • 智能情侣枕Pillow Talk,倾听彼此的心跳
  • ​LeetCode解法汇总2304. 网格中的最小路径代价
  • #LLM入门|Prompt#3.3_存储_Memory
  • #pragma data_seg 共享数据区(转)
  • $$$$GB2312-80区位编码表$$$$
  • (C#)Windows Shell 外壳编程系列4 - 上下文菜单(iContextMenu)(二)嵌入菜单和执行命令...
  • (Java数据结构)ArrayList
  • (附源码)小程序儿童艺术培训机构教育管理小程序 毕业设计 201740
  • (论文阅读40-45)图像描述1
  • (七)Knockout 创建自定义绑定
  • (转)scrum常见工具列表
  • (转)重识new
  • ***利用Ms05002溢出找“肉鸡
  • .NET 8 编写 LiteDB vs SQLite 数据库 CRUD 接口性能测试(准备篇)
  • .NET HttpWebRequest、WebClient、HttpClient
  • .NET Standard、.NET Framework 、.NET Core三者的关系与区别?
  • .net 程序 换成 java,NET程序员如何转行为J2EE之java基础上(9)
  • .net 简单实现MD5
  • .net 重复调用webservice_Java RMI 远程调用详解,优劣势说明
  • .NET/C# 中设置当发生某个特定异常时进入断点(不借助 Visual Studio 的纯代码实现)