話接上篇 [ASP.NET Core - 快取之記憶體快取(上)],所以這裡的目錄從 2.4 開始。
2.4 MemoryCacheEntryOptions
MemoryCacheEntryOptions 是記憶體快取配置類,可以透過它配置快取相關的策略。除了上面講到的過期時間,我們還能夠設定下面這些:
- 設定快取優先順序。
- 設定在從快取中逐出條目後呼叫的 PostEvictionDelegate。
回撥在與從快取中刪除項的程式碼不同的執行緒上執行。 - 限制快取大小
var memoryCacheEntryOption = new MemoryCacheEntryOptions();
memoryCacheEntryOption.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(3);
// 快取優先順序,Low, Normal, High, NeverRemove,Normal是預設值,與快取刪除時的策略有關
memoryCacheEntryOption.SetPriority(CacheItemPriority.Normal);
// 註冊快取項刪除回撥事件
memoryCacheEntryOption.RegisterPostEvictionCallback(PostEvictionDelegate);
_cache.Set(CacheKey, cacheValue, memoryCacheEntryOption);
public void PostEvictionDelegate(object cacheKey, object cacheValue, EvictionReason evictionReason, object state)
{
var memoryCache = (IMemoryCache)state;
Console.WriteLine($"Entry {cacheKey}:{cacheValue} was evicted: {evictionReason}.");
}
快取大小限制要配合 MemoryCache 例項的配置來使用。MemoryCache 例項可以選擇指定並強制實施大小限制。 快取大小限制沒有定義的度量單位,因為快取沒有度量條目大小的機制。 如果設定了快取大小限制,則所有條目都必須指定大小。 ASP.NET Core 執行時不會根據記憶體壓力限制快取大小。 由開發人員限制快取大小。 指定的大小採用開發人員選擇的單位。
例如:
- 如果 Web 應用主要快取字串,則每個快取條目的大小可以是字串長度。
- 應用可以將所有條目的大小指定為 1,大小限制是條目計數。
如果未設定 SizeLimit,則快取會無限增長。 系統記憶體不足時,ASP.NET Core 執行時不會剪裁快取。 應用必須構建為:
這裡的意思是,快取大小沒有單位,我們可以設定一個總的大小,然後為每個快取條目設定一個大小。如果沒有設定大小的情況下,快取可能會無限增長,直至用完伺服器上的所有記憶體。
// 我們可以在進行記憶體快取註冊的時候設定快取大小限制
services.AddMemoryCache(options =>
{
options.SizeLimit = 1024;
});
// 之後設定每個快取項的大小,可根據開發人員的判斷設定,例如這裡無論多大都設定為 1,則最多有 1024 個快取項
memoryCacheEntryOption.SetSize(1);
_cache.Set(CacheKey, cacheValue, memoryCacheEntryOption);
2.5 快取清理
快取到期後不會在後臺自動清理。 沒有計時器可以主動掃描快取中的到期項。 而快取上的任何活動(Get、Set、Remove)都可觸發在後臺掃描到期項。 如果使用了CancellationTokenSource ,則其計時器 (CancelAfter) 也會刪除條目並觸發掃描到期項,這個是下面要講到的。
除了在對快取進行操作時,會觸發對相應的快取項進行過期檢查之外,我們還可以透過手動呼叫 Remove 方法和 Compact 方法對快取進行清理。
_cache.Remove(cacheKey);
_cache.Compact(.25);
Compact 方法會嘗試按以下順序刪除指定百分比的快取:
- 所有到期項。
- 按優先順序排列的項。 首先刪除最低優先順序的項。
- 最近最少使用的物件。
- 絕對到期時間最短的項。
- 可調到期時間最短的項。
永遠不會刪除優先順序為 NeverRemove 的固定項。 上面的程式碼的作用就是刪除cacheKey對於的快取項,並呼叫 Compact 以刪除 25% 的快取條目。
2.6 快取組
快取項的過期策略除了上面講到的過期時間的設定之外,還可以透過 CancellationChangeToken 來控制,透過它可以同時控制多個快取物件的過期策略,實現相關的快取同時過期,構成一個組的概念。
以下為示例程式碼:
首先先定義兩個方法,將快取設定和快取讀取分開:
public interface ICacheService
{
public void PrintDateTimeNow();
public void SetGroupDateTime();
public void PrintGroupDateTime();
}
public class CacheService : ICacheService
{
public const string CacheKey = "CacheTime";
public const string DependentCancellationTokenSourceCacheKey = "DependentCancellationTokenSource";
public const string ParentCacheKey = "Parent";
public const string ChildCacheKey = "Chlid";
private readonly IMemoryCache _cache;
public CacheService(IMemoryCache memoryCache)
{
_cache = memoryCache;
}
public void PrintDateTimeNow()
{
var time = DateTime.Now;
if (!_cache.TryGetValue(CacheKey, out DateTime cacheValue))
{
cacheValue = time;
// 設定絕對過期時間
// 兩種實現的功能是一樣的,只是時間設定的方式不同而已
// 傳入的是 AbsoluteExpirationRelativeToNow, 相對當前的絕對過期時間,傳入時間差,會根據當前時間算出絕對過期時間
// _cache.Set(CacheKey, cacheValue, TimeSpan.FromSeconds(2));
// 傳入的是 AbsoluteExpiration,絕對過期時間,傳入一個DateTimeOffset物件,需要明確的指定具體的時間
// _cache.Set(CacheKey, cacheValue, DateTimeOffset.Now.AddSeconds(2));
//var memoryCacheEntryOption = new MemoryCacheEntryOptions();
//// 滑動過期時間是一個相對時間
//memoryCacheEntryOption.SlidingExpiration = TimeSpan.FromSeconds(3);
//_cache.Set(CacheKey, cacheValue, memoryCacheEntryOption);
var memoryCacheEntryOption = new MemoryCacheEntryOptions();
memoryCacheEntryOption.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(3);
// 快取優先順序,Low, Normal, High, NeverRemove,Normal是預設值,與快取刪除時的策略有關
memoryCacheEntryOption.SetPriority(CacheItemPriority.Normal);
memoryCacheEntryOption.RegisterPostEvictionCallback(PostEvictionDelegate);
// 之後設定每個快取項的大小,可根據開發人員的判斷設定,例如這裡無論多大都設定為 1,則最多有 1024 個快取項
memoryCacheEntryOption.SetSize(1);
_cache.Set(CacheKey, cacheValue, memoryCacheEntryOption);
}
time = cacheValue;
Console.WriteLine("快取時間:" + time.ToString("yyyy-MM-dd HH:mm:ss"));
Console.WriteLine("當前時間:" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
}
public void PostEvictionDelegate(object cacheKey, object cacheValue, EvictionReason evictionReason, object state)
{
var memoryCache = (IMemoryCache)state;
Console.WriteLine($"Entry {cacheKey}:{cacheValue} was evicted: {evictionReason}.");
}
public void SetGroupDateTime()
{
// 這裡為了將 CancellationTokenSource 儲存起來,以便在外部可以獲取得到
var cancellationTokenSource = new CancellationTokenSource();
_cache.Set(
DependentCancellationTokenSourceCacheKey,
cancellationTokenSource);
using var parentCacheEntry = _cache.CreateEntry(ParentCacheKey);
parentCacheEntry.Value = DateTime.Now;
Task.Delay(TimeSpan.FromSeconds(1)).Wait();
_cache.Set(
ChildCacheKey,
DateTime.Now,
new CancellationChangeToken(cancellationTokenSource.Token));
}
public void PrintGroupDateTime()
{
if(_cache.TryGetValue(ParentCacheKey, out DateTime parentCacheDateTime))
{
Console.WriteLine("ParentDateTime:" + parentCacheDateTime.ToString("yyyy-MM-dd HH:mm:ss"));
}
else
{
Console.WriteLine("ParentDateTime is canceled");
}
if (_cache.TryGetValue(ChildCacheKey, out DateTime childCacheDateTime))
{
Console.WriteLine("ChildDateTime:" + childCacheDateTime.ToString("yyyy-MM-dd HH:mm:ss"));
}
else
{
Console.WriteLine("ChildDateTime is canceled");
}
}
}
之後改造一下入口檔案中的測試程式碼:
var service = host.Services.GetRequiredService<ICacheService>();
service.SetGroupDateTime();
service.PrintGroupDateTime();
service.PrintGroupDateTime();
var cache = host.Services.GetRequiredService<IMemoryCache>();
var cancellationTokenSource = cache.Get<CancellationTokenSource>(CacheService.DependentCancellationTokenSourceCacheKey);
cancellationTokenSource.Cancel();
service.PrintGroupDateTime();
從控制檯輸出可以看得到前兩次快取獲取正常,當呼叫 CancellationTokenSource.Cancel() 方法取消請求之後,快取過期了。
如果使用 CancellationTokenSource,則允許將多個快取條目作為一個組逐出。 使用上述程式碼中的 using 模式,在 using 範圍內建立的快取條目會繼承觸發器和到期設定。不過這種方式只有 using 範圍的快取項和 using 範圍內使用 CancellationTokenSource 的快取項可以構成一個組,如果範圍內還有其他的快取項,是不算在一個組內的。
如果要把多個快取項全部納入一個組,還可以用以下的方式:
_cache.Set(ParentCacheKey,
DateTime.Now,
new CancellationChangeToken(cancellationTokenSource.Token));
_cache.Set(
ChildCacheKey,
DateTime.Now,
new CancellationChangeToken(cancellationTokenSource.Token));
_cache.Set(
ChildsCacheKey,
DateTime.Now,
new CancellationChangeToken(cancellationTokenSource.Token));
2.7 一些注意事項
-
使用回撥重新填充快取項時:
• 多個請求可以發現快取的鍵值為空,因為回撥沒有完成。
• 這可能會導致多個執行緒重新填充快取項。 -
當使用一個快取條目建立另一個快取條目時,子條目會複製父條目的到期令牌和基於時間的到期設定。 手動刪除或更新父條目時,子條目不會過期。
官方文件上還有另外幾條,但是我這邊在上面有講過了,這裡就不再重複了。
參考文章:
ASP.NET Core 中的記憶體中快取
ASP.NET Core 系列: