文章目錄
關於 ASP.NET Core IMemoryCache RegisterPostEvictionCallback 的觸發時機
同事提到想用 ASP.NET Core 的 IMemoryCache 來處理 application 本身的 cache,無奈小弟學藝不精沒有太多想法可以參與討論,所以趕緊惡補,藉這個機會學習也順便做些 POC,大致上與過去使用 System.Runtime.Caching
的 MemoryCache
用法概念接近,但實際用法則不全然相同
基本比較
- | MemoryCache(ASP.NET Core) | MemoryCache(.NET Framework) |
---|---|---|
Namespace | Microsoft.Extensions.Caching.Memory | System.Runtime.Caching |
Assembly | Microsoft.Extensions.Caching.Memory.dll | System.Runtime.Caching.dll |
繼承 | Object –> MemoryCache | Object –> ObjectCache –> MemoryCache |
實作 | IMemoryCache,IDisposable | IEnumerable,IDisposable |
建構子 | MemoryCache(IOptions<MemoryCacheOptions>) | MemoryCache(String, NameValueCollection) MemoryCache(String, NameValueCollection, Boolean) |
屬性 | Count | CacheMemoryLimit Default DefaultCacheCapabilities Item[String] Name PhysicalMemoryLimit PollingInterval |
方法 | Compact(Double) CreateEntry(Object) Dispose() Dispose(Boolean) Finalize() Remove(Object) TryGetValue(Object, Object) | Add(CacheItem, CacheItemPolicy) AddOrGetExisting(CacheItem, CacheItemPolicy) AddOrGetExisting(String, Object, CacheItemPolicy, String) AddOrGetExisting(String, Object, DateTimeOffset, String) Contains(String, String) CreateCacheEntryChangeMonitor(IEnumerable<String>, String) Dispose() Get(String, String) GetCacheItem(String, String) GetCount(String) GetEnumerator() GetLastSize(String) GetValues(IEnumerable<String>, String) Remove(String, CacheEntryRemovedReason, String) Remove(String, String) Set(CacheItem, CacheItemPolicy) Set(String, Object, CacheItemPolicy, String) Set(String, Object, DateTimeOffset, String) Trim(Int32) |
明確介面實作 | - | IEnumerable.GetEnumerator() |
擴充方法 | Get(IMemoryCache, Object) Get<TItem>(IMemoryCache, Object) GetOrCreate<TItem>(IMemoryCache, Object, Func GetOrCreateAsync<TItem>(IMemoryCache, Object, Func<ICacheEntry,Task<TItem>>) Set<TItem>(IMemoryCache, Object, TItem) Set<TItem>(IMemoryCache, Object, TItem, MemoryCacheEntryOptions) Set<TItem>(IMemoryCache, Object, TItem, IChangeToken) Set<TItem>(IMemoryCache, Object, TItem, DateTimeOffset) Set<TItem>(IMemoryCache, Object, TItem, TimeSpan) TryGetValue<TItem>(IMemoryCache, Object, TItem) | CopyToDataTable<T>(IEnumerable<T>) CopyToDataTable<T>(IEnumerable<T>, DataTable, LoadOption) CopyToDataTable<T>(IEnumerable<T>, DataTable, LoadOption, FillErrorEventHandler) Cast<TResult>(IEnumerable) OfType<TResult>(IEnumerable) AsParallel(IEnumerable) AsQueryable(IEnumerable) Ancestors<T>(IEnumerable<T>) Ancestors<T>(IEnumerable<T>, XName) DescendantNodes<T>(IEnumerable<T>) Descendants<T>(IEnumerable<T>) Descendants<T>(IEnumerable<T>, XName) Elements<T>(IEnumerable<T>) Elements<T>(IEnumerable<T>, XName) InDocumentOrder<T>(IEnumerable<T>) Nodes<T>(IEnumerable<T>) Remove<T>(IEnumerable<T>) |
基本環境說明
- macOS Mojave 10.14.4
- .NET Core SDK 2.2.107
- NuGet package
- NLog 4.6.3
- NLog.Web.AspNetCore 4.8.2
- ASP.NET Core MVC 預設專案範本
前置準備
註冊 IMemoryCache
Startup.cs
中的ConfigureServices
方法中加入services.AddMemoryCache();
準備
RegisterPostEvictionCallback
private void CacheExpireHandler(object key, object value, EvictionReason reason, object state) { _logger.LogDebug($"Cache Expire ; key :{key};valu:{value};reason:{reason};state:{state}"); setCache(); }
註冊 NLog
加入
nlog.config
<?xml version="1.0" encoding="utf-8" ?> <nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" autoReload="true" internalLogLevel="Info" internalLogFile="internal-nlog.txt"> <!-- enable asp.net core layout renderers --> <extensions> <add assembly="NLog.Web.AspNetCore"/> </extensions> <!-- the targets to write to --> <targets> <!-- write logs to file --> <target xsi:type="File" name="allfile" fileName="./logs/nlog-all-${shortdate}.log" layout="${longdate}|${event-properties:item=EventId_Id}|${uppercase:${level}}|${logger}|${message} ${exception:format=tostring}" /> <!-- another file log, only own logs. Uses some ASP.NET core renderers --> <target xsi:type="File" name="ownFile-web" fileName="./logs/nlog-own-${shortdate}.log" layout="${longdate}|${event-properties:item=EventId_Id}|${uppercase:${level}}|${logger}|${message} ${exception:format=tostring}|url: ${aspnet-request-url}|action: ${aspnet-mvc-action}" /> </targets> <!-- rules to map from logger name to target --> <rules> <!--All logs, including from Microsoft--> <logger name="*" minlevel="Trace" writeTo="allfile" /> <!--Skip non-critical Microsoft logs and so log only own logs--> <logger name="Microsoft.*" maxlevel="Info" final="true" /> <!-- BlackHole without writeTo --> <logger name="*" minlevel="Trace" writeTo="ownFile-web" /> </rules> </nlog>
Program.cs
的CreateWebHostBuilder
加入.UseNLog()
;public static IWebHostBuilder CreateWebHostBuilder(string[] args) => WebHost.CreateDefaultBuilder(args) .UseStartup<Startup>() .UseNLog(); }
使用 IMemoryCache 與 NLog
private static IMemoryCache _cache; private ILogger<HomeController> _logger; public HomeController(IMemoryCache cache,ILogger<HomeController> logger) { _cache = cache; _logger = logger; }
觸發時機
理想狀況就是在 cache expire 當下就觸發 RegisterPostEvictionCallback,即可立馬重新 recache,但實際情況不如預期
確認觸發時機
public class HomeController : Controller { private static IMemoryCache _cache; private ILogger<HomeController> _logger; public HomeController(IMemoryCache cache,ILogger<HomeController> logger) { _cache = cache; _logger = logger; } public void SetCache() { _logger.LogDebug($"before SetCache:{JsonConvert.SerializeObject(_cache)}"); _logger.LogDebug("SetCache Action"); setCache(); _logger.LogDebug($"after SetCache:{JsonConvert.SerializeObject(_cache)}"); } public IActionResult Index() { _logger.LogDebug($"GetCache @ {DateTime.Now}"); _logger.LogDebug($"before getCache:{JsonConvert.SerializeObject(_cache)}"); var cacheData = _cache.Get<string>("dateTimeNow"); _logger.LogDebug($"after getCache:{JsonConvert.SerializeObject(_cache)}"); return View((object) cacheData); } public IActionResult Privacy() { return View(); } private void setCache() { MemoryCacheEntryOptions cacheExpirationOptions = new MemoryCacheEntryOptions(); var datTimeNow = DateTime.Now; _logger.LogDebug($"SetCache @ {datTimeNow}"); cacheExpirationOptions.AbsoluteExpiration = datTimeNow.AddSeconds(10); cacheExpirationOptions.Priority = CacheItemPriority.High; cacheExpirationOptions.RegisterPostEvictionCallback(CacheExpireHandler, this); _cache.Set<string>("dateTimeNow", datTimeNow.ToString(), cacheExpirationOptions); } private void CacheExpireHandler(object key, object value, EvictionReason reason, object state) { _logger.LogDebug($"Cache Expire ; key :{key};valu:{value};reason:{reason};state:{state}"); setCache(); } [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] public IActionResult Error() { return View(new ErrorViewModel {RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier}); } }
實際結果
心得
以測試結果來看, cache 的 expire 機制是被動式的:cache 在無人存取時不會主動刪除,在存取時當下檢查 expire 與否,確定 expire 會連帶刪除 cache 內容接著觸發 RegisterPostEvictionCallback
而觸發 RegisterPostEvictionCallback
的 cache 當次存取除了會造成刪除 cache 內容還會直接回傳 cache miss 無法取得預期中的 cache 內容,因此光憑 RegisterPostEvictionCallback
是沒辦法達成絕對的定期 recache
參考資訊
文章作者 Yowko Tsai
上次更新 2021-11-03
授權合約
本部落格 (Yowko's Notes) 所有的文章內容(包含圖片),任何轉載行為,必須通知並獲本部落格作者 (Yowko Tsai) 的同意始得轉載,且轉載皆須註明出處與作者。
Yowko's Notes 由 Yowko Tsai 製作,以創用CC 姓名標示-非商業性-相同方式分享 3.0 台灣 授權條款 釋出。