asp.net core 系列之Reponse caching之cache in-memory (2)

這篇文章(主要翻譯於官網,水平有限,見諒)講解asp.net core 中的 Cache in-memory (內存緩存).web

 

 

Cache in-memory in ASP.NET Core

Caching basics

Caching 能夠顯著的提高應用的performance(表現) scalability,經過減小生成內容所必需的作的工做Caching 在變更比較的數據上工做的最好。Caching 能夠作一個備份數據,使得數據比從原來的地方取的快一些。api

ASP.NET Core支持幾種不一樣的緩存。最簡單的緩存是基於IMemoryCache, 它表明一個存儲在web服務器的內存上的cache(緩存)當使用in-memory cache時,運行在多個服務器上的服務器集羣應該確保sessions是不動的不動的sessionsSticky sessions)確保隨後的從一個client發來的請求全都到同一臺服務器。例如,Azure Web appsApplication Request Routing(ARR)來路由全部隨後的請求到同一個服務器。緩存

在一個web集羣上的Non-sticky sessions 要求一個distributed cache(分佈式緩存)來避免緩存一致性問題。對於一些應用,a distributed cache 能夠支持更高的擴展比in-memory cache. 用一個分佈式緩存卸載內存緩存到一個外部處理中。服務器

In-memory cache 能夠存儲任意對象;distributed cache interface 僅限於byte[]. 對於in-memorydistributed cache 存儲cache itemskey-value pairs. session

System.Runtime.Caching/MemoryCache

System.Runtime.Caching/MemoryCache能夠被用在:app

  • .NET Standard 2.0 or laterasp.net

  • Any .NET implementation that targets .NET Standard 2.0 or later. 例如, ASP.NET Core 2.0 or laterless

  • .NET Framework 4.5 or laterasync

Microsoft.Extensions.Caching.Memory/IMemoryCache 被推薦在System.Runtime.Cachign/MemoryCache之上使用, 由於Microsoft.Extensions.Caching.Memory/IMemoryCache是更好的集成在ASP.NET Core中。例如,IMemory 天生能夠用ASP.NET Core的依賴注入工做。分佈式

System.Runtime.Caching/MemoryCache做爲一個兼容橋樑,當移植代碼從ASP.NET 4.X ASP.NET Core時。

Cache guidelines

  • 代碼應該總有一個可靠的選項來取數據而且不是依賴於緩存的可獲得的值

  •  緩存使用稀少的資源,內存。限制緩存增加(cache growth)(內存是稀缺資源, 若是在內存中使用緩存,須要限制緩存增加)

    • 不要使用外部輸入做爲cache keys.

    • 使用expirations(過時時間)限制緩存增加

    • 使用SetSize, SizeSizeLimit來限制cache size.  ASP.NET Core runtime不會根據memory pressure(內存壓力)來限制cache size,它取決於開發者限制cache size. 

Using IMemoryCache

In-memory caching 是一個從你的應用中使用依賴注入引入的服務(service)。在ConfigureServices中調用 AddMemoryCache:

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMemoryCache();

        services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
    }

    public void Configure(IApplicationBuilder app)
    {
        app.UseMvcWithDefaultRoute();
    }
}

Request the IMemoryCache實例在構造函數中:

public class HomeController : Controller
{
    private IMemoryCache _cache;

