MemoryCache 如何清除全部快取?
- 2021 年 12 月 21 日
- 筆記
最近有個需求需要定時清理伺服器上所有的快取。本來以為很簡單的調用一下 MemoryCache.Clear 方法就完事了。誰知道 MemoryCache 類以及 IMemoryCache 擴展方法都沒有 Clear 方法。這可給難住了,於是想找到所有的 Keys 來一個個 Remove ,誰知道居然也沒有獲取所有 Key 的方法。於是研究了一下 ,找到一些方法,下面介紹兩個方法:
自定義 CacheWrapper 包裝類
MemoryCache 構造 Entry 的時候支援傳入 CancellationChangeToken 對象,當 CancellationChangeToken.Cancel 觸發的時候會自動使該對象過期。那麼我們只要對 MemoryCache 類包裝一下很容易實現一個自己的 Cache 類。
public class CacheWrapper
{
private readonly IMemoryCache _memoryCache;
private CancellationTokenSource _resetCacheToken = new();
public CacheWrapper(IMemoryCache memoryCache)
{
_memoryCache = memoryCache;
}
public void Add(object key, object value, MemoryCacheEntryOptions memoryCacheEntryOptions)
{
using var entry = _memoryCache.CreateEntry(key);
entry.SetOptions(memoryCacheEntryOptions);
entry.Value = value;
// add an expiration token that allows us to clear the entire cache with a single method call
entry.AddExpirationToken(new CancellationChangeToken(_resetCacheToken.Token));
}
public object Get(object key)
{
return _memoryCache.Get(key);
}
public void Remove(object key)
{
_memoryCache.Remove(key);
}
public void Clear()
{
_resetCacheToken.Cancel(); // this triggers the CancellationChangeToken to expire every item from cache
_resetCacheToken.Dispose(); // dispose the current cancellation token source and create a new one
_resetCacheToken = new CancellationTokenSource();
}
}
然後單元測試測試一下:
[TestMethod()]
public void ClearTest()
{
var memCache = new MemoryCache(new MemoryCacheOptions());
var wrapper = new CacheWrapper(memCache);
for (int i = 0; i < 10; i++)
{
wrapper.Add(i.ToString(), new object(), new MemoryCacheEntryOptions());
}
Assert.IsNotNull(wrapper.Get("1"));
Assert.IsNotNull(wrapper.Get("9"));
wrapper.Clear();
for (int i = 0; i < 10; i++)
{
Assert.IsNull(wrapper.Get(i.ToString()));
}
for (int i = 0; i < 10; i++)
{
wrapper.Add(i.ToString(), new object(), new MemoryCacheEntryOptions());
}
Assert.IsNotNull(wrapper.Get("1"));
Assert.IsNotNull(wrapper.Get("9"));
wrapper.Clear();
for (int i = 0; i < 10; i++)
{
Assert.IsNull(wrapper.Get(i.ToString()));
}
}
測試通過。
Compact 方法
以上 CacheWrapper 類雖然可以實現我們想要的功能,但是對於原來的程式有侵入,需要使用 CacheWrapper 類替換默認的 MemoryCache 類,不是太好。於是不死心繼續研究,後來直接看了 MemoryCache 的程式碼(源碼在這),開源真香。發現 MemoryCache 有個 Compact 方法好像在干刪除的勾當。也怪我英文不好,這單詞是壓縮的意思,居然才發現。。。。於是我們的清除所有對象的需求不就輕而易舉了么?
/// Remove at least the given percentage (0.10 for 10%) of the total entries (or estimated memory?), according to the following policy:
/// 1. Remove all expired items.
/// 2. Bucket by CacheItemPriority.
/// 3. Least recently used objects.
/// ?. Items with the soonest absolute expiration.
/// ?. Items with the soonest sliding expiration.
/// ?. Larger objects - estimated by object graph size, inaccurate.
MemoryCache.Compact(double percentage);
Compact 方法會對快取的對象進行壓縮,參數是個double,0.1 表示壓縮 10% ,那麼傳 1.0 就是壓縮 100%,那不就是 Clear All 么。所以我可以使用 Compact(1.0) 來清除所有的快取對象。單元測試跑一下:
[TestMethod()]
public void CompactTest()
{
var memCache = new MemoryCache(new MemoryCacheOptions());
for (int i = 0; i < 10; i++)
{
memCache.Set(i.ToString(), new object(), new MemoryCacheEntryOptions());
}
Assert.IsNotNull(memCache.Get("1"));
Assert.IsNotNull(memCache.Get("9"));
memCache.Compact(1);
for (int i = 0; i < 10; i++)
{
Assert.IsNull(memCache.Get(i.ToString()));
}
for (int i = 0; i < 10; i++)
{
memCache.Set(i.ToString(), new object(), new MemoryCacheEntryOptions());
}
Assert.IsNotNull(memCache.Get("1"));
Assert.IsNotNull(memCache.Get("9"));
memCache.Compact(1);
for (int i = 0; i < 10; i++)
{
Assert.IsNull(memCache.Get(i.ToString()));
}
}
完美通過。
這裡簡單介紹下 Compact 方法。根據注釋它會按照已下優先順序刪除對象:
- 過期的對象
- CacheItemPriority 設置的優先順序,等級越高越不容易被刪除
- 最近最少被使用的對象
- 絕對過期時間
- 滑動過期時間
- 大對象
關注我的公眾號一起玩轉技術
學習交流群