• C#系列之聊聊.Net Core的InMemoryCache


    作者:暴王
    个人博客:http://www.boydwang.com/2017/12/net-core-in-memory-cache/

    这两天在看.net core的in memory cache,这里记录一下用法,主要涉及MemoryCache的Get/Set/Expire/Flush。
    首先我们先用dotnet命令创建一个mvc的项目,这里我们将使用postman来请求server,

    dotnet new MVC 
    

    因为我们要用到 Microsoft.Extensions.Caching.Memory这个nuget包,所以需要添加引用,用VsCode(或任何编辑器)打开刚才建的mvc项目路径下的*.csproj文件,在这里我的是cache.csproj,找到这个标签,添加如下代码:

    <PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="2.0.0.0"/>
    

    注意版本号可能不一样,我用的是.net core 2.0.
    之后我们需要注册cache服务,打开Startup.cs文件,找到ConfigureServices方法,添加如下代码:

    services.AddMemoryCache();
    

    ConfigureServices方法看起来应该是这样:

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMemoryCache();
        services.AddMvc();
    }
    

    之后我们就可以在controller里通过构造注入的方式使用InMemoryCache啦。
    打开HomeController或者自己新建一个Controller,在修改构造方法

    private IMemoryCache _cache;
    public HomeController(IMemoryCache cache)
    {
        this._cache = cache;
    }
    

    先来看看MemoryCache的定义:

    Constructors:
    MemoryCache(IOptions)
    
    Properties:
    Count(Gets the count of the current entries for diagnostic purposes.)
    
    Methods:
    Compact(Double)
    CreateEntry(Object)
    Dispose()
    Dispose(Boolean)
    Finalize()
    Remove(Object)
    TryGetValue(Object, Object)
    
    Extension Methods:
    Get(IMemoryCache, Object)
    Get(IMemoryCache, Object)
    GetOrCreate(IMemoryCache, Object, Func)
    GetOrCreateAsync(IMemoryCache, Object, Func>)
    Set(IMemoryCache, Object, TItem)
    Set(IMemoryCache, Object, TItem, MemoryCacheEntryOptions)
    Set(IMemoryCache, Object, TItem, IChangeToken)
    Set(IMemoryCache, Object, TItem, DateTimeOffset)
    Set(IMemoryCache, Object, TItem, TimeSpan)
    TryGetValue(IMemoryCache, Object, TItem)
    

    我们用到的大部分都是 扩 展 方 法,这是一个奇怪的现象,但这不是这篇文章讨论的重点,这里会使用到

    TryGetValue(Object, Object)
    Set<TItem>(IMemoryCache, Object, TItem, MemoryCacheEntryOptions)
    

    这两个方法,来Get/Set/Expire缓存项。

    首先我们来添加一个get的webapi:

    [HttpGet("cache/{key}")]
    public IActionResult GetCache(string key)
    {
        object result = new object();
        _cache.TryGetValue(key, out result);
        return new JsonResult(result);
    }
    

    它接受一个key作为参数,如果找到则返回找到的值,若找不到则返回空
    现在我们可以在命令行里输入

    dotnet restore
    dotnet run
    

    来启动web项目,之后我们可以通过

    http://localhost:5000/cache/{key}
    

    这个url来访问cache,此时cache还没有值
    此处输入图片的描述
    因为此时我们还没有set值。
    接下来添加一个Set方法,在添加之前,我们先来看一下MemoryCacheEntryOptions的定义。

    Constructors:
    MemoryCacheEntryOptions()

    Properties:
    AbsoluteExpiration
    AbsoluteExpirationRelativeToNow
    ExpirationTokens
    PostEvictionCallbacks
    Priority
    Size
    SlidingExpiration

    Extension Methods:
    AddExpirationToken(MemoryCacheEntryOptions, IChangeToken)
    RegisterPostEvictionCallback(MemoryCacheEntryOptions, PostEvictionDelegate)
    RegisterPostEvictionCallback(MemoryCacheEntryOptions, PostEvictionDelegate, Object)
    SetAbsoluteExpiration(MemoryCacheEntryOptions, DateTimeOffset)
    SetAbsoluteExpiration(MemoryCacheEntryOptions, TimeSpan)
    SetPriority(MemoryCacheEntryOptions, CacheItemPriority)
    SetSize(MemoryCacheEntryOptions, Int64)
    SetSlidingExpiration(MemoryCacheEntryOptions, TimeSpan)

    这里有几个概念:
    AbsoluteExpiration
    代表了绝对绝对超时时间,在一定时间后必定超时(比如15分钟)

    SlidingExpiration
    代表了滑动超时时间(我自己翻译的。。),滑动的意思就是假如你设置了SlidingExpiration超时时间为5分钟,如果在5分钟里,有新的请求来获取这个cached item,那么这个5分钟会重置,直到超过5分钟没有请求来,才超时

    CacheItemPriority
    这是一个枚举,代表了缓存的优先级,默认值为Normal,如果设置为NeverRemove则一直不超时。

    High	
    Low	
    NeverRemove	
    Normal
    

    RegisterPostEvictionCallback
    这是个方法需要传一个回调,在缓存项被移除(超时)的时候触发回调。

    接着我们来添加一个Set方法,并且为它添加一个canceltoken,以便我们能够手动控制强制清空缓存。

    private static CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
    
    [HttpPost("cache/")]
    public IActionResult SetCache([FromBody]CacheItem item)
    {
        var cacheEntryOptions = new MemoryCacheEntryOptions()
        .SetAbsoluteExpiration(TimeSpan.FromMinutes(5))
        .RegisterPostEvictionCallback(DependentEvictionCallback, null)
        .AddExpirationToken(new CancellationChangeToken(cancellationTokenSource.Token));
        _cache.Set(item.key, item.value, cacheEntryOptions);
        return Ok();
    }
    

    然后我们就可以用postman的post请求来Set缓存了,地址是:

    http://localhost:5000/cache
    

    此处输入图片的描述

    接下来我们来添加一个flush api,我们能够手动清空缓存。这里我们利用了上面在Set时添加的cancellationTokenSource

    [HttpGet("cache/flush")]
    public IActionResult Flush()
    {
        cancellationTokenSource.Cancel();
        return Ok();
    }
    

    访问地址:

    http://localhost:5000/cache/flush
    

    调用这个api会发现在console里有一行输出

    Parent entry was evicted. Reason: TokenExpired, Key: a.
    

    可以在多个缓存项中添加同一个token,达到同时清除多个缓存项的目的。

    遇到的坑:
    1.token不work的问题.
    我在最初实现的时候,加了一个token,是这么写的

    private CancellationTokenSource cancellationTokenSource;
    
    public HomeController(IMemoryCache cache)
    {
        this._cache = cache;
        cancellationTokenSource = new CancellationTokenSource();
    }
    
    [HttpGet("cache/flush")]
    public IActionResult Flush()
    {
        cancellationTokenSource.Cancel();
        return Ok();
    }
    

    然后发现调用flush一直不生效,cache并没有被清掉,很纳闷,以为我的token方法用的有问题。
    直到我换成了下面的代码,大家体会一下。

    private static CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
    
    public HomeController(IMemoryCache cache)
    {
        this._cache = cache;
    }
    
    [HttpGet("cache/flush")]
    public IActionResult Flush()
    {
        cancellationTokenSource.Cancel();
        return Ok();
    }
    

    仅仅是一个static的问题,就产生了不一样的结果,这是因为每次httprequest过来,都会启动一个新线程去响应它,因此在set的时候加进去的那个token,并不是flush请求过来的token,因为又调用了一次构造方法,生成了一个新的CancellationTokenSource对象,因此调用token.Cancel()方法必然会失效,因此改成了static,让每次请求的都是同一个token,这样就不会造成不同token导致的Cancel方法不work的问题,清空cache也就能正常工作了。

    2.RegisterPostEvictionCallback重复触发的问题

    RegisterPostEvictionCallback不仅仅在缓存超时的时候触发,也会在缓存被替换(更新)的时候触发,在PostEvictionDelegate有一个参数为EvictionReason指明了缓存项被移除的原因

     public delegate void PostEvictionDelegate(object key, object value, EvictionReason reason, object state);
    
    EvictionReason
    None = 0,
    Removed = 1,  缓存项被Remove()方法移除
    Replaced = 2,  缓存项被更新
    Expired = 3,  缓存项超时
    TokenExpired = 4, 缓存由token触发超时
    Capacity = 5 缓存空间不足
    

    因此我们需要在callback里根据需要判断缓存是因为什么原因被移除,才能避免意外的回调触发。

  • 相关阅读:
    Nginx模块fastcgi_cache的几个注意点 转
    CGI与FastCGI 转
    APUE--UNIX环境编程
    与 在记事本中的内容表现方式 原创
    Linux 内核使用的 GNU C 扩展
    GCC内嵌汇编
    linux下64位汇编的系统调用系列
    查看源码Vim+Cscope
    GCC 编绎选项 转
    Linux系统启动流程 图解
  • 原文地址:https://www.cnblogs.com/xiandnc/p/9517017.html
Copyright © 2020-2023  润新知