    public HomeController(IMemoryCache memoryCache)
    {
        _cache = memoryCache;
    }

IMemoryCache要求有NuGet package Microsoft.Extensions.Caching.Memory, 它在Microsoft.AspNetCore.App metapackage也是可用的

下面的代碼使用 TryGetValue 來檢驗 if a time is in the cache. If a time isn’t cached, a new entry is created and added to the cache with Set. 檢驗一個時間值是否在緩存中。若是時間值沒有被緩存,一個新的entry被建立而且with Set加入到緩存中。(即,若是沒被緩存,則加入緩存)

public static class CacheKeys
{
    public static string Entry { get { return "_Entry"; } }
    public static string CallbackEntry { get { return "_Callback"; } }
    public static string CallbackMessage { get { return "_CallbackMessage"; } }
    public static string Parent { get { return "_Parent"; } }
    public static string Child { get { return "_Child"; } }
    public static string DependentMessage { get { return "_DependentMessage"; } }
    public static string DependentCTS { get { return "_DependentCTS"; } }
    public static string Ticks { get { return "_Ticks"; } }
    public static string CancelMsg { get { return "_CancelMsg"; } }
    public static string CancelTokenSource { get { return "_CancelTokenSource"; } }
}

 

public IActionResult CacheTryGetValueSet()
{
    DateTime cacheEntry;

    // Look for cache key.
    if (!_cache.TryGetValue(CacheKeys.Entry, out cacheEntry))
    {
        // Key not in cache, so get data.
        cacheEntry = DateTime.Now;

        // Set cache options.
        var cacheEntryOptions = new MemoryCacheEntryOptions()
            // Keep in cache for this time, reset time if accessed.
            .SetSlidingExpiration(TimeSpan.FromSeconds(3));

        // Save data in cache.
        _cache.Set(CacheKeys.Entry, cacheEntry, cacheEntryOptions);
    }

    return View("Cache", cacheEntry);
}

當前時間和緩存時間都被展現了:

@model DateTime?

<div>
    <h2>Actions</h2>
    <ul>
        <li><a asp-controller="Home" asp-action="CacheTryGetValueSet">TryGetValue and Set</a></li>
        <li><a asp-controller="Home" asp-action="CacheGet">Get</a></li>
        <li><a asp-controller="Home" asp-action="CacheGetOrCreate">GetOrCreate</a></li>
        <li><a asp-controller="Home" asp-action="CacheGetOrCreateAsync">GetOrCreateAsync</a></li>
        <li><a asp-controller="Home" asp-action="CacheRemove">Remove</a></li>
    </ul>
</div>

<h3>Current Time: @DateTime.Now.TimeOfDay.ToString()</h3>
<h3>Cached Time: @(Model == null ? "No cached entry found" : Model.Value.TimeOfDay.ToString())</h3>

requests在超時時間以內時,緩存的時間值保留在緩存中。下面的圖片展現了當前時間和從緩存中檢索的更早的時間。

下面的代碼使用GetOrCreate和GetOrCreateAsync來緩存數據。

public IActionResult CacheGetOrCreate()
{
    var cacheEntry = _cache.GetOrCreate(CacheKeys.Entry, entry => { entry.SlidingExpiration = TimeSpan.FromSeconds(3); return DateTime.Now; }); return View("Cache", cacheEntry);
}

public async Task<IActionResult> CacheGetOrCreateAsync()
{
    var cacheEntry = await _cache.GetOrCreateAsync(CacheKeys.Entry, entry => { entry.SlidingExpiration = TimeSpan.FromSeconds(3); return Task.FromResult(DateTime.Now); });

    return View("Cache", cacheEntry);
}

下面的代碼調用Get來取到緩存時間:

public IActionResult CacheGet()
{
    var cacheEntry = _cache.Get<DateTime?>(CacheKeys.Entry);
    return View("Cache", cacheEntry);
}

GetOrCreate, GetOrCreateAsyc, 和Get 是CacheExtensions類的擴展方法的一部分,CacheExtension類擴展了IMemory的能力。 

MemoryCacheEntryOptions

下面的例子(用來設置內存緩存的一些選項):

