.NET Core MemoryCache缓存批量获取Key或者删除

发布时间 2024-01-10 13:51:58作者: pccai

.Net Core下使用缓存,除了大家耳熟能详的Redis做分布式缓存外,本地内存缓存也会一起结合来使用,它存取更快,使我们的应用达到极致性能要求。这也是我们经常提到的3级或者4级缓存,每一层都有自己的使用场景,优缺点,结合业务特点来选择合适的才是王道。

这里我们就使用Net原生的 Microsoft.Extensions.Caching.Memory.IMemoryCache 来实现本机内存缓存的一些延展问题进行探讨。该接口实现可以参考源代码:

https://github.com/dotnet/runtime/blob/main/src/libraries/Microsoft.Extensions.Caching.Memory/src/MemoryCache.cs#L272

接口公开了几个常用的方法:

 其中,根据缓存键值来清理缓存用的比较频繁,前提是要精确找到曾经使用的Key名称或者集合,再来逐个清理。业务上有时候我们希望能根据一些特征,批量把一批Key来移除掉,这个时候就找不到枚举缓存Key集合的方法了,网上有也有很多示例:

 1  List<string> getCacheKeys()
 2 {
 3             BindingFlags flags = BindingFlags.Instance | BindingFlags.NonPublic;
 4             var entries = _memoryCache.GetType().GetField("_entries", flags).GetValue(_memoryCache);
 5             var cacheItems = entries.GetType().GetProperty("Keys").GetValue(entries) as ICollection<object>;
 6             var keys = new List<string>();
 7             if (cacheItems == null || cacheItems.Count() == 0)
 8                 return keys;
 9 
10             return cacheItems.Select(r=>r.ToString()).ToList();
11 }

但是执行时找不到"_entries"属性了,接口调整了内部逻辑,继续查找上述源代码发现:

1 ...
2 CoherentState coherentState = _coherentState; // Clear() can update the reference in the meantime
3 if (coherentState._entries.TryGetValue(entry.Key, out CacheEntry? priorEntry))
4 {
5      priorEntry.SetExpired(EvictionReason.Replaced);
6 }
7 ...

说明"_entries"属性被移动到 “_coherentState”属性里面了,我们可以据此修改上述获取Key的代码:

 1 public List<string> GetCacheKeys()
 2 {
 3             const BindingFlags flags = BindingFlags.Instance | BindingFlags.NonPublic;
 4             var coherentState = Cache.GetType().GetField("_coherentState", flags).GetValue(Cache);
 5             var entries = coherentState.GetType().GetField("_entries", flags).GetValue(coherentState);
 6             var cacheItems = entries as IDictionary;
 7             var keys = new List<string>();
 8             if (cacheItems == null) return keys;
 9             foreach (DictionaryEntry cacheItem in cacheItems)
10             {
11                 keys.Add(cacheItem.Key.ToString());
12             }
13             return keys;
14   }

先反射获取_coherentState 属性对象,再接着找里面的缓存键集合,如此就可以批量获取到Key集合了。若需要按照特殊前缀来模糊清理,可以类似下面写法实现:

 1  /// <summary>
 2  /// 清理指定前缀的键值内存缓存
 3  /// </summary>
 4  /// <param name="keyPrefix">平台字典缓存键</param>
 5  public void CleanMemoryCache(string keyPrefix = "mck.dict")
 6  {
 7      var _memoryCache = Furion.App.GetRequiredService<IMemoryCache>();
 8 
 9      //以下反射发现所有内存缓存,一旦量大了,或者在分布式下,就不能去枚举,性能会严重下降
10 
11      const BindingFlags flags = BindingFlags.Instance | BindingFlags.NonPublic;
12      var coherentState = _memoryCache.GetType().GetField("_coherentState", flags).GetValue(_memoryCache);
13      var entries = coherentState.GetType().GetField("_entries", flags).GetValue(coherentState);
14      var cacheItems = entries as IDictionary;
15 
16      foreach (DictionaryEntry cacheItem in cacheItems)
17      {
18          if (cacheItem.Key.ToString().StartsWith(keyPrefix))
19          {
20              _memoryCache.Remove(cacheItem.Key.ToString());
21              Logger.Debug($"Auto clean memory cache:{cacheItem.Key.ToString()}");
22          }
23      }
24  }