  • 設置徹底的expiration time(超時時間)。這是這個記錄能夠被緩存的最大時間,而且能夠防止這個記錄變的太陳舊,當變化的expiration 是不斷更新的。
  • 設置一個變化的expiration time. 到達這個緩存(cache item)的請求將從新設置變化的時間。
  • 給CacheItemPriority.NeverRemove設置cache priority(緩存優先級)
  • 設置PostEvictionDelegate(回調),它將會被調用,在記錄被從緩存驅逐以後Callback(回調)是運行在一個不一樣於從緩存中移除緩存項的線程的其餘線程。(Callback運行在一個區別於移除緩存項線程的其餘線程)
public IActionResult CreateCallbackEntry()
{
    var cacheEntryOptions = new MemoryCacheEntryOptions()
        // Pin to cache.
        .SetPriority(CacheItemPriority.NeverRemove)
        // Add eviction callback
        .RegisterPostEvictionCallback(callback: EvictionCallback, state: this);

    _cache.Set(CacheKeys.CallbackEntry, DateTime.Now, cacheEntryOptions);

    return RedirectToAction("GetCallbackEntry");
}

public IActionResult GetCallbackEntry() { return View("Callback", new CallbackViewModel { CachedTime = _cache.Get<DateTime?>(CacheKeys.CallbackEntry), Message = _cache.Get<string>(CacheKeys.CallbackMessage) }); } public IActionResult RemoveCallbackEntry()
{
    _cache.Remove(CacheKeys.CallbackEntry);
    return RedirectToAction("GetCallbackEntry");
}

private static void EvictionCallback(object key, object value,
    EvictionReason reason, object state)
{
    var message = $"Entry was evicted. Reason: {reason}.";
    ((HomeController)state)._cache.Set(CacheKeys.CallbackMessage, message);
}

使用SetSize, SizeSizeLImit來限制 cache size

一個MemoryCache實例能夠選擇指定或者強制一個size limitThe memory size limit 沒有一個定義的測量單元,由於cache沒有結構來測量記錄(entries)大小(size). 若是cache memory size limit被設置了,全部的entries必須指定size.  ASP.NET Core runtime不會根據memory pressure來limit cache size . 它取決於開發者limit cache size. The size spcified is in units the developer chooses. 

MemoryCache instance may optionally specify and enforce a size limit. The memory size limit does not have a defined unit of measure because the cache has no mechanism to measure the size of entries. If the cache memory size limit is set, all entries must specify size. The ASP.NET Core runtime does not limit cache size based on memory pressure. It's up to the developer to limit cache size. The size specified is in units the developer chooses.

例如:

  • 若是一個web應用主要caching string , 每一個cache entry size應該是字符串長度

  • 應用能夠指定the size of all entries (全部的entry)1,而且這個size limitthe count of entries. (注意:這裏指定全部的entry的大小爲1,則size limit能夠用entry的數量表示。即二者一個是cache entry size(單個entry大小),另外一個是limit size(緩存限制的大小))

下面的代碼建立了一個unitless fiexed size MemoryCache accessible(易接近的) by dependency injection:

// using Microsoft.Extensions.Caching.Memory;
public class MyMemoryCache 
{
    public MemoryCache Cache { get; set; }
    public MyMemoryCache()
    {
        Cache = new MemoryCache(new MemoryCacheOptions
        {
            SizeLimit = 1024
        });
    }
}

SizeLimit沒有 units .   若是cache memory size 已經被設置,Cached entries 必須指定最合適的size in whatever units they deem(認爲)。一個cache 實例的全部用戶應該用一樣的unit system . 如果the sum of the cached entry sizes 超過經過SizeLimit指定的值, An entry 將不會被緩存若是no cache size limit被設置,the cache size set on the entry 將會被忽略。

SizeLimit does not have units. Cached entries must specify size in whatever units they deem most appropriate if the cache memory size has been set. All users of a cache instance should use the same unit system. An entry will not be cached if the sum of the cached entry sizes exceeds the value specified by SizeLimit. If no cache size limit is set, the cache size set on the entry will be ignored.

下面的代碼使用依賴注入容器註冊MyMemoryCache. 

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);

    services.AddSingleton<MyMemoryCache>();
}

MyMemoryCache被建立爲一個independent memory cache 的組件,這個組件瞭解size limited cahce而且知道怎麼合適的設置cache entry size 。

下面是使用MyMemoryCache的代碼:

public class AboutModel : PageModel
{
    private MemoryCache _cache;
    public static readonly string MyKey = "_MyKey";

    public AboutModel(MyMemoryCache memoryCache)
    {
        _cache = memoryCache.Cache;
    }

    [TempData]
    public string DateTime_Now { get; set; }

    public IActionResult OnGet()
    {
        if (!_cache.TryGetValue(MyKey, out string cacheEntry))
        {
            // Key not in cache, so get data.
            cacheEntry = DateTime.Now.TimeOfDay.ToString();

            var cacheEntryOptions = new MemoryCacheEntryOptions() 
                // Set cache entry size by extension method.
                .SetSize(1) // Keep in cache for this time, reset time if accessed.
                .SetSlidingExpiration(TimeSpan.FromSeconds(3));

            // Set cache entry size via property.
            // cacheEntryOptions.Size = 1;

            // Save data in cache.
            _cache.Set(MyKey, cacheEntry, cacheEntryOptions);
        }

        DateTime_Now = cacheEntry;

        return RedirectToPage("./Index");
    }
}

The size of the cache entry 能夠被設置,經過SizeSetSize擴展方法

public IActionResult OnGet()
{
    if (!_cache.TryGetValue(MyKey, out string cacheEntry))
    {
        // Key not in cache, so get data.
        cacheEntry = DateTime.Now.TimeOfDay.ToString();

        var cacheEntryOptions = new MemoryCacheEntryOptions() 
            // Set cache entry size by extension method.
            .SetSize(1) 
            // Keep in cache for this time, reset time if accessed.
            .SetSlidingExpiration(TimeSpan.FromSeconds(3));

        // Set cache entry size via property. // cacheEntryOptions.Size = 1;

        // Save data in cache.
        _cache.Set(MyKey, cacheEntry, cacheEntryOptions);
    }

    DateTime_Now = cacheEntry;

    return RedirectToPage("./Index");
}

Cache dependencies

下面的示例展現了怎麼設置過時一個緩存記錄(how to expire a cache entry)若是一個dependent entry expires(過時). 一個CancellationChangeToken 被加入到cached item. Cancel CancellationTokenSource上被調用,兩個cache entry 都被拋棄

public IActionResult CreateDependentEntries()
{
    var cts = new CancellationTokenSource();
    _cache.Set(CacheKeys.DependentCTS, cts);

    using (var entry = _cache.CreateEntry(CacheKeys.Parent))
    {
        // expire this entry if the dependant entry expires.
        entry.Value = DateTime.Now;
        entry.RegisterPostEvictionCallback(DependentEvictionCallback, this);

        _cache.Set(CacheKeys.Child,
            DateTime.Now,
            new CancellationChangeToken(cts.Token));
    }

    return RedirectToAction("GetDependentEntries");
}

public IActionResult GetDependentEntries()
{
    return View("Dependent", new DependentViewModel
    {
        ParentCachedTime = _cache.Get<DateTime?>(CacheKeys.Parent),
        ChildCachedTime = _cache.Get<DateTime?>(CacheKeys.Child),
        Message = _cache.Get<string>(CacheKeys.DependentMessage)
    });
}

public IActionResult RemoveChildEntry()
{
    _cache.Get<CancellationTokenSource>(CacheKeys.DependentCTS).Cancel();
    return RedirectToAction("GetDependentEntries");
}

private static void DependentEvictionCallback(object key, object value,
    EvictionReason reason, object state)
{
    var message = $"Parent entry was evicted. Reason: {reason}.";
    ((HomeController)state)._cache.Set(CacheKeys.DependentMessage, message);
}

使用CancellationTokenSource容許多個cache entries做爲一組被拋棄。使用代碼中有用的模式,cache entires

Created inside the using block will inherit triggers and expiration settings. 

Additional notes

  • 當用一個回調來增長一個cache item:
    • 能夠發現多個requests緩存的鍵的value 空.由於回調沒有完成。

    • 這可能會致使幾個線程增長cached item

  • 當一個cache entry被使用來建立另外一個cache entry,child複製父母親的entryexpration tokens 而且基於時間的exiration settings. THe child不會經過手動刪除或者parent entry的更新而過時
  • PostEvictionCallback來設置callback, 這個callback將會被觸發。

參考資料:

https://docs.microsoft.com/en-us/aspnet/core/performance/caching/memory?view=aspnetcore-2.2

相關文章
相關標籤/搜索